import { z } from 'zod';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { config } from '../../config.js';
import { detectJsPackageManager, runScriptCommandFor } from '../../lib/frameworks/packageManager.js';
import { runCommand } from '../../lib/runCommand.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { requireReact } from './requireReact.js';

function parseReactTestingLibraryOutput(output: string): Record<string, unknown> {
  // Extract failing test names and common RTL errors for a quick summary.
  const failingTests = output
    .split(/\n(?=\s*FAIL|●)/)
    .filter((chunk) => chunk.includes('●'))
    .map((chunk) => {
      const match = chunk.match(/●\s*(.+?)(?:\n|$)/);
      return match?.[1]?.trim() ?? null;
    })
    .filter((item): item is string => Boolean(item));

  const rtlErrors: string[] = [];
  if (/Unable to find an element/i.test(output)) rtlErrors.push('Unable to find an element (getBy/queryBy failed)');
  if (/Found multiple elements/i.test(output)) rtlErrors.push('Found multiple elements with the same query');
  if (/found accessibility element/i.test(output)) rtlErrors.push('Role/name based query mismatch');
  if (/Timed out in waitFor/i.test(output)) rtlErrors.push('waitFor timeout (async assertion not satisfied)');
  if (/Warning: An update to .* inside a test was not wrapped in act/i.test(output)) {
    rtlErrors.push('Missing act() wrapper around state update');
  }

  const totalMatch = output.match(/Tests\s+(\d+)\s+passed/i) || output.match(/Test Suites?:\s*(\d+)\s+passed/i);
  return {
    failingTestCount: failingTests.length,
    failingTests: failingTests.slice(0, 10),
    rtlErrors: rtlErrors.slice(0, 10),
    testsPassed: totalMatch ? Number(totalMatch[1]) : null,
  };
}

export function registerRunReactTestsTool(server: McpServer): void {
  server.registerTool(
    'run_react_tests',
    {
      title: 'Run React Tests',
      description:
        'Run the React test runner (Jest/Vitest + React Testing Library) via package.json "test" script and surface RTL-specific failures.',
      inputSchema: {
        extraArgs: z
          .array(z.string())
          .optional()
          .describe('Extra CLI args, e.g. ["--filter", "UserCard"].'),
      },
    },
    async ({ extraArgs }) => {
      try {
        const react = requireReact();
        const pm = detectJsPackageManager(react.root);
        const { command, args } = runScriptCommandFor(pm, 'test', extraArgs ?? []);
        const result = await runCommand(command, args, {
          cwd: react.root,
          timeoutMs: config.scaffoldCommandTimeoutMs,
        });
        return jsonResult({
          runner: 'npm-test-script',
          summary: parseReactTestingLibraryOutput(result.output),
          ...result,
        });
      } catch (error) {
        return errorResult(toErrorMessage(error));
      }
    },
  );
}
