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

import * as Host from '../../../core/host/host.js';
import * as SDK from '../../../core/sdk/sdk.js';
import type {FunctionCallHandlerResult} from '../agents/AiAgent.js';
import {DOMNodeContext} from '../contexts/DOMNodeContext.js';

import {
  type BaseToolCapability,
  type OriginLockCapability,
  type TargetCapability,
  type Tool,
  type ToolArgs,
  ToolName,
} from './Tool.js';

/**
 * Arguments for resolving a Lighthouse path to a backend node ID.
 */
export interface ResolveLighthousePathArgs extends ToolArgs {
  /**
   * A Lighthouse-style element path.
   * This is typically a comma-separated list of child indices and tag names
   * representing the path from the root to the target element (e.g., "1,HTML,1,BODY").
   */
  path: string;
  explanation: string;
}

/**
 * A tool that resolves a Lighthouse-style element path to a backend node ID.
 *
 * This is used by the AI assistant to identify specific DOM nodes referred to in
 * Lighthouse reports. It ensures the resolved node belongs to the locked origin.
 */
export class ResolveLighthousePathTool implements
    Tool<ResolveLighthousePathArgs, {backendNodeId: number}, BaseToolCapability&TargetCapability&OriginLockCapability> {
  readonly name = ToolName.RESOLVE_LIGHTHOUSE_PATH;
  readonly description = 'Resolves a Lighthouse path to a backend node ID.';

  readonly parameters: Host.AidaClient.FunctionObjectParam<keyof ResolveLighthousePathArgs> = {
    type: Host.AidaClient.ParametersTypes.OBJECT,
    description: 'Arguments for resolving a Lighthouse path to a backend node ID.',
    nullable: false,
    properties: {
      explanation: {
        type: Host.AidaClient.ParametersTypes.STRING,
        description: 'Reason for requesting this resolution.',
        nullable: false,
      },
      path: {
        type: Host.AidaClient.ParametersTypes.STRING,
        description: 'Lighthouse path string.',
        nullable: false,
      },
    },
    required: ['explanation', 'path'],
  };

  displayInfoFromArgs(params: ResolveLighthousePathArgs): {
    title: string,
    thought: string,
    action: string,
  } {
    return {
      title: 'Resolving element path',
      thought: params.explanation,
      action: `resolveLighthousePath('${params.path}')`,
    };
  }

  /**
   * Handles the resolution request.
   *
   * It retrieves the node path using the target's DOMModel and verifies
   * that the node's origin matches the established origin lock to prevent
   * access to nodes from other origins.
   */
  async handler(
      params: ResolveLighthousePathArgs,
      context: BaseToolCapability&TargetCapability&OriginLockCapability,
      ): Promise<FunctionCallHandlerResult<{backendNodeId: number}>> {
    const establishedOrigin = context.getEstablishedOrigin();
    if (!establishedOrigin) {
      return {error: 'Error: Origin lock is not established.'};
    }

    const target = context.getTarget();
    const domModel = target?.model(SDK.DOMModel.DOMModel);
    if (!domModel) {
      return {error: 'Error: Inspected target not found.'};
    }

    let nodeId;
    try {
      // Resolves the Lighthouse path (a representation of the path to a node)
      // and ensures the node is loaded into the frontend DOM model, returning its ID.
      nodeId = await domModel.pushNodeByPathToFrontend(params.path);
    } catch {
      return {error: 'Error: Could not find node by path.'};
    }
    if (!nodeId) {
      return {error: 'Error: Could not find node by path.'};
    }

    const node = domModel.nodeForId(nodeId);
    if (!node) {
      return {error: 'Error: Could not retrieve resolved node.'};
    }

    const nodeContext = new DOMNodeContext(node);
    // Security check: Ensure the resolved node belongs to the same origin
    // that this AI assistance session is locked to, preventing cross-origin access.
    if (!nodeContext.isOriginAllowed(establishedOrigin)) {
      return {error: 'Error: Node does not belong to the locked origin.'};
    }

    return {
      result: {backendNodeId: node.backendNodeId()},
    };
  }
}
