// Generator Service — streams MCP server code generation via Claude

import Anthropic from '@anthropic-ai/sdk';
import { getSkill } from '../skills/loader';
import type {
  PipelineEvent,
  AnalysisResult,
  ToolDefinition,
  ServerBundle,
  GeneratedFile,
} from '../types';

const MODEL = 'claude-sonnet-4-5-20250514';
const MAX_TOKENS = 16384;

/**
 * Generate a complete MCP server from analysis results and tool configuration.
 * Streams file-by-file as they are generated.
 */
export async function* generateServer(
  analysis: AnalysisResult,
  toolConfig: ToolDefinition[]
): AsyncGenerator<PipelineEvent> {
  const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
  const systemPrompt = getSkill('builder');

  yield { type: 'generate:progress', file: 'Initializing generation', percent: 0 };

  const toolSummary = toolConfig.map((t) => ({
    name: t.name,
    description: t.description,
    endpoint: t.endpoint,
    method: t.method,
    inputSchema: t.inputSchema,
    annotations: t.annotations,
  }));

  try {
    const stream = client.messages.stream({
      model: MODEL,
      max_tokens: MAX_TOKENS,
      system: systemPrompt,
      messages: [
        {
          role: 'user',
          content: `Generate a complete MCP server for the "${analysis.service}" API.

## API Details
- Base URL: ${analysis.baseUrl}
- Auth: ${JSON.stringify(analysis.authFlow)}
- Rate Limits: ${JSON.stringify(analysis.rateLimits)}

## Tools to Implement (${toolConfig.length} total)
\`\`\`json
${JSON.stringify(toolSummary, null, 2)}
\`\`\`

## Requirements
1. Generate each file separately, wrapped in a code block with the file path as the language tag
2. Use this format for each file:
   \`\`\`typescript // path: src/index.ts
   ...code...
   \`\`\`
3. Include: package.json, tsconfig.json, src/index.ts (entry), src/tools/*.ts, src/auth.ts, src/client.ts
4. Follow MCP SDK best practices from your training
5. Add proper error handling, input validation, and rate limit respect
6. TypeScript strict mode throughout

Generate all files now.`,
        },
      ],
    });

    let fullText = '';
    const generatedFiles: GeneratedFile[] = [];
    let lastFileCount = 0;

    for await (const event of stream) {
      if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
        fullText += event.delta.text;

        // Check for newly completed file blocks
        const files = extractFiles(fullText);
        if (files.length > lastFileCount) {
          for (let i = lastFileCount; i < files.length; i++) {
            const file = files[i];
            generatedFiles.push(file);
            yield {
              type: 'generate:file_ready',
              path: file.path,
              content: file.content,
            };
            yield {
              type: 'generate:progress',
              file: file.path,
              percent: Math.min(90, Math.floor((files.length / estimateFileCount(toolConfig.length)) * 90)),
            };
          }
          lastFileCount = files.length;
        }
      }
    }

    // Final extraction pass (catch any files missed during streaming)
    const finalFiles = extractFiles(fullText);
    for (let i = lastFileCount; i < finalFiles.length; i++) {
      const file = finalFiles[i];
      generatedFiles.push(file);
      yield {
        type: 'generate:file_ready',
        path: file.path,
        content: file.content,
      };
    }

    // Get token usage
    const finalMessage = await stream.finalMessage();

    yield { type: 'generate:progress', file: 'Finalizing bundle', percent: 95 };

    // Build the server bundle
    const packageJson = generatedFiles.find((f) => f.path.endsWith('package.json'));
    const tsConfig = generatedFiles.find((f) => f.path.endsWith('tsconfig.json'));

    const bundle: ServerBundle = {
      files: generatedFiles,
      packageJson: packageJson ? JSON.parse(packageJson.content) : buildDefaultPackageJson(analysis.service),
      tsConfig: tsConfig ? JSON.parse(tsConfig.content) : buildDefaultTsConfig(),
      entryPoint: 'src/index.ts',
      toolCount: toolConfig.length,
    };

    yield { type: 'generate:complete', bundle };
  } catch (error) {
    const msg = error instanceof Error ? error.message : String(error);
    yield {
      type: 'error',
      message: `Generation failed: ${msg}`,
      recoverable: error instanceof Anthropic.RateLimitError,
    };
  }
}

/** Extract file blocks from streamed text. */
function extractFiles(text: string): GeneratedFile[] {
  const files: GeneratedFile[] = [];
  // Match: ```typescript // path: some/path.ts  OR  ```json // path: package.json
  const regex = /```(\w+)\s*\/\/\s*path:\s*(\S+)\s*\n([\s\S]*?)```/g;
  let match;
  while ((match = regex.exec(text)) !== null) {
    const lang = match[1] as GeneratedFile['language'];
    const path = match[2].trim();
    const content = match[3].trim();
    files.push({
      path,
      content,
      language: lang === 'json' ? 'json' : lang === 'markdown' ? 'markdown' : 'typescript',
    });
  }
  return files;
}

function estimateFileCount(toolCount: number): number {
  // base files (index, auth, client, package.json, tsconfig) + one file per tool
  return 5 + toolCount;
}

function buildDefaultPackageJson(service: string): object {
  const slug = service.toLowerCase().replace(/[^a-z0-9]+/g, '-');
  return {
    name: `@mcpengine/${slug}-mcp`,
    version: '1.0.0',
    type: 'module',
    main: 'dist/index.js',
    scripts: {
      build: 'tsc',
      start: 'node dist/index.js',
      dev: 'tsx src/index.ts',
    },
    dependencies: {
      '@modelcontextprotocol/sdk': '^1.12.1',
    },
    devDependencies: {
      typescript: '^5.8.0',
      tsx: '^4.19.0',
    },
  };
}

function buildDefaultTsConfig(): object {
  return {
    compilerOptions: {
      target: 'ES2022',
      module: 'Node16',
      moduleResolution: 'Node16',
      outDir: './dist',
      rootDir: './src',
      strict: true,
      esModuleInterop: true,
      declaration: true,
    },
    include: ['src/**/*'],
  };
}
