/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file contains the shared types for the translations component. The intended use
 * is for defining types to be used in JSDoc. They are used in a form that the TypeScript
 * language server can read them, and provide code hints.
 */

/**
 * For Remote Settings, the JSON details about the attachment.
 */
export interface Attachment {
  // e.g. "2f7c0f7bbc...ca79f0850c4de",
  hash: string;
  // e.g. 5047568,
  size: number;
  // e.g. "lex.50.50.deen.s2t.bin",
  filename: string;
  // e.g. "main-workspace/translations-models/316ebb3a-0682-42cc-8e73-a3ba4bbb280f.bin",
  location: string;
  // e.g. "application/octet-stream"
  mimetype: string;
}

/**
 * The JSON that is synced from Remote Settings for the translation models.
 */
export interface TranslationModelRecord {
  // e.g. "0d4db293-a17c-4085-9bd8-e2e146c85000"
  id: string;
  // The full model name, e.g. "lex.50.50.deen.s2t.bin"
  name: string;
  // The BCP 47 language tag, e.g. "de"
  sourceLanguage: string;
  // The BCP 47 language tag, e.g. "en"
  targetLanguage: string;
  // The architecture of the model, e.g "base", "base-memory", "tiny"
  architecture: string;
  // A model variant. This is a developer-only property that can be used in Nightly or
  // local builds to test different types of models.
  variant?: string;
  // The semver number, used for handling future format changes. e.g. 1.0
  version: string;
  // e.g. "lex"
  fileType: string;
  // The sha256 hash of the decompressed file
  decompressedHash: string;
  // The size of the decompressed file (bytes)
  decompressedSize: number;
  // The file attachment for this record
  attachment: Attachment;
  // e.g. 1673023100578
  schema: number;
  // e.g. 1673455932527
  last_modified: string;
  // A JEXL expression to determine whether this record should be pulled from Remote Settings
  // See: https://remote-settings.readthedocs.io/en/latest/target-filters.html#filter-expressions
  filter_expression: string;
}

/**
 * The JSON that is synced from Remote Settings for the wasm binaries.
 */
export interface WasmRecord {
  // e.g. "0d4db293-a17c-4085-9bd8-e2e146c85000"
  id: string;
  // The name of the project, e.g. "bergamot-translator"
  name: string;
  // The human readable identifier for the release. e.g. "v0.4.4"
  release: string;
  // The commit hash for the project that generated the wasm.
  revision: string;
  // The license of the wasm, as a https://spdx.org/licenses/
  license: string;
  // The semver number, used for handling future format changes. e.g. 1.0
  version: string;
  // The sha256 hash of the decompressed file
  decompressedHash: string;
  // The size of the decompressed wasm file (bytes)
  decompressedSize: number;
  // The file attachment for this record
  attachment: Attachment;
  // e.g. 1673455932527
  last_modified: string;
  // A JEXL expression to determine whether this record should be pulled from Remote Settings
  // See: https://remote-settings.readthedocs.io/en/latest/target-filters.html#filter-expressions
  filter_expression: string;
}

export type TranslationsRecord = TranslationModelRecord | WasmRecord;

/**
 * The following are the types that are provided by the Bergamot wasm library.
 *
 * See: https://github.com/mozilla/bergamot-translator/tree/main/wasm/bindings
 */
export namespace Bergamot {
  /**
   * The main module that is returned from bergamot-translator.js.
   */
  export interface ModuleExport {
    BlockingService: typeof BlockingService;
    AlignedMemoryList: typeof AlignedMemoryList;
    TranslationModel: typeof TranslationModel;
    AlignedMemory: typeof AlignedMemory;
    VectorResponseOptions: typeof VectorResponseOptions;
    VectorString: typeof VectorString;
  }

  /**
   * This class represents a C++ std::vector. The implementations will extend from it.
   */
  export class Vector<T> {
    size(): number;
    get(index: number): T;
    push_back(item: T): void;
  }

