import type {
  PortableBridgeRequest,
  PortableRegexBridge,
} from "../portable/PortableEngineAdapter";
import {
  decodeBase64OutputEnvelope,
  maximumBase64JsonUtf16,
} from "../portable/base64-output";
import {
  MAXIMUM_PORTABLE_REQUEST_JSON_BYTES,
  stringifyBoundedJsonRequest,
} from "../portable/json-request";

const MAXIMUM_IDENTITY_JSON_UTF16 = 16 * 1024;
const MAXIMUM_NON_OUTPUT_RESULT_JSON_UTF16 = 16 * 1024 * 1024;

export interface RustRegexWasmModule {
  readonly default: (input?: unknown) => Promise<unknown>;
  readonly initSync: (input: unknown) => unknown;
  readonly identity: () => string;
  readonly run: (encoded: string) => string;
}

function isRustRegexWasmModule(value: unknown): value is RustRegexWasmModule {
  if (typeof value !== "object" || value === null) return false;
  const module = value as Partial<RustRegexWasmModule>;
  return (
    typeof module.default === "function" &&
    typeof module.initSync === "function" &&
    typeof module.identity === "function" &&
    typeof module.run === "function"
  );
}

function parseJsonText(
  value: unknown,
  maximumUtf16: number,
  label: string,
): Readonly<Record<string, unknown>> {
  if (typeof value !== "string") {
    throw new Error(`${label} did not return JSON text.`);
  }
  if (value.length > maximumUtf16) {
    throw new Error(`${label} returned JSON beyond its bounded contract.`);
  }
  let parsed: unknown;
  try {
    parsed = JSON.parse(value);
  } catch {
    throw new Error(`${label} returned invalid JSON.`);
  }
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
    throw new Error(`${label} returned a non-object JSON value.`);
  }
  return parsed as Readonly<Record<string, unknown>>;
}

export async function importRustRegexWasmModule(
  moduleUrl: URL,
  wasmUrl: URL,
): Promise<RustRegexWasmModule> {
  const imported: unknown = await import(/* @vite-ignore */ moduleUrl.href);
  if (!isRustRegexWasmModule(imported)) {
    throw new Error(
      "The bundled Rust regex module is missing its bridge exports.",
    );
  }
  await imported.default({ module_or_path: wasmUrl });
  return imported;
}

export class RustWasmBridge implements PortableRegexBridge {
  readonly #modulePromise: Promise<RustRegexWasmModule>;
  #loadPromise: Promise<Readonly<Record<string, unknown>>> | undefined;
  #terminated = false;

  constructor(module: RustRegexWasmModule | Promise<RustRegexWasmModule>) {
    this.#modulePromise = Promise.resolve(module);
  }

  load(): Promise<Readonly<Record<string, unknown>>> {
    this.#loadPromise ??= Promise.resolve().then(async () => {
      if (this.#terminated) {
        throw new Error("The Rust WebAssembly bridge has been terminated.");
      }
      const module = await this.#modulePromise;
      return parseJsonText(
        module.identity(),
        MAXIMUM_IDENTITY_JSON_UTF16,
        "The Rust identity export",
      );
    });
    return this.#loadPromise;
  }

  async run(
    request: PortableBridgeRequest,
  ): Promise<Readonly<Record<string, unknown>>> {
    await this.load();
    if (this.#terminated) {
      throw new Error("The Rust WebAssembly bridge has been terminated.");
    }
    const module = await this.#modulePromise;
    const maximumOutputBytes =
      request.operation === "replace"
        ? (request.maximumOutputBytes ?? 0)
        : undefined;
    const label = "The Rust run export";
    const encodedRequest = stringifyBoundedJsonRequest(
      request,
      MAXIMUM_PORTABLE_REQUEST_JSON_BYTES,
      label,
    );
    const parsed = parseJsonText(
      module.run(encodedRequest),
      maximumBase64JsonUtf16(
        MAXIMUM_NON_OUTPUT_RESULT_JSON_UTF16,
        maximumOutputBytes ?? 0,
      ),
      label,
    );
    return decodeBase64OutputEnvelope(parsed, maximumOutputBytes, label);
  }

  terminate(): void {
    this.#terminated = true;
  }
}
