/**
 * This file defines the interface for a plugin manifest.
 *
 * Note that it is possible to validate a manifest against a TypeScript interface as follows:
 * (1) Convert the interface to a JSON schema at build time using
 *     https://www.npmjs.com/package/typescript-json-schema:
 *     bin/typescript-json-schema --required --noExtraProps PluginManifest.ts PublishedPlugin
 * (2) Use a JSON schema validator like https://www.npmjs.com/package/ajv to validate manifests
 *     read at run-time and produce informative errors automatically.
 *
 * TODO [Proposal]: To save an ImportSource for reuse, we would save:
 *  {
 *    pluginId: string;
 *    importSource: ImportSource;
 *    importProcessor?: Implementation;
 *    parseOptions?: ParseOptions;        // If importProcessor is omitted and fileParser is used.
 *  }
 * This should suffice for database re-imports, as well as for re-imports from a URL, or from a
 * saved path in the filesystem (which can be a builtIn plugin available for Electron version).
 */

/**
 * PublishedPlugin is a BarePlugin with additional attributes to identify and describe a plugin
 * for publishing.
 */
export interface PublishedPlugin extends BarePlugin {
  name: string;
  version: string;
}


/**
 * BarePlugin defines the functionality of a plugin. It is the only part required for a plugin to
 * function, and is implemented by built-in plugins, published plugins, and private plugins (such
 * as those being developed).
 */
export interface BarePlugin {
  /**
   * Components describe how the plugin runs. A plugin may provide UI and behavior that runs in
   * the browser, Python code that runs in a secure sandbox, and arbitrary code that runs in Node.
   */
  components: {
    /**
     * Relative path to the directory whose content will be served to the browser. Required for
     * those plugins that need to render their own HTML or run in-browser Javascript. This
     * directory should contain all html files referenced in the manifest.
     *
     * It is "safe" in that Grist offers protections that allow such plugins to be marked "safe".
     */
    safeBrowser?: string;

    /**
     * Relative path to a file with Python code that will be run in a python sandbox. This
     * file is started on plugin activation, and should register any implemented APIs.
     * Required for plugins that do Python processing.
     *
     * It is "safe" in that Grist offers protections that allow such plugins to be marked "safe".
     */
    safePython?: string;

    /**
     * Relative path to a file containing Javascript code that will be executed with Node.js.
     * The code is called on plugin activation, and should register any implemented APIs
     * once we've figured out how that should happen (TODO).  Required for plugins that need
     * to do any "unsafe" work, such as accessing the local filesystem or starting helper
     * programs.
     *
     * It is "unsafe" in that it can do too much, and Grist marks such plugins as "unsafe".
     *
     * An unsafeNode component opens as separate process to run plugin node code, with the
     * NODE_PATH set to the plugin directory.  The node code can execute arbitrary actions -
     * there is no sandboxing.
     *
     * The node child may communicate with the server via standard ChildProcess ipc
     * (`process.send`, `process.on('message', ...)`).  The child is expected to
     * `process.send` a message to the server once it is listening to the `message`
     * event.  That message is expected to contain a `ready` field set to `true`.  All
     * other communication should follow the protocol implemented by the Rpc module.
     * TODO: provide plugin authors with documentation + library to use that implements
     * these requirements.
     *
     */
    unsafeNode?: string;

    /**
     * Options for when to deactivate the plugin, i.e. when to stop any plugin processes. (Note
     * that we may in the future also add options for when to activate the plugin, which is for
     * now automatic and not configurable.)
     */
    deactivate?: {
      // Deactivate after this many seconds of inactivity. Defaults to 300 (5 minutes) if omitted.
      inactivitySec?: number;
    }
  };

  /**
   * Contributions describe what new functionality the plugin contributes to the Grist
   * application. See documentation for individual contribution types for details. Any plugin may
   * provide multiple contributions. It is common to provide just one, in which case include a
   * single property with a single-element array.
   */
  contributions: {
    importSources?: ImportSource[];
    fileParsers?: FileParser[];
    customSections?: CustomSection[];
  };

