import { Node } from 'web-tree-sitter';
import { BaseIndexer } from './base-indexer.js';
import { ExportMetadata, SymbolType } from '@scandiumsys/owstra-types';

export class JSIndexer extends BaseIndexer {
  public override readonly language: string = 'javascript';

  public extractExports(root: Node, filePath: string): ExportMetadata[] {
    const exports: ExportMetadata[] = [];

    // Helper to add export
    const addExport = (name: string, type: SymbolType, node: Node, isDefault = false) => {
      exports.push({
        name,
        type,
        filePath,
        line: node.startPosition.row + 1,
        column: node.startPosition.column + 1,
        isDefault,
        parameters: this.extractParameters(node)
      });
    };

    // 1. ESM Exports
    const esmExportNodes = root.descendantsOfType('export_statement');
    for (const node of esmExportNodes) {
        // export const x = ...
        const decl = node.childForFieldName('declaration');
        if (decl) {
            if (decl.type === 'lexical_declaration' || decl.type === 'variable_declaration') {
                const declarators = decl.descendantsOfType('variable_declarator');
                for (const d of declarators) {
                    const name = d.childForFieldName('name')?.text;
                    if (name) addExport(name, 'variable', d);
                }
            } else if (decl.type === 'function_declaration') {
                const name = decl.childForFieldName('name')?.text;
                if (name) addExport(name, 'function', decl);
            }
        }
        
        // export { x, y }
        const exportClause = node.childForFieldName('export_clause');
        if (exportClause) {
            const specifiers = exportClause.descendantsOfType('export_specifier');
            for (const s of specifiers) {
                const name = s.childForFieldName('alias')?.text || s.childForFieldName('name')?.text;
                if (name) addExport(name, 'variable', s);
            }
        }

        // export default ...
        if (node.text.includes('export default')) {
            addExport('default', 'variable', node, true);
        }
    }

    // 2. CommonJS Exports (module.exports = ... or exports.x = ...)
    // Look for assignments
    const assignments = root.descendantsOfType('assignment_expression');
    for (const node of assignments) {
        const left = node.childForFieldName('left');
        const right = node.childForFieldName('right');
        if (!left || !right) continue;

        const leftText = left.text;
        if (leftText === 'module.exports') {
            addExport('default', 'variable', right, true);
        } else if (leftText.startsWith('exports.')) {
            const name = leftText.split('.')[1];
            if (name) addExport(name, 'variable', right);
        }
    }

    return exports;
  }

  public extractImports(root: Node): Record<string, string> {
    const imports: Record<string, string> = {};

    // 1. ESM Imports
    const importNodes = root.descendantsOfType('import_statement');
    for (const node of importNodes) {
        const source = node.childForFieldName('source')?.text.replace(/['"]/g, '');
        if (!source) continue;

        // import { x as y } from 'source'
        const clauses = node.descendantsOfType('import_specifier');
        for (const c of clauses) {
            const local = c.childForFieldName('alias')?.text || c.childForFieldName('name')?.text;
            if (local) imports[local] = source;
        }

        // import x from 'source'
        const defaultImport = node.children.find(c => c.type === 'identifier');
        if (defaultImport) {
            imports[defaultImport.text] = source;
        }

        // import * as x from 'source'
        const namespaceImport = node.descendantsOfType('namespace_import')[0];
        if (namespaceImport) {
            const identifier = namespaceImport.children.find(c => c.type === 'identifier');
            if (identifier) imports[identifier.text] = source;
        }
    }

    // 2. CommonJS require
    const callNodes = root.descendantsOfType('call_expression');
    for (const node of callNodes) {
        const functionNode = node.childForFieldName('function');
        if (functionNode?.text === 'require') {
            const arg = node.childForFieldName('arguments')?.child(1); // child(0) is '(', child(1) is first arg
            if (arg && (arg.type === 'string' || arg.type === 'string_fragment')) {
                const source = arg.text.replace(/['"]/g, '');
                
                // Track where this require result is assigned
                let parent = node.parent;
                while (parent && parent.type !== 'variable_declarator' && parent.type !== 'assignment_expression') {
                    parent = parent.parent;
                }

                if (parent?.type === 'variable_declarator') {
                    const name = parent.childForFieldName('name')?.text;
                    if (name) imports[name] = source;
                }
            }
        }
    }

    return imports;
  }

  public extractCalls(root: Node): Array<{ name: string, argPositions: Array<{ row: number; column: number }> }> {
    const calls: Array<{ name: string, argPositions: Array<{ row: number; column: number }> }> = [];
    const callNodes = root.descendantsOfType('call_expression');

    for (const node of callNodes) {
        const func = node.childForFieldName('function');
        if (func && func.type === 'identifier') {
            const argsNode = node.childForFieldName('arguments');
            const argPositions: Array<{ row: number; column: number }> = [];
            if (argsNode) {
                // Skip '(' and ')'
                for (let i = 1; i < argsNode.childCount - 1; i++) {
                    const arg = argsNode.child(i);
                    if (arg && arg.isNamed) {
                        argPositions.push({
                            row: arg.startPosition.row,
                            column: arg.startPosition.column
                        });
                    }
                }
            }
            calls.push({ name: func.text, argPositions });
        }
    }

    return calls;
  }

  protected extractParameters(node: Node): string[] {
    const params: string[] = [];
    let paramNode = node.childForFieldName('parameters');
    
    if (!paramNode && node.type === 'variable_declarator') {
        const value = node.childForFieldName('value');
        if (value && (value.type === 'function_expression' || value.type === 'arrow_function')) {
            paramNode = value.childForFieldName('parameters');
        }
    }

    if (paramNode) {
        const identifiers = paramNode.descendantsOfType('identifier');
        for (const id of identifiers) {
            params.push(id.text);
        }
    }
    return params;
  }
}