  export class VectorResponse extends Vector<Response> {}
  export class VectorString extends Vector<string> {}
  export class VectorResponseOptions extends Vector<ResponseOptions> {}
  export class AlignedMemoryList extends Vector<AlignedMemory> {}

  /**
   * A blocking (e.g. non-threaded) translation service, via Bergamot.
   */
  export class BlockingService {
    /**
     * Translate multiple messages in a single synchronous API call using a single model.
     */
    translate(
      translationModel: TranslationModel,
      vectorSourceText: VectorString,
      vectorResponseOptions: VectorResponseOptions
    ): VectorResponse;

    /**
     * Translate by pivoting between two models
     *
     * For example to translate "fr" to "es", pivot using "en":
     *   "fr" to "en"
     *   "en" to "es"
     *
     * See https://github.com/mozilla/bergamot-translator/blob/5ae1b1ebb3fa9a3eabed8a64ca6798154bd486eb/src/translator/service.h#L80
     */
    translateViaPivoting(
      first: TranslationModel,
      second: TranslationModel,
      vectorSourceText: VectorString,
      vectorResponseOptions: VectorResponseOptions
    ): VectorResponse;
  }

  /**
   * The actual translation model, which is passed into the `BlockingService` methods.
   */
  export class TranslationModel {}

  /**
   * The models need to be placed in the wasm memory space. This object represents
   * aligned memory that was allocated on the wasm side of things. The memory contents
   * can be set via the getByteArrayView method and the Uint8Array.prototype.set method.
   */
  export class AlignedMemory {
    constructor(size: number, alignment: number);
    size(): number;
    getByteArrayView(): Uint8Array;
  }

  /**
   * The response from the translation. This definition isn't complete, but just
   * contains a subset of the available methods.
   *
   * See https://github.com/mozilla/bergamot-translator/blob/main/src/translator/response.h
   */
  export class Response {
    getOriginalText(): string;
    getTranslatedText(): string;
  }

  /**
   * The options to configure a translation response.
   *
   * See https://github.com/mozilla/bergamot-translator/blob/main/src/translator/response_options.h
   */
  export class ResponseOptions {
    // Include the quality estimations.
    qualityScores: boolean;
    // Include the alignments.
    alignment: boolean;
    // Remove HTML tags from text and insert it back into the output.
    html: boolean;
    // Whether to include sentenceMappings or not. Alignments require
    // sentenceMappings and are available irrespective of this option if
    // `alignment=true`.
    sentenceMappings: boolean;
  }
}

/**
 * The client to interact with RemoteSettings.
 * See services/settings/RemoteSettingsClient.sys.mjs
 */
export interface RemoteSettingsClient {
  on: Function;
  get: Function;
  sync: Function;
  attachments: Attachments;
}

export interface AttachmentDownloadResult {
  buffer: ArrayBuffer;
  blob?: Blob;
  record?: TranslationsRecord;
  _source?: string;
}

/**
 * Attachments API for Remote Settings.
 * Implemented by services/settings/Attachments.sys.mjs
 */
export interface Attachments {
  download(record: TranslationsRecord): Promise<AttachmentDownloadResult>;
  isDownloaded(record: TranslationsRecord): Promise<boolean>;
  deleteAll(): Promise<void>;
  deleteDownloaded(record: TranslationsRecord): Promise<void>;
}

/**
 * A single language model file.
 */
interface LanguageTranslationModelFile {
  blob: Blob | null;
  buffer?: ArrayBuffer;
  record: TranslationModelRecord;
}

/**
 * The data required to construct a Bergamot Translation Model.
 */
interface TranslationModelPayload {
  sourceLanguage: string;
  targetLanguage: string;
  variant?: string;
  languageModelFiles: LanguageTranslationModelFiles;
}

/**
 * The files required to construct a Bergamot Translation Model's aligned memory.
 */
interface LanguageTranslationModelFiles {
  // The machine learning language model.
  model: LanguageTranslationModelFile;
  // The lexical shortlist that limits possible output of the decoder and makes
  // inference faster.
  lex?: LanguageTranslationModelFile;

  // There is either a single vocab file:
  vocab?: LanguageTranslationModelFile;