  /**
   * Experimental plugins run only if the environment variable GRIST_EXPERIMENTAL_PLUGINS is
   * set. Otherwise they are ignored. This is useful for plugins that needs a bit of experimentation
   * before being pushed to production (ie: production does not have GRIST_EXPERIMENTAL_PLUGINS set
   * but staging does). Keep in mind that developers need to set this environment if they want to
   * run them locally.
   */
  experimental?: boolean;
}

/**
 * An ImportSource plugin creates a new source of imports, such as an external API, a file-sharing
 * service, or a new type of database. It adds a new item for the user to select when importing.
 */
export interface ImportSource {
  /**
   * Label shows up as a new item for the user to select when starting an import.
   */
  label: string;

  /**
   * Whether this import source can be exposed on a home screen for all users. Home imports
   * support only a safeBrowser component and have no access to current document. Primarily used as
   * an external/cloud storage providers.
   */
  safeHome?: boolean;

  /**
   * Implementation of ImportSourceAPI. Supports safeBrowser component, which allows you to create
   * custom UI to show to the user. Or describe UI using a .json or .yml config file and use
   * {component: "builtIn", name: "importSourceConfig", path: "your-config"}.
   */
  importSource: Implementation;

  /**
   * Implementation of ImportProcessorAPI. It receives the output of importSource, and produces
   * Grist data. If omitted, uses the default ImportProcessor, which is equivalent to
   * {component: "builtIn", name: "fileParser"}.
   *
   * The default ImportProcessor handles received ImportSourceItems as follows:
   *    (1) items of type "file" are saved to temp files.
   *    (2) items of type "url" are downloaded to temp files.
   *    (3) calls ParseFileAPI.parseFile() with all temp files, to produce Grist tables
   *    (4) returns those Grist tables along with all items of type "table".
   * Note that the default ImportParser ignores ImportSource items of type "custom".
   */
  importProcessor?: Implementation;

}

/**
 * A FileParser plugin adds support to parse a new type of file data, such as "csv", "yml", or
 * "ods". It then enables importing the new type of file via upload or from any other ImportSource
 * that produces Files or URLs.
 */
export interface FileParser {
  /**
   * File extensions for which this FileParser should be considered, e.g. "csv", "yml". You may
   * use "" for files with no extensions, and "*" to match any extension.
   */
  fileExtensions: string[];

  /**
   * Implementation of EditOptionsAPI. Supports safeBrowser component, which allows you to create
   * custom UI to show to the user. Or describe UI using a .json or .yml config file and use
   * {component: "builtIn", name: "parseOptionsConfig", path: "your-config"}.
   *
   * If omitted, the user will be shown no parse options.
   */
  editOptions?: Implementation;

  /**
   * Implementation of ParseFileAPI, which converts Files to Grist data using parse options.
   */
  parseFile: Implementation;
}

/**
 * A CustomSection plugin adds support to add new types of section to Grist, such as a calendar,
 * maps, data visualizations.
 */
export interface CustomSection {
  /**
   * Path to an html file.
   */
  path: string;
  /**
   * The name should uniquely identify the section in the plugin.
   */
  name: string;
}

/**
 * A Plugin supplies one or more Implementation of some APIs. Components register implementation
 * using a call such as:
 *    grist.register(SomeAPI, 'myName', impl).
 * The manifest documentation describes which API must be implemented at any particular point, and
 * it is the plugin's responsibility to register an implementation of the correct API and refer to
 * it by Implementation.name.
 */
export interface Implementation  {
  /**
   * Which component of the plugin provides this implementation.
   */
  component: "safeBrowser" | "safePython" | "unsafeNode";

  /**
   * The name of the implementation registered by the chosen component. The same component can
   * register any number of APIs at any names.
   */
  name: string;

  /**
   * Path is used by safeBrowser component for which page to load. Defaults to 'index.html'.
   * It is also used by certain builtIn implementation, e.g. if name is 'parse-options-config',
   * path is the path to JSON or YAML file containing the configuration.
   */
  path?: string;
}