// codragraph/src/core/ingestion/method-extractors/configs/csharp.ts
// Verified against tree-sitter-c-sharp 0.23.1
import { SupportedLanguages } from '../../../../_shared/index.js';
import { findVisibility, hasModifier, hasKeyword, collectModifierTexts, } from '../../field-extractors/configs/helpers.js';
import { extractSimpleTypeName } from '../../type-extractors/shared.js';
// ---------------------------------------------------------------------------
// C# helpers
// ---------------------------------------------------------------------------
const CSHARP_VIS = new Set(['public', 'private', 'protected', 'internal']);
/**
 * Walk the parameter_list of a method or constructor and return typed ParameterInfo
 * entries.
 *
 * In tree-sitter-c-sharp (verified against ^0.23.1), the `params` variadic keyword
 * is NOT wrapped inside a `parameter` node.  It appears as a bare unnamed `params`
 * token at the `parameter_list` level, followed by a type node and an identifier
 * node that are also direct children of `parameter_list` (not of a `parameter` node).
 *
 * All other parameters are normal `parameter` named children of `parameter_list`:
 *   - name comes from field 'name'
 *   - type comes from field 'type'
 *   - ref / out: modifier children inside the parameter node prefix the type string
 *   - isOptional: an '=' token appears among the children (indicates a default value)
 */
function extractCSharpParameters(node) {
    const paramList = node.childForFieldName('parameters');
    if (!paramList)
        return [];
    return extractParametersFromList(paramList);
}
/** Extract parameters from a parameter_list node directly. */
function extractParametersFromList(paramList) {
    const params = [];
    let i = 0;
    while (i < paramList.childCount) {
        const child = paramList.child(i);
        if (!child) {
            i++;
            continue;
        }
        // `params` variadic: bare unnamed `params` keyword followed by type + identifier
        // siblings at the parameter_list level (not wrapped in a `parameter` node).
        if (!child.isNamed && child.type === 'params') {
            let typeNode = null;
            let nameText;
            let j = i + 1;
            while (j < paramList.childCount) {
                const sibling = paramList.child(j);
                if (!sibling) {
                    j++;
                    continue;
                }
                if (sibling.isNamed && sibling.type !== 'parameter') {
                    if (!typeNode) {
                        typeNode = sibling;
                    }
                    else if (sibling.type === 'identifier') {
                        nameText = sibling.text;
                        i = j;
                        break;
                    }
                }
                j++;
            }
            if (nameText) {
                params.push({
                    name: nameText,
                    type: typeNode
                        ? (extractSimpleTypeName(typeNode) ?? typeNode.text?.trim() ?? null)
                        : null,
                    rawType: typeNode?.text?.trim() ?? null,
                    isOptional: false,
                    isVariadic: true,
                });
            }
            i++;
            continue;
        }
        // Regular named `parameter` node
        if (child.isNamed && child.type === 'parameter') {
            const nameNode = child.childForFieldName('name');
            if (nameNode && nameNode.text.trim()) {
                const typeNode = child.childForFieldName('type');
                let typeName = typeNode
                    ? (extractSimpleTypeName(typeNode) ?? typeNode.text?.trim() ?? null)
                    : null;
                // Detect ref / out modifiers inside the parameter node — prefix the type string
                for (let j = 0; j < child.namedChildCount; j++) {
                    const c = child.namedChild(j);
                    if (!c || c.type !== 'modifier')
                        continue;
                    const modText = c.text.trim();
                    if (modText === 'out' || modText === 'ref' || modText === 'in' || modText === 'this') {
                        typeName = typeName ? `${modText} ${typeName}` : modText;
                        break;
                    }
                }
                // Detect optional (default value) — an '=' token among direct children
                let isOptional = false;
                for (let j = 0; j < child.childCount; j++) {
                    const c = child.child(j);
                    if (c && c.text.trim() === '=') {
                        isOptional = true;
                        break;
                    }
                }
                params.push({
                    name: nameNode.text,
                    type: typeName,
                    rawType: typeNode?.text?.trim() ?? null,
                    isOptional,
                    isVariadic: false,
                });
            }
        }
        i++;
    }
    return params;
}
/**
 * Collect C# attributes from attribute_list nodes on a method or constructor.
 * Skips attribute lists with a target specifier (e.g. [return: NotNull],
 * [param: Required]) — those target a different declaration element.
 * Names are prefixed with '@' to mirror the JVM convention.
 */