  // Or there are two:
  srcvocab?: LanguageTranslationModelFile;
  trgvocab?: LanguageTranslationModelFile;
}

/**
 * This is the type that is generated when the models are loaded into wasm aligned memory.
 */
type LanguageTranslationModelFilesAligned = {
  [K in keyof LanguageTranslationModelFiles]: Bergamot.AlignedMemory;
};

/**
 * These are the files that are downloaded from Remote Settings that are necessary
 * to start the translations engine. These may not be available if running in tests,
 * and so the engine will be mocked.
 */
interface TranslationsEnginePayload {
  bergamotWasmBlob: Blob | null;
  bergamotWasmArrayBuffer?: ArrayBuffer;
  translationModelPayloads: TranslationModelPayload[];
  isMocked: boolean;
}

/**
 * Nodes that are being translated are given priority according to their visibility.
 */
export type NodeVisibility = "in-viewport" | "beyond-viewport" | "hidden";

/**
 * Document-language metadata collected from the content page.
 */
export interface DocumentLanguageMetadata {
  htmlLangAttribute: string;
  textSample: string;
}

/**
 * Used to decide how to translate a page for full page translations.
 */
export interface LangTags {
  isDocLangTagSupported: boolean;
  docLangTag: string | null;
  userLangTag: string | null;
  htmlLangAttribute: string | null;
  identified: null | DetectionResult;
}

/**
 * Describes how Translations should respond after considering page-language
 * identification, document metadata, and the user's Language and Translations settings.
 *
 * "allowOffer" means the page is eligible to move forward with automatic
 * translation or automatic offer UI. If auto-translate is enabled for the
 * resolved source language, the page should translate automatically. Otherwise,
 * Translations may offer the panel when automatic popup is enabled.
 *
 * "showButton" means Translations should show the URL-bar button, but should
 * not auto-translate or automatically offer the panel. This represents a scenario
 * in which we detect that there may be a translatable language, but we don't have
 * high confidence that the detection is correct. This makes the UI easier for the
 * user to open, without committing to making an automated decision.
 *
 * "hideButton" means Translations should hide the URL-bar button and not offer
 * translation UI for the page. This represents scenarios in which we do not think
 * that the current page is in a language that the user may want to translate.
 */
export type TranslationsOfferAction =
  | "allowOffer"
  | "showButton"
  | "hideButton";

/**
 * The normalized status of a page-language candidate used by the offer matrix.
 */
export type TranslationsOfferMatrixLanguageStatus =
  | "supported"
  | "user-language"
  | "unsupported"
  | null;

/**
 * A page-language candidate after matching it against Translations support and
 * the user's web content languages.
 */
export interface TranslationsOfferLanguageCandidate {
  langTag: string;
  supportedLangTag: string | null;
  matchesWebContentLanguage: boolean;
}

/**
 * A page-language candidate produced from language identification.
 */
export interface TranslationsOfferIdentifiedLanguageCandidate extends TranslationsOfferLanguageCandidate {
  identificationResult: DetectionResult;
}

/**
 * The page-language candidate selected by a Translations offer matrix outcome.
 */
export type TranslationsOfferMatrixSourceCandidate = "html" | "identified";

/**
 * The comparable page-language values used by the Translations offer matrix.
 * Omitted fields in a matrix entry are free variables.
 */
export interface TranslationsOfferMatrixConditions {
  htmlStatus?: TranslationsOfferMatrixLanguageStatus;
  identificationStatus?: TranslationsOfferMatrixLanguageStatus;
  identificationMatchesHtml?: boolean;
  identificationConfident?: boolean;
}

/**
 * The outcome to apply when a Translations offer matrix scenario matches.
 */
export interface TranslationsOfferMatrixOutcome {
  action: TranslationsOfferAction;
  sourceCandidate?: TranslationsOfferMatrixSourceCandidate;
}

/**
 * A scenario in the Translations offer matrix.
 */
export interface TranslationsOfferMatrixScenario {
  conditions: TranslationsOfferMatrixConditions;
  outcome: TranslationsOfferMatrixOutcome;
}

