/**
 * Spec Parser
 *
 * Parses living docs to extract documented API endpoints and function signatures.
 *
 * @module core/discrepancy/spec-parser
 */
import * as fs from 'fs';
import * as path from 'path';
import { consoleLogger } from '../../utils/logger.js';
/**
 * Parses living docs to extract documented behavior.
 */
export class SpecParser {
    constructor(options = {}) {
        this.logger = options.logger ?? consoleLogger;
        this.config = {
            docsPath: options.config?.docsPath ?? '.specweave/docs',
        };
    }
    /**
     * Parse all specs from the living docs directory.
     */
    async parseAll(docsPath) {
        const basePath = docsPath ?? this.config.docsPath;
        const specs = [];
        const errors = [];
        if (!fs.existsSync(basePath)) {
            errors.push(`Docs path not found: ${basePath}`);
            return { specs, errors };
        }
        // Scan for markdown files
        const files = this.findMarkdownFiles(basePath);
        for (const file of files) {
            try {
                const result = await this.parseFile(file);
                specs.push(...result.specs);
                errors.push(...result.errors);
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                errors.push(`Failed to parse ${file}: ${message}`);
            }
        }
        return { specs, errors };
    }
    /**
     * Parse a single markdown file for specs.
     */
    async parseFile(filePath) {
        const specs = [];
        const errors = [];
        if (!fs.existsSync(filePath)) {
            errors.push(`File not found: ${filePath}`);
            return { specs, errors };
        }
        const content = fs.readFileSync(filePath, 'utf-8');
        const lines = content.split('\n');
        for (let i = 0; i < lines.length; i++) {
            const line = lines[i];
            // Parse API route documentation
            const apiSpec = this.parseApiLine(line, i + 1, filePath);
            if (apiSpec) {
                // Look for description in following lines
                apiSpec.description = this.extractDescription(lines, i + 1);
                specs.push(apiSpec);
            }
            // Parse function documentation
            const funcSpec = this.parseFunctionLine(line, i + 1, filePath);
            if (funcSpec) {
                funcSpec.description = this.extractDescription(lines, i + 1);
                specs.push(funcSpec);
            }
            // Parse type documentation
            const typeSpec = this.parseTypeLine(line, i + 1, filePath);
            if (typeSpec) {
                typeSpec.description = this.extractDescription(lines, i + 1);
                specs.push(typeSpec);
            }
        }
        return { specs, errors };
    }
    /**
     * Parse API specs from a docs path.
     */
    async parseApiSpecs(docsPath) {
        const result = await this.parseAll(docsPath);
        return result.specs.filter(s => s.type === 'api');
    }
    /**
     * Parse function specs from a docs path.
     */
    async parseFunctionSpecs(docsPath) {
        const result = await this.parseAll(docsPath);
        return result.specs.filter(s => s.type === 'function');
    }
    parseApiLine(line, lineNumber, filePath) {
        // Pattern: ## GET /users or ### POST /api/users
        const apiPattern = /^#{2,4}\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(\S+)/i;
        const match = line.match(apiPattern);
        if (!match)
            return null;
        const method = match[1].toUpperCase();
        const routePath = match[2];
        const params = this.extractRouteParams(routePath);
        return {
            type: 'api',
            source: filePath,
            lineNumber,
            identifier: routePath,
            description: '',
            method,
            params,
        };
    }
    parseFunctionLine(line, lineNumber, filePath) {
        // Pattern: ### functionName(param1: type, param2: type): returnType
        // Or: ### `functionName(param1, param2)`
        const funcPattern = /^#{2,4}\s+`?(\w+)\s*\(([^)]*)\)\s*(?::\s*(\w+))?`?/;
        const match = line.match(funcPattern);
        if (!match)
            return null;
        const name = match[1];
        const paramsStr = match[2];
        const returnType = match[3];
        // Parse parameters
        const params = paramsStr
            .split(',')
            .map(p => p.trim())
            .filter(p => p.length > 0);
        return {
            type: 'function',
            source: filePath,
            lineNumber,
            identifier: name,
            description: '',
            params,
            returnType,
        };
    }
    parseTypeLine(line, lineNumber, filePath) {
        // Pattern: ### interface TypeName or ### type TypeName
        const typePattern = /^#{2,4}\s+(interface|type|class)\s+(\w+)/i;
        const match = line.match(typePattern);
        if (!match)
            return null;
        const kind = match[1].toLowerCase();
        const name = match[2];
        return {
            type: 'type',
            source: filePath,
            lineNumber,
            identifier: name,
            description: '',
            metadata: { kind },
        };
    }
    extractDescription(lines, startIndex) {
        const descriptionLines = [];
        for (let i = startIndex; i < lines.length; i++) {
            const line = lines[i];
            // Stop at next heading
            if (/^#{1,6}\s/.test(line))
                break;
            // Stop at empty line followed by heading
            if (line.trim() === '' && i + 1 < lines.length && /^#{1,6}\s/.test(lines[i + 1]))
                break;
            // Skip empty lines at the start
            if (descriptionLines.length === 0 && line.trim() === '')
                continue;
            // Add non-empty content
            if (line.trim()) {
                descriptionLines.push(line.trim());
            }
            // Stop after a few lines of description
            if (descriptionLines.length >= 3)
                break;
        }
        return descriptionLines.join(' ').slice(0, 500);
    }
    extractRouteParams(routePath) {
        // Extract :param or {param} from path
        const colonParams = routePath.match(/:([^/]+)/g) || [];
        const braceParams = routePath.match(/\{([^}]+)\}/g) || [];
        return [
            ...colonParams.map(p => p.slice(1)),
            ...braceParams.map(p => p.slice(1, -1)),
        ];
    }
    findMarkdownFiles(dir) {
        if (!fs.existsSync(dir))
            return [];
        const files = [];
        const skipDirs = new Set(['node_modules', '.git', 'dist', '_archive']);
        const entries = fs.readdirSync(dir, { withFileTypes: true });
        for (const entry of entries) {
            const fullPath = path.join(dir, entry.name);
            if (entry.isDirectory() && !skipDirs.has(entry.name)) {
                files.push(...this.findMarkdownFiles(fullPath));
            }
            else if (entry.isFile() && entry.name.endsWith('.md')) {
                files.push(fullPath);
            }
        }
        return files;
    }
}
//# sourceMappingURL=spec-parser.js.map