/**
 * TypeScript Code Analyzer
 *
 * Extracts function signatures and type definitions from TypeScript files
 * using the TypeScript Compiler API.
 *
 * @module core/discrepancy/analyzers/typescript-analyzer
 */
import * as ts from 'typescript';
import * as fs from 'fs';
import * as path from 'path';
import { consoleLogger } from '../../../utils/logger.js';
/**
 * Analyzes TypeScript files to extract function signatures and type definitions.
 */
export class TypeScriptAnalyzer {
    constructor(options = {}) {
        this.logger = options.logger ?? consoleLogger;
        this.config = {
            include: options.config?.include ?? ['**/*.ts'],
            exclude: options.config?.exclude ?? ['**/*.test.ts', '**/*.spec.ts', 'node_modules/**', 'dist/**'],
            basePath: options.config?.basePath ?? process.cwd(),
        };
    }
    /**
     * Analyze a list of TypeScript files.
     */
    async analyze(files) {
        const functions = [];
        const types = [];
        const errors = [];
        for (const filePath of files) {
            try {
                const result = await this.analyzeFile(filePath);
                functions.push(...result.functions);
                types.push(...result.types);
                errors.push(...result.errors);
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                errors.push(`Failed to analyze ${filePath}: ${message}`);
                this.logger.warn(`Failed to analyze ${filePath}: ${message}`);
            }
        }
        return { functions, types, errors };
    }
    /**
     * Analyze a single TypeScript file.
     */
    async analyzeFile(filePath) {
        const functions = [];
        const types = [];
        const errors = [];
        const absolutePath = path.isAbsolute(filePath)
            ? filePath
            : path.resolve(this.config.basePath, filePath);
        if (!fs.existsSync(absolutePath)) {
            errors.push(`File not found: ${absolutePath}`);
            return { functions, types, errors };
        }
        const content = fs.readFileSync(absolutePath, 'utf-8');
        const sourceFile = ts.createSourceFile(absolutePath, content, ts.ScriptTarget.Latest, true);
        const visit = (node) => {
            // Check for function declarations
            if (ts.isFunctionDeclaration(node) && node.name) {
                const sig = this.extractFunctionSignature(node, sourceFile, absolutePath);
                if (sig)
                    functions.push(sig);
            }
            // Check for arrow functions assigned to const/let
            if (ts.isVariableStatement(node)) {
                const exported = this.hasExportModifier(node);
                for (const decl of node.declarationList.declarations) {
                    if (ts.isIdentifier(decl.name) && decl.initializer) {
                        if (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer)) {
                            const sig = this.extractArrowFunctionSignature(decl.name.text, decl.initializer, sourceFile, absolutePath, exported);
                            if (sig)
                                functions.push(sig);
                        }
                    }
                }
            }
            // Check for method declarations in classes
            if (ts.isMethodDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
                const parent = node.parent;
                if (ts.isClassDeclaration(parent)) {
                    const sig = this.extractMethodSignature(node, sourceFile, absolutePath, parent);
                    if (sig)
                        functions.push(sig);
                }
            }
            // Check for interface declarations
            if (ts.isInterfaceDeclaration(node)) {
                const typeDef = this.extractInterfaceDefinition(node, sourceFile, absolutePath);
                if (typeDef)
                    types.push(typeDef);
            }
            // Check for type alias declarations
            if (ts.isTypeAliasDeclaration(node)) {
                const typeDef = this.extractTypeAliasDefinition(node, sourceFile, absolutePath);
                if (typeDef)
                    types.push(typeDef);
            }
            // Check for class declarations
            if (ts.isClassDeclaration(node) && node.name) {
                const typeDef = this.extractClassDefinition(node, sourceFile, absolutePath);
                if (typeDef)
                    types.push(typeDef);
            }
            ts.forEachChild(node, visit);
        };
        visit(sourceFile);
        return { functions, types, errors };
    }
    extractFunctionSignature(node, sourceFile, filePath) {
        if (!node.name)
            return null;
        const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
        const exported = this.hasExportModifier(node);
        const jsdoc = this.extractJSDoc(node, sourceFile);
        return {
            name: node.name.text,
            filePath,
            lineNumber: line + 1,
            params: this.extractParameters(node.parameters, sourceFile),
            returnType: this.extractReturnType(node, sourceFile),
            exported,
            async: this.hasAsyncModifier(node),
            jsdoc,
        };
    }
    extractArrowFunctionSignature(name, node, sourceFile, filePath, exported) {
        const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
        return {
            name,
            filePath,
            lineNumber: line + 1,
            params: this.extractParameters(node.parameters, sourceFile),
            returnType: this.extractReturnType(node, sourceFile),
            exported,
            async: this.hasAsyncModifier(node),
        };
    }
    extractMethodSignature(node, sourceFile, filePath, classNode) {
        if (!node.name || !ts.isIdentifier(node.name))
            return null;
        const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
        const className = classNode.name?.text ?? 'AnonymousClass';
        const exported = this.hasExportModifier(classNode);
        const jsdoc = this.extractJSDoc(node, sourceFile);
        return {
            name: `${className}.${node.name.text}`,
            filePath,
            lineNumber: line + 1,
            params: this.extractParameters(node.parameters, sourceFile),
            returnType: this.extractReturnType(node, sourceFile),
            exported,
            async: this.hasAsyncModifier(node),
            jsdoc,
        };
    }
    extractInterfaceDefinition(node, sourceFile, filePath) {
        const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
        return {
            name: node.name.text,
            filePath,
            lineNumber: line + 1,
            kind: 'interface',
            exported: this.hasExportModifier(node),
            properties: this.extractInterfaceProperties(node, sourceFile),
        };
    }
    extractTypeAliasDefinition(node, sourceFile, filePath) {
        const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
        return {
            name: node.name.text,
            filePath,
            lineNumber: line + 1,
            kind: 'type',
            exported: this.hasExportModifier(node),
        };
    }
    extractClassDefinition(node, sourceFile, filePath) {
        if (!node.name)
            return null;
        const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
        return {
            name: node.name.text,
            filePath,
            lineNumber: line + 1,
            kind: 'class',
            exported: this.hasExportModifier(node),
        };
    }
    extractParameters(params, sourceFile) {
        return params.map((param) => {
            const name = ts.isIdentifier(param.name) ? param.name.text : param.name.getText(sourceFile);
            const type = param.type ? param.type.getText(sourceFile) : 'unknown';
            const optional = !!param.questionToken || !!param.initializer;
            return { name, type, optional };
        });
    }
    extractReturnType(node, sourceFile) {
        if (node.type) {
            return node.type.getText(sourceFile);
        }
        return 'unknown';
    }
    extractInterfaceProperties(node, sourceFile) {
        const properties = [];
        for (const member of node.members) {
            if (ts.isPropertySignature(member) && member.name) {
                const name = ts.isIdentifier(member.name)
                    ? member.name.text
                    : member.name.getText(sourceFile);
                const type = member.type ? member.type.getText(sourceFile) : 'unknown';
                const optional = !!member.questionToken;
                properties.push({ name, type, optional });
            }
        }
        return properties;
    }
    extractJSDoc(node, sourceFile) {
        const jsdocNodes = ts.getJSDocCommentsAndTags(node);
        if (jsdocNodes.length === 0)
            return undefined;
        const comments = [];
        for (const jsdoc of jsdocNodes) {
            if (ts.isJSDoc(jsdoc) && jsdoc.comment) {
                const comment = typeof jsdoc.comment === 'string'
                    ? jsdoc.comment
                    : jsdoc.comment.map(c => c.getText(sourceFile)).join('');
                comments.push(comment);
            }
        }
        return comments.length > 0 ? comments.join('\n') : undefined;
    }
    hasExportModifier(node) {
        const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
        return modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
    }
    hasAsyncModifier(node) {
        const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
        return modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;
    }
}
//# sourceMappingURL=typescript-analyzer.js.map