// tools/api-spec-generator-handler.ts — generate_spec_from_api tool handler (SPEC-286)
import { readFile } from 'node:fs/promises';
import { parseOpenApi } from '../engine/api-spec-generator/openapi-parser.js';
import { parseGraphQL } from '../engine/api-spec-generator/graphql-parser.js';
import { buildACs, formatACsAsMarkdown } from '../engine/api-spec-generator/ac-builder.js';
import { handleCreateSpec } from './create-spec.js';
// ---------------------------------------------------------------------------
// Contract detection
// ---------------------------------------------------------------------------
function detectContractType(content) {
    const trimmed = content.trim();
    if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
        return 'openapi';
    }
    // GraphQL SDL typically has type/schema/query keywords
    if (/\btype\s+\w+\s*\{/.test(trimmed) || /\bschema\s*\{/.test(trimmed)) {
        return 'graphql';
    }
    // Fallback: attempt JSON parse
    try {
        JSON.parse(trimmed);
        return 'openapi';
    }
    catch {
        return 'graphql';
    }
}
function groupByEndpoint(contract) {
    return contract.operations.map((op) => ({
        title: op.title,
        operations: [op],
        tags: op.tags,
    }));
}
function groupByResource(contract) {
    const groups = new Map();
    for (const op of contract.operations) {
        const existing = groups.get(op.resourceGroup) ?? [];
        existing.push(op);
        groups.set(op.resourceGroup, existing);
    }
    return Array.from(groups.entries()).map(([resource, ops]) => ({
        title: `${resource} CRUD`,
        operations: ops,
        tags: [...new Set(ops.flatMap((o) => o.tags))],
    }));
}
function groupByOperation(contract) {
    // For GraphQL: by-operation is the same as by-endpoint (one spec per operation)
    // For REST in this mode: group by endpoint as well
    return groupByEndpoint(contract);
}
function groupOperations(contract, mode) {
    if (mode === 'by-resource') {
        return groupByResource(contract);
    }
    if (mode === 'by-operation') {
        return groupByOperation(contract);
    }
    return groupByEndpoint(contract);
}
// ---------------------------------------------------------------------------
// Spec description builder
// ---------------------------------------------------------------------------
function buildSpecDescription(group, contract, extraMeta) {
    const lines = [];
    lines.push(`# ${group.title}`, '');
    lines.push(`**Source**: ${contract.source.toUpperCase()} — ${contract.title} v${contract.version}`, '');
    if (contract.description.length > 0) {
        lines.push(contract.description, '');
    }
    // Detect frontend context from tags to filter irrelevant backend ACs
    const allTags = [...group.tags, ...(extraMeta.extraTags?.split(',') ?? [])];
    const isFrontend = allTags.some((t) => ['ui', 'storybook', 'design', 'component', 'frontend'].includes(t.toLowerCase()));
    const acOptions = isFrontend ? { target: 'frontend', tags: allTags } : undefined;
    for (const op of group.operations) {
        const opLabel = op.method !== undefined
            ? `${op.method} ${op.path ?? ''}`
            : `${op.operationType ?? 'operation'}: ${op.externalId}`;
        lines.push(`## Operation: ${opLabel}`, '');
        if (op.description.length > 0) {
            lines.push(op.description, '');
        }
        if (op.fields.length > 0) {
            lines.push('### Fields');
            for (const f of op.fields) {
                const req = f.required ? ' *(required)*' : '';
                const fmt = f.format !== undefined ? ` [format: ${f.format}]` : '';
                lines.push(`- \`${f.name}\`: ${f.type}${fmt}${req}`);
            }
            lines.push('');
        }
        const acs = buildACs(op, acOptions);
        if (acs.length > 0) {
            lines.push('### Acceptance Criteria');
            lines.push(formatACsAsMarkdown(acs));
            lines.push('');
        }
    }
    // Append extra metadata
    for (const [k, v] of Object.entries(extraMeta)) {
        lines.push(`**${k}**: ${v}`);
    }
    return lines.join('\n').trim();
}
// ---------------------------------------------------------------------------
// Main handler
// ---------------------------------------------------------------------------
export async function handleGenerateSpecFromApi(args) {
    // Load content
    let rawContent;
    if (args.content !== undefined && args.content.trim().length > 0) {
        rawContent = args.content;
    }
    else if (args.filePath !== undefined && args.filePath.trim().length > 0) {
        try {
            rawContent = await readFile(args.filePath, 'utf-8');
        }
        catch {
            return {
                content: [{ type: 'text', text: `Error: could not read file at ${args.filePath}` }],
                isError: true,
            };
        }
    }
    else {
        return {
            content: [{ type: 'text', text: 'Error: provide either content or filePath' }],
            isError: true,
        };
    }
    // Parse contract
    let contract;
    const detectedType = detectContractType(rawContent);
    try {
        contract = detectedType === 'openapi' ? parseOpenApi(rawContent) : parseGraphQL(rawContent);
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        return {
            content: [{ type: 'text', text: `Error parsing API contract: ${msg}` }],
            isError: true,
        };
    }
    if (contract.operations.length === 0) {
        return {
            content: [{ type: 'text', text: 'No operations found in the provided API contract' }],
            isError: true,
        };
    }
    const groups = groupOperations(contract, args.mode);
    const extraTags = args.extraTags ?? [];
    const baseTags = [contract.source, 'api', 'reverse-engineering'];
    const createdSpecIds = [];
    const errors = [];
    for (const group of groups) {
        const tags = [...new Set([...baseTags, ...group.tags, ...extraTags])];
        const description = buildSpecDescription(group, contract, {
            externalId: group.operations.map((o) => o.externalId).join(', '),
            source: contract.source,
        });
        const result = await handleCreateSpec({
            projectPath: args.projectPath,
            title: group.title,
            description,
            type: 'feature',
            scope: 'feature',
            target: 'backend',
            tags,
        });
        if (result.isError === true) {
            const errText = result.content[0]?.type === 'text' ? result.content[0].text : 'unknown error';
            errors.push(`Failed to create spec for "${group.title}": ${errText}`);
            continue;
        }
        // Extract the spec ID from the result text
        const text = result.content[0]?.type === 'text' ? result.content[0].text : '';
        const specIdMatch = /\*\*SPEC-(\d+)\*\*/.exec(text);
        if (specIdMatch !== null) {
            createdSpecIds.push(`SPEC-${specIdMatch[1] ?? '?'}`);
        }
    }
    const summary = [
        `Generated ${String(createdSpecIds.length)} spec(s) from ${String(contract.operations.length)} endpoints (${contract.source.toUpperCase()}: ${contract.title})`,
        '',
        `Mode: ${args.mode}`,
        `Specs created: ${createdSpecIds.length > 0 ? createdSpecIds.join(', ') : 'none'}`,
    ];
    if (errors.length > 0) {
        summary.push('', 'Errors:', ...errors.map((e) => `- ${e}`));
    }
    return {
        content: [{ type: 'text', text: summary.join('\n') }],
        isError: false,
    };
}
//# sourceMappingURL=api-spec-generator-handler.js.map