// codragraph/src/core/ingestion/field-extractors/generic.ts
import { BaseFieldExtractor } from '../field-extractor.js';
// ---------------------------------------------------------------------------
// Factory
// ---------------------------------------------------------------------------
/**
 * Create a FieldExtractor from a declarative config.
 */
export function createFieldExtractor(config) {
    const typeDeclarationSet = new Set(config.typeDeclarationNodes);
    const fieldNodeSet = new Set(config.fieldNodeTypes);
    const bodyNodeSet = new Set(config.bodyNodeTypes);
    class GenericFieldExtractor extends BaseFieldExtractor {
        language = config.language;
        isTypeDeclaration(node) {
            return typeDeclarationSet.has(node.type);
        }
        extractVisibility(node) {
            return config.extractVisibility(node);
        }
        extract(node, context) {
            if (!this.isTypeDeclaration(node))
                return null;
            const nameNode = node.childForFieldName('name');
            if (!nameNode)
                return null;
            const ownerFqn = nameNode.text;
            const fields = [];
            // Find body container(s)
            const bodies = this.findBodies(node);
            for (const body of bodies) {
                this.extractFieldsFromBody(body, context, fields);
            }
            // Extract fields from primary constructor parameters (e.g. C# records)
            if (config.extractPrimaryFields) {
                const primaryFields = config.extractPrimaryFields(node, context);
                for (const f of primaryFields)
                    fields.push(f);
            }
            return { ownerFqn, fields, nestedTypes: [] };
        }
        // ------------------------------------------------------------------
        // private helpers
        // ------------------------------------------------------------------
        findBodies(node) {
            const result = [];
            // Try named 'body' field first
            const bodyField = node.childForFieldName('body');
            if (bodyField && bodyNodeSet.has(bodyField.type)) {
                result.push(bodyField);
                return result;
            }
            // Walk immediate children for matching body node types
            for (let i = 0; i < node.namedChildCount; i++) {
                const child = node.namedChild(i);
                if (child && bodyNodeSet.has(child.type)) {
                    result.push(child);
                }
            }
            // Fallback: use the body field even if its type is not in bodyNodeSet
            if (result.length === 0 && bodyField) {
                result.push(bodyField);
            }
            return result;
        }
        extractFieldsFromBody(body, context, out) {
            for (let i = 0; i < body.namedChildCount; i++) {
                const child = body.namedChild(i);
                if (!child)
                    continue;
                if (fieldNodeSet.has(child.type)) {
                    if (config.extractNames) {
                        // Multi-name path: one node may declare several fields (e.g. Ruby attr_accessor)
                        const names = config.extractNames(child);
                        for (const name of names) {
                            const field = this.buildField(child, name, context);
                            if (field)
                                out.push(field);
                        }
                    }
                    else {
                        const field = this.extractSingleField(child, context);
                        if (field)
                            out.push(field);
                    }
                }
            }
        }
        extractSingleField(node, context) {
            const name = config.extractName(node);
            if (!name)
                return null;
            return this.buildField(node, name, context);
        }
        buildField(node, name, context) {
            if (!name)
                return null;
            let type = config.extractType(node) ?? null;
            if (type) {
                type = this.normalizeType(type);
                const resolved = this.resolveType(type, context);
                if (resolved)
                    type = resolved;
            }
            return {
                name,
                type,
                visibility: config.extractVisibility(node),
                isStatic: config.isStatic(node),
                isReadonly: config.isReadonly(node),
                sourceFile: context.filePath,
                line: node.startPosition.row + 1,
            };
        }
    }
    return new GenericFieldExtractor();
}