function extractCSharpAnnotations(node) {
    const annotations = [];
    for (let i = 0; i < node.namedChildCount; i++) {
        const child = node.namedChild(i);
        if (!child || child.type !== 'attribute_list')
            continue;
        // Skip targeted attribute lists (e.g. [return: ...], [method: ...])
        let hasTarget = false;
        for (let j = 0; j < child.namedChildCount; j++) {
            if (child.namedChild(j)?.type === 'attribute_target_specifier') {
                hasTarget = true;
                break;
            }
        }
        if (hasTarget)
            continue;
        for (let j = 0; j < child.namedChildCount; j++) {
            const attr = child.namedChild(j);
            if (!attr || attr.type !== 'attribute')
                continue;
            const nameNode = attr.childForFieldName('name');
            if (nameNode)
                annotations.push('@' + nameNode.text);
        }
    }
    return annotations;
}
// ---------------------------------------------------------------------------
// C# config
// ---------------------------------------------------------------------------
export const csharpMethodConfig = {
    language: SupportedLanguages.CSharp,
    typeDeclarationNodes: [
        'class_declaration',
        'struct_declaration',
        'interface_declaration',
        'record_declaration',
    ],
    methodNodeTypes: [
        'method_declaration',
        'constructor_declaration',
        'destructor_declaration',
        'operator_declaration',
        'conversion_operator_declaration',
        'local_function_statement',
    ],
    bodyNodeTypes: ['declaration_list'],
    extractName(node) {
        // destructor_declaration: prefix with ~ to distinguish from constructor
        if (node.type === 'destructor_declaration') {
            const name = node.childForFieldName('name')?.text;
            return name ? `~${name}` : undefined;
        }
        // operator_declaration: no 'name' field — use 'operator' field (e.g., +, ==)
        if (node.type === 'operator_declaration') {
            const op = node.childForFieldName('operator');
            return op ? `operator ${op.text.trim()}` : undefined;
        }
        // conversion_operator_declaration: no 'name' field — implicit/explicit + target type
        if (node.type === 'conversion_operator_declaration') {
            const typeNode = node.childForFieldName('type');
            const typeName = typeNode
                ? (extractSimpleTypeName(typeNode) ?? typeNode.text?.trim())
                : undefined;
            for (let i = 0; i < node.childCount; i++) {
                const c = node.child(i);
                if (c && !c.isNamed && (c.text === 'implicit' || c.text === 'explicit')) {
                    return typeName ? `${c.text} operator ${typeName}` : undefined;
                }
            }
            return typeName ? `operator ${typeName}` : undefined;
        }
        return node.childForFieldName('name')?.text;
    },
    extractReturnType(node) {
        // Constructors and destructors have no return type
        // operator_declaration and conversion_operator_declaration use 'type' field, not 'returns'
        const returnsNode = node.childForFieldName('returns');
        if (returnsNode)
            return returnsNode.text?.trim();
        // Fallback for operator/conversion declarations that use 'type' as return type field
        if (node.type === 'operator_declaration' || node.type === 'conversion_operator_declaration') {
            const typeNode = node.childForFieldName('type');
            if (typeNode)
                return extractSimpleTypeName(typeNode) ?? typeNode.text?.trim();
        }
        return undefined;
    },
    extractParameters: extractCSharpParameters,
    extractVisibility(node) {
        // Detect compound C# visibilities: protected internal, private protected
        const mods = collectModifierTexts(node);
        if (mods.has('protected') && mods.has('internal'))
            return 'protected internal';
        if (mods.has('private') && mods.has('protected'))
            return 'private protected';
        return findVisibility(node, CSHARP_VIS, 'private', 'modifier');
    },
    isStatic(node) {
        return hasKeyword(node, 'static') || hasModifier(node, 'modifier', 'static');
    },
    isAbstract(node, ownerNode) {
        if (hasKeyword(node, 'abstract') || hasModifier(node, 'modifier', 'abstract'))
            return true;
        // Interface methods are implicitly abstract when they have no body
        if (ownerNode.type === 'interface_declaration') {
            const body = node.childForFieldName('body');
            return !body;
        }
        return false;
    },
    isFinal(node) {
        // C# uses 'sealed' instead of 'final'
        return hasKeyword(node, 'sealed') || hasModifier(node, 'modifier', 'sealed');
    },
    extractAnnotations: extractCSharpAnnotations,
    isVirtual(node) {
        return hasKeyword(node, 'virtual') || hasModifier(node, 'modifier', 'virtual');
    },
    isOverride(node) {
        return hasKeyword(node, 'override') || hasModifier(node, 'modifier', 'override');
    },
    isAsync(node) {
        return hasKeyword(node, 'async') || hasModifier(node, 'modifier', 'async');
    },
    isPartial(node) {
        return hasKeyword(node, 'partial') || hasModifier(node, 'modifier', 'partial');
    },
    extractPrimaryConstructor(ownerNode, context) {
        // C# 12 primary constructors: class Point(int x, int y) { }
        // The parameter_list is a direct named child of class_declaration/record_declaration
        // but has NO field name — it must be found by iterating named children.
        let paramList = null;
        for (let i = 0; i < ownerNode.namedChildCount; i++) {
            const child = ownerNode.namedChild(i);
            if (child?.type === 'parameter_list') {
                paramList = child;
                break;
            }
        }
        if (!paramList)
            return null;
        const name = ownerNode.childForFieldName('name')?.text;
        if (!name)
            return null;
        const parameters = extractParametersFromList(paramList);
        return {
            name,
            receiverType: null,
            returnType: null,
            parameters,
            // Reuse the config's extractVisibility on the owner declaration node
            visibility: csharpMethodConfig.extractVisibility(ownerNode),
            isStatic: false,
            isAbstract: false,
            isFinal: false,
            annotations: [], // C# has no syntax for attributes on primary constructors
            sourceFile: context.filePath,
            line: paramList.startPosition.row + 1,
        };
    },
};
