import fs from 'node:fs';
import path from 'node:path';
import { z } from 'zod';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { requireReact } from './requireReact.js';

export function registerAnalyzeReactComponentTool(server: McpServer): void {
  server.registerTool(
    'analyze_react_component',
    {
      title: 'Analyze React Component',
      description:
        'Static scan of a React component file for common issues: missing `key` in lists, hook-rule violations, unused props, and Next.js App Router heuristics (hooks/events without "use client").',
      inputSchema: {
        file: z
          .string()
          .min(1)
          .describe('Path to the component file, relative to the React project root.'),
      },
    },
    async ({ file }) => {
      try {
        const react = requireReact();
        const absolutePath = path.resolve(react.root, file);
        if (!absolutePath.startsWith(react.root + path.sep)) {
          return errorResult('Refusing to analyze a file outside the React project root.');
        }
        if (!fs.existsSync(absolutePath)) {
          return errorResult(`File not found: ${file}`);
        }

        const source = fs.readFileSync(absolutePath, 'utf8');
        const issues: Array<{ type: string; line: number; message: string }> = [];
        const lines = source.split('\n');
        const hasUseClient = /^\s*['"]use client['"]\s*;?/m.test(source);
        const usesClientOnlyApis =
          /\bon[A-Z][A-Za-z]+\s*=/.test(source) ||
          /\buse(State|Effect|Reducer|Ref|LayoutEffect|Callback|Memo|Context)\s*\(/.test(source);

        if (!hasUseClient && usesClientOnlyApis) {
          issues.push({
            type: 'missing-use-client',
            line: 1,
            message:
              'File uses client-only APIs (hooks or event handlers) but is missing a "use client" directive. ' +
              'Required for Next.js App Router Client Components.',
          });
        }

        for (let i = 0; i < lines.length; i++) {
          const line = lines[i] ?? '';
          const lineNumber = i + 1;

          // Missing key in array maps (heuristic).
          if (/(\w+\s*\.\s*map|React\.Children\.map)\s*\(/.test(line) && !/key\s*=/.test(line)) {
            issues.push({
              type: 'missing-key',
              line: lineNumber,
              message: 'Array.map without a key prop on the returned element.',
            });
          }

          // Conditional or loop hook calls (simple heuristics).
          if (/\buse[A-Z][A-Za-z0-9]*\s*\(/.test(line)) {
            if (/\b(if|while|for|switch)\s*\(/.test(line)) {
              issues.push({
                type: 'rules-of-hooks',
                line: lineNumber,
                message: 'Hook call inside a conditional or loop block.',
              });
            }
          }

          // Functions / non-serializable values passed into imported components from a Server Component.
          if (!hasUseClient && /<(?:[A-Z][\w.]*)\b[^>]*\{[^}]*=>/.test(line)) {
            issues.push({
              type: 'server-to-client-props',
              line: lineNumber,
              message:
                'Looks like a function/arrow is passed as a prop from a Server Component. ' +
                'Move interactive children into a Client Component ("use client").',
            });
          }
        }

        // Unused props: find destructured props and see if they are referenced.
        const propDestructuring = source.match(/function\s+\w+\s*\(\s*\{\s*([^}]+)\s*\}\s*\)/);
        if (propDestructuring && propDestructuring[1]) {
          const rawProps = propDestructuring[1];
          const props = rawProps
            .split(',')
            .map((p) => p.trim().split(/\s*:\s*/)[0]?.replace(/\?|:.*$/, '').trim() ?? '')
            .filter(Boolean);
          const destructuringIndex = propDestructuring.index ?? 0;
          for (const prop of props) {
            const propRegex = new RegExp(`\\b${prop}\\b`, 'g');
            const matches = [...source.matchAll(propRegex)].length;
            // The declaration itself counts as one match, plus the original destructuring pattern may add another.
            if (matches <= 2) {
              issues.push({
                type: 'unused-prop',
                line: source.substring(0, destructuringIndex).split('\n').length,
                message: `Prop "${prop}" appears unused in the component body.`,
              });
            }
          }
        }

        return jsonResult({
          file,
          projectRoot: react.root,
          hasUseClient,
          issues,
          issueCount: issues.length,
          note: 'This is a heuristic static scan, not a full ESLint run. Use run_lint for comprehensive checks.',
        });
      } catch (error) {
        return errorResult(toErrorMessage(error));
      }
    },
  );
}