/**
 * The concrete action and resolved language tags to use for a Translations offer.
 */
export interface TranslationsOfferInstruction {
  action: TranslationsOfferAction;
  resolvedLangTags: LangTags;
}

/**
 * All of the necessary information to pick models for doing a translation. This pair
 * should be solvable by picking model variants, and pivoting through English.
 */
export interface LanguagePair {
  sourceLanguage: string;
  targetLanguage: string;
  sourceVariant?: string;
  targetVariant?: string;
}

/**
 * In the case of a single model, there will only be a single potential model variant.
 * A LanguagePair can resolve into 1 or 2 NonPivotLanguagePair depending on the pivoting
 * needs and how they are resolved.
 */
export interface NonPivotLanguagePair {
  sourceLanguage: string;
  targetLanguage: string;
  variant?: string;
}

export interface SupportedLanguage {
  langTag: string;
  langTagKey: string;
  variant: string;
  displayName: string;
}

/**
 * A structure that contains all of the information needed to render dropdowns
 * for translation language selection.
 */
export interface SupportedLanguages {
  languagePairs: NonPivotLanguagePair[];
  sourceLanguages: Array<SupportedLanguage>;
  targetLanguages: Array<SupportedLanguage>;
}

export type TranslationErrors = "engine-load-error";

export type SelectTranslationsPanelState =
  // The panel is closed.
  | { phase: "closed" }

  // The panel is idle after successful initialization and ready to attempt translation.
  | {
      phase: "idle";
      sourceLanguage: string;
      targetLanguage: string;
      sourceText: string;
    }

  // The language dropdown menus failed to populate upon opening the panel.
  // This state contains all of the information for the try-again button to close and re-open the panel.
  | {
      phase: "init-failure";
      event: Event;
      screenX: number;
      screenY: number;
      sourceText: string;
      isTextSelected: boolean;
      langPairPromise: Promise<{
        sourceLanguage?: string;
        targetLanguage?: string;
      }>;
    }

  // The translation failed to complete.
  | {
      phase: "translation-failure";
      sourceLanguage: string;
      targetLanguage: string;
      sourceText: string;
    }

  // The selected language pair is determined to be translatable.
  | {
      phase: "translatable";
      sourceLanguage: string;
      targetLanguage: string;
      sourceText: string;
    }

  // The panel is actively translating the source text.
  | {
      phase: "translating";
      sourceLanguage: string;
      targetLanguage: string;
      sourceText: string;
    }

  // The source text has been translated successfully.
  | {
      phase: "translated";
      sourceLanguage: string;
      targetLanguage: string;
      sourceText: string;
      translatedText: string;
    }

  // The source language is not currently supported by Translations in Firefox.
  | {
      phase: "unsupported";
      detectedLanguage: string;
      targetLanguage: string;
      sourceText: string;
    };

export type RequestTranslationsPort = (
  languagePair: LanguagePair
) => Promise<MessagePort>;

export type TranslationsPortMessages =
  // We have determined that the source text is already translated into the target language, so do nothing.
  | { type: "TranslationsPort:Passthrough"; translationId: string }
  // We found translated text for this request in our cache, so send the targetText directly without translating.
  | {
      type: "TranslationsPort:CachedTranslation";
      translationId: string;
      targetText: string;
    }
  // This is a new, uncached request, and it needs to be translated by the TranslationsEngine.
  | {
      type: "TranslationsPort:TranslationRequest";
      translationId: string;
      sourceText: string;
      isHTML: boolean;
    };

export type EngineStatus = "uninitialized" | "ready" | "error" | "closed";

export type PortToPage =
  // The targetText may be null if the TranslationsEngine had an error, or if this is a response to a Passthrough.
  | {
      type: "TranslationsPort:TranslationResponse";
      targetText: string | null;
      translationId: number;
    }
  | { type: "TranslationsPort:GetEngineStatusResponse"; status: EngineStatus }
  | { type: "TranslationsPort:EngineTerminated" };

