/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import {
  BaseDeclarativeTool,
  BaseToolInvocation,
  Kind,
  type ToolInvocation,
  type ToolResult,
  type ToolCallConfirmationDetails,
  type ExecuteOptions,
} from './tools.js';
import { GET_INTERNAL_DOCS_TOOL_NAME } from './tool-names.js';
import type { MessageBus } from '../confirmation-bus/message-bus.js';
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { glob } from 'glob';
import { ToolErrorType } from './tool-error.js';
import { GET_INTERNAL_DOCS_DEFINITION } from './definitions/coreTools.js';
import { resolveToolDeclaration } from './definitions/resolver.js';

/**
 * Parameters for the GetInternalDocs tool.
 */
export interface GetInternalDocsParams {
  /**
   * The relative path to a specific documentation file (e.g., 'cli/commands.md').
   * If omitted, the tool will return a list of all available documentation paths.
   */
  path?: string;
}

/**
 * Helper to find the absolute path to the documentation directory.
 */
async function getDocsRoot(): Promise<string> {
  const currentFile = fileURLToPath(import.meta.url);
  let searchDir = path.dirname(currentFile);

  const isDocsDir = async (dir: string) => {
    try {
      const stats = await fs.stat(dir);
      if (stats.isDirectory()) {
        const marker = path.join(dir, 'sidebar.json');
        await fs.access(marker);
        return true;
      }
    } catch {
      // Not a valid docs directory
    }
    return false;
  };

  while (true) {
    const candidate = path.join(searchDir, 'docs');
    if (await isDocsDir(candidate)) {
      return candidate;
    }

    const parent = path.dirname(searchDir);
    if (parent === searchDir) {
      break;
    }
    searchDir = parent;
  }

  throw new Error('Could not find Gemini CLI documentation directory.');
}

class GetInternalDocsInvocation extends BaseToolInvocation<
  GetInternalDocsParams,
  ToolResult
> {
  constructor(
    params: GetInternalDocsParams,
    messageBus: MessageBus,
    _toolName?: string,
    _toolDisplayName?: string,
  ) {
    super(params, messageBus, _toolName, _toolDisplayName);
  }

  override async shouldConfirmExecute(
    _abortSignal: AbortSignal,
  ): Promise<ToolCallConfirmationDetails | false> {
    return false;
  }

  getDescription(): string {
    if (this.params.path) {
      return `Reading internal documentation: ${this.params.path}`;
    }
    return 'Listing all available internal documentation.';
  }

  async execute({ abortSignal: _signal }: ExecuteOptions): Promise<ToolResult> {
    try {
      const docsRoot = await getDocsRoot();

      if (!this.params.path) {
        // List all .md and .mdx files recursively
        const files = await glob('**/*.{md,mdx}', {
          cwd: docsRoot,
          posix: true,
        });

        files.sort();

        const fileList = files.map((f) => `- ${f}`).join('\n');
        const resultContent = `Available Gemini CLI documentation files:\n\n${fileList}`;

        return {
          llmContent: resultContent,
          returnDisplay: `Found ${files.length} documentation files.`,
        };
      }

      // Read a specific file
      // Security: Prevent path traversal by resolving and verifying it stays within docsRoot
      const resolvedPath = path.resolve(docsRoot, this.params.path);
      if (!resolvedPath.startsWith(docsRoot)) {
        throw new Error(
          'Access denied: Requested path is outside the documentation directory.',
        );
      }

      const content = await fs.readFile(resolvedPath, 'utf8');

      return {
        llmContent: content,
        returnDisplay: `Successfully read documentation: ${this.params.path}`,
      };
    } catch (error) {
      const errorMessage =
        error instanceof Error ? error.message : String(error);
      return {
        llmContent: `Error accessing internal documentation: ${errorMessage}`,
        returnDisplay: `Failed to access documentation: ${errorMessage}`,
        error: {
          message: errorMessage,
          type: ToolErrorType.EXECUTION_FAILED,
        },
      };
    }
  }
}

/**
 * A tool that provides access to Gemini CLI's internal documentation.
 * If no path is provided, it returns a list of all available documentation files.
 * If a path is provided, it returns the content of that specific file.
 */
export class GetInternalDocsTool extends BaseDeclarativeTool<
  GetInternalDocsParams,
  ToolResult
> {
  static readonly Name = GET_INTERNAL_DOCS_TOOL_NAME;

  constructor(messageBus: MessageBus) {
    super(
      GetInternalDocsTool.Name,
      'GetInternalDocs',
      GET_INTERNAL_DOCS_DEFINITION.base.description!,
      Kind.Think,
      GET_INTERNAL_DOCS_DEFINITION.base.parametersJsonSchema,
      messageBus,
      /* isOutputMarkdown */ true,
      /* canUpdateOutput */ false,
    );
  }

  protected createInvocation(
    params: GetInternalDocsParams,
    messageBus: MessageBus,
    _toolName?: string,
    _toolDisplayName?: string,
  ): ToolInvocation<GetInternalDocsParams, ToolResult> {
    return new GetInternalDocsInvocation(
      params,
      messageBus,
      _toolName ?? GetInternalDocsTool.Name,
      _toolDisplayName,
    );
  }

  override getSchema(modelId?: string) {
    return resolveToolDeclaration(GET_INTERNAL_DOCS_DEFINITION, modelId);
  }
}
