/**
 * @license
 * Copyright 2023 Google Inc.
 * SPDX-License-Identifier: Apache-2.0
 */

import type {JSHandle} from '../api/JSHandle.js';
import {Realm} from '../api/Realm.js';
import type {TimeoutSettings} from '../common/TimeoutSettings.js';
import type {EvaluateFunc, HandleFor} from '../common/types.js';
import {withSourcePuppeteerURLIfNone} from '../common/util.js';

import type {BrowsingContext} from './BrowsingContext.js';
import {BidiElementHandle} from './ElementHandle.js';
import type {BidiFrame} from './Frame.js';
import type {BidiRealm as BidiRealm} from './Realm.js';
/**
 * A unique key for {@link SandboxChart} to denote the default world.
 * Realms are automatically created in the default sandbox.
 *
 * @internal
 */
export const MAIN_SANDBOX = Symbol('mainSandbox');
/**
 * A unique key for {@link SandboxChart} to denote the puppeteer sandbox.
 * This world contains all puppeteer-internal bindings/code.
 *
 * @internal
 */
export const PUPPETEER_SANDBOX = Symbol('puppeteerSandbox');

/**
 * @internal
 */
export interface SandboxChart {
  [key: string]: Sandbox;
  [MAIN_SANDBOX]: Sandbox;
  [PUPPETEER_SANDBOX]: Sandbox;
}

/**
 * @internal
 */
export class Sandbox extends Realm {
  readonly name: string | undefined;
  readonly realm: BidiRealm;
  #frame: BidiFrame;

  constructor(
    name: string | undefined,
    frame: BidiFrame,
    // TODO: We should split the Realm and BrowsingContext
    realm: BidiRealm | BrowsingContext,
    timeoutSettings: TimeoutSettings
  ) {
    super(timeoutSettings);
    this.name = name;
    this.realm = realm;
    this.#frame = frame;
    this.realm.setSandbox(this);
  }

  override get environment(): BidiFrame {
    return this.#frame;
  }

  async evaluateHandle<
    Params extends unknown[],
    Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
  >(
    pageFunction: Func | string,
    ...args: Params
  ): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
    pageFunction = withSourcePuppeteerURLIfNone(
      this.evaluateHandle.name,
      pageFunction
    );
    return await this.realm.evaluateHandle(pageFunction, ...args);
  }

  async evaluate<
    Params extends unknown[],
    Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
  >(
    pageFunction: Func | string,
    ...args: Params
  ): Promise<Awaited<ReturnType<Func>>> {
    pageFunction = withSourcePuppeteerURLIfNone(
      this.evaluate.name,
      pageFunction
    );
    return await this.realm.evaluate(pageFunction, ...args);
  }

  async adoptHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
    return (await this.evaluateHandle(node => {
      return node;
    }, handle)) as unknown as T;
  }

  async transferHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
    if (handle.realm === this) {
      return handle;
    }
    const transferredHandle = await this.evaluateHandle(node => {
      return node;
    }, handle);
    await handle.dispose();
    return transferredHandle as unknown as T;
  }

  override async adoptBackendNode(
    backendNodeId?: number
  ): Promise<JSHandle<Node>> {
    const {object} = await this.environment.client.send('DOM.resolveNode', {
      backendNodeId: backendNodeId,
    });
    return new BidiElementHandle(this, {
      handle: object.objectId,
      type: 'node',
    });
  }
}
