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

import fs from 'node:fs';
import path from 'node:path';

import {Client} from '@modelcontextprotocol/sdk/client/index.js';
import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js';

import {parseArguments} from '../build/src/bin/chrome-devtools-mcp-cli-options.js';
import {buildFlag} from '../build/src/index.js';
import {
  labels,
  ToolCategory,
  OFF_BY_DEFAULT_CATEGORIES,
} from '../build/src/tools/categories.js';
import {createTools} from '../build/src/tools/tools.js';

const OUTPUT_PATH = path.join(
  import.meta.dirname,
  '../src/bin/chrome-devtools-cli-options.ts',
);

async function fetchTools() {
  console.log('Connecting to chrome-devtools-mcp to fetch tools...');
  // Use the local build of the server
  const serverPath = path.join(
    import.meta.dirname,
    '../build/src/bin/chrome-devtools-mcp.js',
  );

  const transport = new StdioClientTransport({
    command: 'node',
    args: [serverPath, '--viaCli'],
    env: {...process.env, CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: 'true'},
  });

  const client = new Client(
    {
      name: 'chrome-devtools-cli-generator',
      version: '0.1.0',
    },
    {
      capabilities: {},
    },
  );

  await client.connect(transport);
  try {
    const toolsResponse = await client.listTools();
    if (!toolsResponse.tools?.length) {
      throw new Error(`No tools were fetched`);
    }
    const tools = toolsResponse.tools || [];
    console.log(`Fetched ${tools.length} tools`);
    return tools;
  } finally {
    await client.close();
  }
}

interface CliOption {
  name: string;
  type: string;
  description: string;
  required: boolean;
  default?: unknown;
  enum?: unknown[];
}

interface JsonSchema {
  type?: string | string[];
  description?: string;
  properties?: Record<string, JsonSchema>;
  required?: string[];
  default?: unknown;
  enum?: unknown[];
}

function schemaToCLIOptions(schema: JsonSchema): CliOption[] {
  if (!schema || !schema.properties) {
    return [];
  }
  const required = schema.required || [];
  const properties = schema.properties;
  return Object.entries(properties).map(([name, prop]) => {
    const isRequired = required.includes(name);
    const description = prop.description || '';
    if (typeof prop.type !== 'string') {
      throw new Error(
        `Property ${name} has a complex type not supported by CLI.`,
      );
    }
    return {
      name,
      type: prop.type,
      description,
      required: isRequired,
      default: prop.default,
      enum: prop.enum,
    };
  });
}

async function generateCli() {
  const tools = await fetchTools();

  const staticTools = createTools(parseArguments());
  const toolNameToCategoryEnum = new Map<string, string>();
  const toolNameToConditions = new Map<string, string[]>();

  for (const tool of staticTools) {
    toolNameToCategoryEnum.set(tool.name, tool.annotations.category);
    toolNameToConditions.set(tool.name, tool.annotations.conditions || []);
  }

  // Sort tools by name
  const sortedTools = tools
    .sort((a, b) => a.name.localeCompare(b.name))
    .filter(tool => {
      // Skipping fill_form because it is not relevant in shell scripts
      // and CLI does not handle array/JSON args well.
      if (tool.name === 'fill_form') {
        return false;
      }
      // Skipping wait_for because CLI does not handle array/JSON args well
      // and shell scripts have many mechanisms for waiting.
      if (tool.name === 'wait_for') {
        return false;
      }
      // Skipping get_tab_id as it is for internal integrations
      if (tool.name === 'get_tab_id') {
        return false;
      }
      // Skipping in_page tools as they are not launched yet
      if (toolNameToCategoryEnum.get(tool.name) === ToolCategory.IN_PAGE) {
        return false;
      }
      return true;
    });

  const commands: Record<
    string,
    {description: string; category: string; args: Record<string, CliOption>}
  > = {};

  for (const tool of sortedTools) {
    const options = schemaToCLIOptions(tool.inputSchema);
    const args: Record<string, CliOption> = {};
    for (const opt of options) {
      args[opt.name] = opt;
    }

    const categoryEnum = toolNameToCategoryEnum.get(tool.name);
    if (!categoryEnum) {
      throw new Error(`Tool ${tool.name} has no category.`);
    }
    const category = labels[categoryEnum as unknown as keyof typeof labels];
    if (!tool.description) {
      throw new Error(`Tool ${tool.name} is missing description`);
    }

    let description = tool.description;
    const requiredFlags: string[] = [];

    const isOffByDefault = OFF_BY_DEFAULT_CATEGORIES.includes(categoryEnum);
    if (isOffByDefault) {
      const categoryFlag = buildFlag(categoryEnum);
      requiredFlags.push(`--${categoryFlag}=true`);
    }

    const conditions = toolNameToConditions.get(tool.name) || [];
    for (const condition of conditions) {
      requiredFlags.push(`--${condition}=true`);
    }

    if (requiredFlags.length > 0) {
      description += ` (requires flag: ${requiredFlags.join(', ')})`;
    }

    commands[tool.name] = {
      description,
      category,
      args,
    };
  }

  const lines: string[] = [];
  lines.push(`/**
 * @license
 * Copyright ${new Date().getFullYear()} Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

// NOTE: do not edit manually. Auto-generated by 'npm run cli:generate'.

export interface ArgDef {
  name: string;
  type: string;
  description: string;
  required: boolean;
  default?: string | number | boolean;
  enum?: ReadonlyArray<string | number>;
}
export type Commands = Record<
  string,
  {
    description: string;
    category: string;
    args: Record<string, ArgDef>
  }
>;
export const commands: Commands = ${JSON.stringify(commands, null, 2)} as const;
`);

  fs.mkdirSync(path.dirname(OUTPUT_PATH), {recursive: true});
  fs.writeFileSync(OUTPUT_PATH, lines.join(''));
  console.log(`Generated CLI at ${OUTPUT_PATH}`);
}

generateCli().catch(err => {
  console.error('Error during generation:', err);
  process.exit(1);
});
