/**
 * SandboxedToolAdapter: Makes a WASM plugin look like a regular ToolDefinition.
 *
 * Bridges WasmToolHost (WASM execution) and CredentialBoundary (secret injection)
 * to the pi extension system's ToolDefinition interface. The adapter serializes
 * tool params to JSON, calls the WASM plugin, deserializes output, and returns
 * an AgentToolResult. Optional tracking hooks (onToolStart/onToolEnd) are called
 * around WASM execution for auto-tool-tracking compatibility.
 */

/**
 * @typedef {import('./wasm-tool-host.js').WasmToolHost} WasmToolHost
 * @typedef {import('./credential-boundary.js').CredentialBoundary} CredentialBoundary
 */

/**
 * @typedef {object} ToolTrackingHooks
 * @property {(toolCallId: string, isActive: boolean, toolName?: string) => void} markToolStart
 * @property {(toolCallId: string) => void} markToolEnd
 */

/**
 * Adapter that wraps a WasmToolHost + CredentialBoundary pair as a ToolDefinition.
 *
 * The WASM plugin receives JSON-serialized params as input and must return
 * a JSON string as output. The adapter deserializes the output into the
 * AgentToolResult content format.
 */
export class SandboxedToolAdapter {
  /** @type {string} */
  #name;
  /** @type {string} */
  #label;
  /** @type {string} */
  #description;
  /** @type {object} */
  #parameters;
  /** @type {WasmToolHost} */
  #host;
  /** @type {string} */
  #exportName;
  /** @type {ToolTrackingHooks|null} */
  #tracking;

  /**
   * @param {object} options
   * @param {string} options.name - Tool name (used in LLM tool calls)
   * @param {string} options.label - Human-readable label for UI
   * @param {string} options.description - Description for LLM
   * @param {object} options.parameters - TypeBox parameter schema
   * @param {WasmToolHost} options.host - Loaded WasmToolHost instance
   * @param {string} [options.exportName='execute'] - WASM export function name to call
   * @param {ToolTrackingHooks} [options.tracking] - Optional auto-tool-tracking hooks
   */
  constructor({ name, label, description, parameters, host, exportName = 'execute', tracking }) {
    if (!name || typeof name !== 'string') {
      throw new Error('SandboxedToolAdapter: name is required');
    }
    if (!host) {
      throw new Error('SandboxedToolAdapter: host (WasmToolHost) is required');
    }

    this.#name = name;
    this.#label = label || name;
    this.#description = description || '';
    this.#parameters = parameters || {};
    this.#host = host;
    this.#exportName = exportName;
    this.#tracking = tracking || null;
  }

  /** Tool name (used in LLM tool calls) */
  get name() { return this.#name; }

  /** Human-readable label for UI */
  get label() { return this.#label; }

  /** Description for LLM */
  get description() { return this.#description; }

  /** Parameter schema (TypeBox) */
  get parameters() { return this.#parameters; }

  /**
   * Execute the sandboxed tool.
   *
   * Serializes params to JSON, calls the WASM plugin via WasmToolHost,
   * deserializes the output, and returns an AgentToolResult.
   *
   * @param {string} toolCallId - Unique ID for this tool call
   * @param {object} params - Tool parameters from LLM
   * @param {AbortSignal} [signal] - Cancellation signal
   * @param {Function} [onUpdate] - Streaming update callback (unused for WASM)
   * @param {object} [ctx] - Extension context (unused)
   * @returns {Promise<import('@nerf/pi-agent-core').AgentToolResult>}
   */
  async execute(toolCallId, params, signal, onUpdate, ctx) {
    if (this.#tracking) {
      this.#tracking.markToolStart(toolCallId, true, this.#name);
    }

    try {
      // Check for cancellation before WASM execution
      if (signal?.aborted) {
        return {
          content: [{ type: 'text', text: `Tool execution cancelled: ${this.#name}` }],
          isError: true,
        };
      }

      // Set up abort listener to close plugin on cancellation
      let abortHandler;
      if (signal) {
        abortHandler = () => {
          this.#host.close().catch(() => {});
        };
        signal.addEventListener('abort', abortHandler, { once: true });
      }

      try {
        const input = JSON.stringify(params);
        const rawOutput = await this.#host.call(this.#exportName, input);

        // Try to parse as JSON for structured output
        let outputText;
        try {
          const parsed = JSON.parse(rawOutput);
          outputText = typeof parsed === 'string' ? parsed : JSON.stringify(parsed, null, 2);
        } catch {
          // Not JSON: use raw string output
          outputText = rawOutput;
        }

        return {
          content: [{ type: 'text', text: outputText }],
          isError: false,
        };
      } finally {
        if (signal && abortHandler) {
          signal.removeEventListener('abort', abortHandler);
        }
      }
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      return {
        content: [{ type: 'text', text: `WASM tool "${this.#name}" execution failed: ${message}` }],
        isError: true,
      };
    } finally {
      if (this.#tracking) {
        this.#tracking.markToolEnd(toolCallId);
      }
    }
  }
}
