// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

export namespace Chrome {
  export namespace DevTools {
    export interface EventSink<ListenerT extends(...args: any[]) => void> {
      addListener(listener: ListenerT): void;
      removeListener(listener: ListenerT): void;
    }

    export interface Resource {
      readonly url: string;
      readonly type: string;

      /**
       * For WASM resources the content of the `build_id` custom section. For JavaScript resources the
       * `debugId` magic comment.
       */
      readonly buildId?: string;

      getContent(callback: (content: string, encoding: string) => unknown): void;
      setContent(content: string, commit: boolean, callback?: (error?: Object) => unknown): void;
      /**
       * Augments this resource's scopes information based on the list of {@link NamedFunctionRange}s
       * for improved debuggability and function naming.
       *
       * @throws
       * If this resource was not produced by a sourcemap or if {@link ranges} are not nested properly.
       * Concretely: For each range, start position must be less than end position, and
       * there must be no "straddling" (i.e. partially overlapping ranges).
       */
      setFunctionRangesForScript(ranges: NamedFunctionRange[]): Promise<void>;
      attachSourceMapURL(sourceMapURL: string): Promise<void>;
    }

    export interface InspectedWindow {
      tabId: number;

      onResourceAdded: EventSink<(resource: Resource) => unknown>;
      onResourceContentCommitted: EventSink<(resource: Resource, content: string) => unknown>;

      eval(
          expression: string,
          options?: {scriptExecutionContext?: string, frameURL?: string, useContentScriptContext?: boolean},
          callback?: (result: unknown, exceptioninfo: {
            code: string,
            description: string,
            details: unknown[],
            isError: boolean,
            isException: boolean,
            value: string,
          }) => unknown): void;
      getResources(callback: (resources: Resource[]) => unknown): void;
      reload(reloadOptions?: {ignoreCache?: boolean, injectedScript?: string, userAgent?: string}): void;
    }

    export interface Button {
      onClicked: EventSink<() => unknown>;
      update(iconPath?: string, tooltipText?: string, disabled?: boolean): void;
    }

    export interface ExtensionView {
      onHidden: EventSink<() => unknown>;
      onShown: EventSink<(window?: Window) => unknown>;
    }

    export interface ExtensionPanel extends ExtensionView {
      show(): void;
      onSearch: EventSink<(action: string, queryString?: string) => unknown>;
      createStatusBarButton(iconPath: string, tooltipText: string, disabled: boolean): Button;
    }

    export interface RecorderView extends ExtensionView {
      show(): void;
    }

    export interface ExtensionSidebarPane extends ExtensionView {
      setHeight(height: string): void;
      setObject(jsonObject: string, rootTitle?: string, callback?: () => unknown): void;
      setPage(path: string): void;
    }

    export interface PanelWithSidebar {
      createSidebarPane(title: string, callback?: (result: ExtensionSidebarPane) => unknown): void;
      onSelectionChanged: EventSink<() => unknown>;
    }

    export interface Panels {
      elements: PanelWithSidebar;
      sources: PanelWithSidebar;
      network: NetworkPanel;
      themeName: string;

      create(title: string, iconPath: string, pagePath: string, callback?: (panel: ExtensionPanel) => unknown): void;
      openResource(url: string, lineNumber: number, columnNumber?: number, callback?: () => unknown): void;

      setOpenResourceHandler(
          callback?: (resource: Resource, lineNumber: number, columnNumber: number) => void, scheme?: string): void;

      /**
       * Fired when the theme changes in DevTools.
       *
       * @param callback The handler callback to register and be invoked on theme changes.
       */
      setThemeChangeHandler(callback?: (themeName: string) => unknown): void;
    }

    export interface Request {
      getContent(callback: (content: string, encoding: string) => unknown): void;
    }

    export interface Network {
      onNavigated: EventSink<(url: string) => unknown>;
      onRequestFinished: EventSink<(request: Request) => unknown>;

      getHAR(callback: (harLog: object) => unknown): void;
    }

    export interface NetworkPanel {
      show(options?: {filter: string}): Promise<void>;
    }

    export interface DevToolsAPI {
      network: Network;
      panels: Panels;
      inspectedWindow: InspectedWindow;
      languageServices: LanguageExtensions;
      recorder: RecorderExtensions;
      performance: Performance;
    }

    export interface ExperimentalDevToolsAPI {
      inspectedWindow: InspectedWindow;
    }

    export interface RawModule {
      url: string;
      code?: ArrayBuffer;
    }

    export interface RawLocationRange {
      rawModuleId: string;
      startOffset: number;
      endOffset: number;
    }

    export interface RawLocation {
      rawModuleId: string;
      codeOffset: number;
      inlineFrameIndex: number;
    }

    export interface SourceLocation {
      rawModuleId: string;
      sourceFileURL: string;
      lineNumber: number;
      columnNumber: number;
    }

    export interface Variable {
      scope: string;
      name: string;
      type: string;
      nestedName?: string[];
    }

    export interface ScopeInfo {
      type: string;
      typeName: string;
      icon?: string;
    }

    export interface FunctionInfo {
      name: string;
    }

    export type RecorderExtensionPlugin = RecorderExtensionExportPlugin|RecorderExtensionReplayPlugin;

    export interface RecorderExtensionExportPlugin {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      stringify(recording: Record<string, any>): Promise<string>;
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      stringifyStep(step: Record<string, any>): Promise<string>;
    }
    export interface RecorderExtensionReplayPlugin {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      replay(recording: Record<string, any>): void;
    }

    export type RemoteObjectId = string;
    export type RemoteObjectType = 'object'|'undefined'|'string'|'number'|'boolean'|'bigint'|'array'|'null';