/**
 * The translation mode of the page.
 *
 * - In "lazy" mode only nodes within proximity to the viewport are translated.
 *
 * - In "content-eager" mode, all nodes with translatable text content will be translated,
 *   but nodes with attribute translations will still be translated lazily.
 */
export type TranslationsMode = "lazy" | "content-eager";

/**
 * A hint at the user's most recent scroll direction on the page.
 */
export type ScrollDirection = "up" | "down";

/**
 * The location of a node with respect to the viewport.
 */
export type NodeViewportContext =
  | "within"
  | "above"
  | "right"
  | "below"
  | "left";

/**
 * The spatial context of a node, which may include the top, left, and right coordinates
 * of the node's bounding client rect, as well as the node's location with respect to the viewport.
 */
export interface NodeSpatialContext {
  top?: number;
  right?: number;
  left?: number;
  viewportContext?: NodeViewportContext;
}

/**
 * The eligibility of a node to be updated with translated content when its request completes.
 */
export type UpdateEligibility = "stale" | "detached" | "valid";

/**
 * An element with translatable content that is sortable based on its spatial context with
 * respect to the viewport.
 */
export interface SortableContentElement {
  element: Element;
  nodeSet: Set<Node>;
  top?: number;
  left?: number;
  right?: number;
}

/**
 * Elements that have been prioritized for content translations based on their spatial context
 * with respect to the viewport.
 */
export interface PrioritizedContentElements {
  titleElement?: Element;
  inViewportContent: Array<SortableContentElement>;
  aboveViewportContent: Array<SortableContentElement>;
  belowViewportContent: Array<SortableContentElement>;
  otherContent: Array<SortableContentElement>;
}

/**
 * An element with translatable attributes that is sortable based on its spatial context with
 * respect to the viewport.
 */
export interface SortableAttributeElement {
  element: Element;
  attributeSet: Set<string>;
  top?: number;
  left?: number;
  right?: number;
}

/**
 * Elements that have been prioritized for content translations based on their spatial context
 * with respect to the viewport.
 */
export interface PrioritizedAttributeElements {
  inViewportAttributes: Array<SortableAttributeElement>;
  aboveViewportAttributes: Array<SortableAttributeElement>;
  belowViewportAttributes: Array<SortableAttributeElement>;
  otherAttributes: Array<SortableAttributeElement>;
}

/**
 * These are the kinds of priorities that a translation request may be assigned.
 * Each time requests are prioritized and sent to the scheduler, each kind of
 * priority defined below will receive a unique number. Depending on the current
 * context within the page, some of these priorities may be more or less important.
 */
export interface TranslationPriorityKinds {
  inViewportContentPriority: number;
  inViewportAttributePriority: number;
  aboveViewportContentPriority: number;
  aboveViewportAttributePriority: number;
  belowViewportContentPriority: number;
  belowViewportAttributePriority: number;
  otherContentPriority: number;
  otherAttributePriority: number;
}

/**
 * All of the information needed to perform a translation request.
 */
export interface TranslationRequest {
  node: Node;
  sourceText: string;
  translationId: number;
  isHTML: boolean;
  priority: number;
  resolve: (translation: Promise<string> | string | null) => unknown;
  reject: (reason: any) => unknown;
}

/**
 * A convenience type describing a function that executes a translation.
 */
export type TranslationFunction = (message: string) => Promise<string>;

/**
 * A larger web document can be composed of multiple languages. This object details the
 * breakdown of what languages are present in the document, and at what percentages.
 * For instance a document could be 70% English and 30% French:
 *
 *   [
 *      { language: "en", percentage: 70 },
 *      { language: "fr", percentage: 30 },
 *   ]
 */
interface MultilingualSection {
  // BCP 47 language tag, or "un" for unknown.
  language: string;
  // The integral percentage ranged 0-100.
  percent: number;
}

interface DetectionResult {
  // The language code.
  language: string;
  // Whether the detector is confident of the result.
  confident: boolean;
  // The list of languages detected in multilingual content. This is between 0 and 3
  // languages.
  languages: MultilingualSection[];
}
