// codragraph/src/core/ingestion/field-extractors/typescript.ts
import { SupportedLanguages } from '../../../_shared/index.js';
import { BaseFieldExtractor } from '../field-extractor.js';
/**
 * Hand-written TypeScript field extractor.
 *
 * This exists alongside the config-based extractor in configs/typescript-javascript.ts
 * (used for JavaScript) because TypeScript has unique requirements:
 * 1. type_alias_declaration with object type literals (e.g., type Config = { key: string })
 * 2. Optional property detection appending '| undefined' to types
 * 3. Nested type discovery within class/interface bodies
 *
 * The config-based extractor cannot express these TS-specific capabilities.
 * JavaScript uses the config-based version since it lacks type syntax.
 */
export class TypeScriptFieldExtractor extends BaseFieldExtractor {
    language = SupportedLanguages.TypeScript;
    /**
     * Node types that represent type declarations with fields in TypeScript
     */
    static TYPE_DECLARATION_NODES = new Set([
        'class_declaration',
        'interface_declaration',
        'abstract_class_declaration',
        'type_alias_declaration', // for object type literals
    ]);
    /**
     * Node types that contain field definitions within class bodies
     */
    static FIELD_NODE_TYPES = new Set([
        'public_field_definition', // class field: private users: User[]
        'property_signature', // interface property: name: string
        'field_definition', // fallback field type
    ]);
    /**
     * Visibility modifiers in TypeScript
     */
    static VISIBILITY_MODIFIERS = new Set([
        'public',
        'private',
        'protected',
    ]);
    /**
     * Check if this node represents a type declaration with fields
     */
    isTypeDeclaration(node) {
        return TypeScriptFieldExtractor.TYPE_DECLARATION_NODES.has(node.type);
    }
    /**
     * Extract visibility modifier from a field node
     */
    extractVisibility(node) {
        // Check for accessibility_modifier named child (tree-sitter typescript)
        for (let i = 0; i < node.namedChildCount; i++) {
            const child = node.namedChild(i);
            if (child && child.type === 'accessibility_modifier') {
                const text = child.text.trim();
                if (TypeScriptFieldExtractor.VISIBILITY_MODIFIERS.has(text)) {
                    return text;
                }
            }
        }
        // Check for modifiers in the field's unnamed children (fallback)
        for (let i = 0; i < node.childCount; i++) {
            const child = node.child(i);
            if (child && !child.isNamed) {
                const text = child.text.trim();
                if (TypeScriptFieldExtractor.VISIBILITY_MODIFIERS.has(text)) {
                    return text;
                }
            }
        }
        // Check for modifier node (tree-sitter typescript may group these)
        const modifiers = node.childForFieldName('modifiers');
        if (modifiers) {
            for (let i = 0; i < modifiers.childCount; i++) {
                const modifier = modifiers.child(i);
                const modText = modifier?.text.trim();
                if (modText && TypeScriptFieldExtractor.VISIBILITY_MODIFIERS.has(modText)) {
                    return modText;
                }
            }
        }
        // TypeScript class members are public by default
        return 'public';
    }
    /**
     * Check if a field has the static modifier
     */
    isStatic(node) {
        for (let i = 0; i < node.childCount; i++) {
            const child = node.child(i);
            if (child && !child.isNamed && child.text.trim() === 'static') {
                return true;
            }
        }
        const modifiers = node.childForFieldName('modifiers');
        if (modifiers) {
            for (let i = 0; i < modifiers.childCount; i++) {
                const modifier = modifiers.child(i);
                if (modifier && modifier.text === 'static') {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Check if a field has the readonly modifier
     */
    isReadonly(node) {
        for (let i = 0; i < node.childCount; i++) {
            const child = node.child(i);
            if (child && !child.isNamed && child.text.trim() === 'readonly') {
                return true;
            }
        }
        const modifiers = node.childForFieldName('modifiers');
        if (modifiers) {
            for (let i = 0; i < modifiers.childCount; i++) {
                const modifier = modifiers.child(i);
                if (modifier && modifier.text === 'readonly') {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Check if a property is optional (has ?: syntax)
     */
    isOptional(node) {
        // Look for the optional marker '?' in unnamed children
        for (let i = 0; i < node.childCount; i++) {
            const child = node.child(i);
            if (child && !child.isNamed && child.text === '?') {
                return true;
            }
        }
        // Also check for optional_property_signature or marker in type
        const kind = node.childForFieldName('kind');
        if (kind && kind.text === '?') {
            return true;
        }
        return false;
    }
    /**
     * Extract the full type text, handling complex generic types.
     *
     * type_annotation nodes wrap the literal ': SomeType' — only that branch
     * needs special handling to unwrap the inner child and skip the colon.
     * All other node kinds are already the type text itself, so normalizeType
     * is applied directly.
     */
    extractFullType(typeNode) {
        if (!typeNode)
            return null;
        if (typeNode.type === 'type_annotation') {
            const innerType = typeNode.firstNamedChild;
            return innerType ? this.normalizeType(innerType.text) : null;
        }
        return this.normalizeType(typeNode.text);
    }
    /**
     * Extract a single field from a field definition node
     */
    extractField(node, context) {
        // Get the field name
        const nameNode = node.childForFieldName('name') ?? node.childForFieldName('property');
        if (!nameNode)
            return null;
        const name = nameNode.text;
        if (!name)
            return null;
        // Get the type annotation
        const typeNode = node.childForFieldName('type');
        let type = this.extractFullType(typeNode);
        // Try to resolve the type using the context
        if (type) {
            const resolvedType = this.resolveType(type, context);
            type = resolvedType ?? type;
        }
        return {
            name,
            type,
            visibility: this.extractVisibility(node),
            isStatic: this.isStatic(node),
            isReadonly: this.isReadonly(node),
            sourceFile: context.filePath,
            line: node.startPosition.row + 1,
        };
    }
    /**
     * Extract fields from a class body or interface body
     */
    extractFieldsFromBody(bodyNode, context) {
        const fields = [];
        // Find all field definition nodes within the body
        for (let i = 0; i < bodyNode.namedChildCount; i++) {
            const child = bodyNode.namedChild(i);
            if (!child)
                continue;
            if (TypeScriptFieldExtractor.FIELD_NODE_TYPES.has(child.type)) {
                const field = this.extractField(child, context);
                if (field) {
                    fields.push(field);
                }
            }
        }
        return fields;
    }
    /**
     * Extract fields from an object type (used in type aliases)
     */
    extractFieldsFromObjectType(objectTypeNode, context) {
        const fields = [];
        // Find all property_signature nodes within the object type
        const propertySignatures = objectTypeNode.descendantsOfType('property_signature');
        for (const propNode of propertySignatures) {
            const field = this.extractField(propNode, context);
            if (field) {
                // Mark optional properties
                if (this.isOptional(propNode) && field.type) {
                    field.type = field.type + ' | undefined';
                }
                fields.push(field);
            }
        }
        return fields;
    }
    /**
     * Extract fields from a class or interface declaration
     */
    extract(node, context) {
        if (!this.isTypeDeclaration(node))
            return null;
        // Get the type name
        const nameNode = node.childForFieldName('name');
        if (!nameNode)
            return null;
        const typeName = nameNode.text;
        const ownerFqn = typeName;
        const fields = [];
        const nestedTypes = [];
        // Handle different declaration types
        if (node.type === 'class_declaration' || node.type === 'abstract_class_declaration') {
            // Find the class body
            const bodyNode = node.childForFieldName('body');
            if (bodyNode) {
                const extractedFields = this.extractFieldsFromBody(bodyNode, context);
                fields.push(...extractedFields);
            }
        }
        else if (node.type === 'interface_declaration') {
            // Find the interface body
            const bodyNode = node.childForFieldName('body');
            if (bodyNode) {
                const extractedFields = this.extractFieldsFromBody(bodyNode, context);
                fields.push(...extractedFields);
            }
        }
        else if (node.type === 'type_alias_declaration') {
            // Handle type aliases with object types
            const valueNode = node.childForFieldName('value');
            if (valueNode && valueNode.type === 'object_type') {
                const extractedFields = this.extractFieldsFromObjectType(valueNode, context);
                fields.push(...extractedFields);
            }
        }
        // Find nested type declarations
        const nestedClasses = node.descendantsOfType('class_declaration');
        const nestedInterfaces = node.descendantsOfType('interface_declaration');
        const nestedDeclarations = [...nestedClasses, ...nestedInterfaces];
        for (const nested of nestedDeclarations) {
            // Skip the current node itself
            if (nested === node)
                continue;
            const nestedName = nested.childForFieldName('name');
            if (nestedName) {
                nestedTypes.push(nestedName.text);
            }
        }
        return {
            ownerFqn,
            fields,
            nestedTypes,
        };
    }
}
// Export a singleton instance for registration
export const typescriptFieldExtractor = new TypeScriptFieldExtractor();