    export interface RemoteObject {
      type: RemoteObjectType;
      className?: string;
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      value?: any;
      description?: string;
      objectId?: RemoteObjectId;
      linearMemoryAddress?: number;
      linearMemorySize?: number;
      hasChildren: boolean;
    }

    /**
     * This refers to a Javascript or a Wasm value of reference type
     * in the V8 engine. We call it foreign object here to emphasize
     * the difference with the remote objects managed by a language
     * extension plugin.
     */
    export interface ForeignObject {
      type: 'reftype';
      valueClass: 'local'|'global'|'operand';
      index: number;
    }

    export interface PropertyDescriptor {
      name: string;
      value: RemoteObject|ForeignObject;
    }

    export interface LanguageExtensionPlugin {
      /**
       * A new raw module has been loaded. If the raw wasm module references an external debug info module, its URL will be
       * passed as symbolsURL.
       */
      addRawModule(rawModuleId: string, symbolsURL: string|undefined, rawModule: RawModule):
          Promise<string[]|{missingSymbolFiles: string[]}>;

      /**
       * Find locations in raw modules from a location in a source file.
       */
      sourceLocationToRawLocation(sourceLocation: SourceLocation): Promise<RawLocationRange[]>;

      /**
       * Find locations in source files from a location in a raw module.
       */
      rawLocationToSourceLocation(rawLocation: RawLocation): Promise<SourceLocation[]>;

      /**
       * Return detailed information about a scope.
       */
      getScopeInfo(type: string): Promise<ScopeInfo>;

      /**
       * List all variables in lexical scope at a given location in a raw module.
       */
      listVariablesInScope(rawLocation: RawLocation): Promise<Variable[]>;

      /**
       * Notifies the plugin that a script is removed.
       */
      removeRawModule(rawModuleId: string): Promise<void>;

      /**
       * Retrieve function name(s) for the function(s) containing the rawLocation. This returns more than one entry if
       * the location is inside of an inlined function with the innermost function at index 0.
       */
      getFunctionInfo(rawLocation: RawLocation): Promise<{frames: FunctionInfo[], missingSymbolFiles: string[]}|
                                                         {missingSymbolFiles: string[]}|{frames: FunctionInfo[]}>;

      /**
       * Find locations in raw modules corresponding to the inline function
       * that rawLocation is in. Used for stepping out of an inline function.
       */
      getInlinedFunctionRanges(rawLocation: RawLocation): Promise<RawLocationRange[]>;

      /**
       * Find locations in raw modules corresponding to inline functions
       * called by the function or inline frame that rawLocation is in.
       * Used for stepping over inline functions.
       */
      getInlinedCalleesRanges(rawLocation: RawLocation): Promise<RawLocationRange[]>;

      /**
       * Retrieve a list of line numbers in a file for which line-to-raw-location mappings exist.
       */
      getMappedLines(rawModuleId: string, sourceFileURL: string): Promise<number[]|undefined>;

      /**
       * Evaluate a source language expression in the context of a given raw location and a given stopId. stopId is an
       * opaque key that should be passed to the APIs accessing wasm state, e.g., getWasmLinearMemory. A stopId is
       * invalidated once the debugger resumes.
       */
      evaluate(expression: string, context: RawLocation, stopId: unknown): Promise<RemoteObject|ForeignObject|null>;

      /**
       * Retrieve properties of the remote object identified by the object id.
       */
      getProperties(objectId: RemoteObjectId): Promise<PropertyDescriptor[]>;

      /**
       * Permanently release the remote object identified by the object id.
       */
      releaseObject(objectId: RemoteObjectId): Promise<void>;
    }

    export interface SupportedScriptTypes {
      language: string;
      // eslint-disable-next-line @typescript-eslint/naming-convention
      symbol_types: string[];
    }

    export type WasmValue = {type: 'i32'|'f32'|'f64', value: number}|{type: 'i64', value: bigint}|
        {type: 'v128', value: string}|ForeignObject;

    export interface LanguageExtensions {
      registerLanguageExtensionPlugin(
          plugin: LanguageExtensionPlugin, pluginName: string,
          supportedScriptTypes: SupportedScriptTypes): Promise<void>;
      unregisterLanguageExtensionPlugin(plugin: LanguageExtensionPlugin): Promise<void>;

      getWasmLinearMemory(offset: number, length: number, stopId: unknown): Promise<ArrayBuffer>;
      getWasmLocal(local: number, stopId: unknown): Promise<WasmValue>;
      getWasmGlobal(global: number, stopId: unknown): Promise<WasmValue>;
      getWasmOp(op: number, stopId: unknown): Promise<WasmValue>;

      reportResourceLoad(resourceUrl: string, status: {success: boolean, errorMessage?: string, size?: number}):
          Promise<void>;
    }

    export interface Position {
      line: number;
      column: number;
    }

    export interface NamedFunctionRange {
      readonly name: string;
      readonly start: Position;
      readonly end: Position;
    }

    export interface RecorderExtensions {
      registerRecorderExtensionPlugin(plugin: RecorderExtensionPlugin, pluginName: string, mediaType?: string):
          Promise<void>;
      unregisterRecorderExtensionPlugin(plugin: RecorderExtensionPlugin): Promise<void>;
      createView(title: string, pagePath: string): Promise<RecorderView>;
    }

    export interface Performance {
      onProfilingStarted: EventSink<() => unknown>;
      onProfilingStopped: EventSink<() => unknown>;
    }

    export interface Chrome {
      devtools: DevToolsAPI;
      experimental: {devtools: ExperimentalDevToolsAPI};
    }
  }
}

declare global {
  interface Window {
    chrome: Chrome.DevTools.Chrome;
  }
}
