import { extractModifierVisibility, extractSimpleParameters, findChild, findFirstChildOfTypes, findParentNode, nodeEndLine, nodeStartLine, pushCall, pushImport, stripQuotes, } from './helpers.js';
/**
 * Extract symbols from Solidity files.
 *
 * Solidity's tree-sitter grammar covers contracts, interfaces, libraries,
 * structs, enums, events, errors, functions, modifiers, and import paths.
 */
export function extractSoliditySymbols(tree, _filePath) {
    const ctx = {
        definitions: [],
        calls: [],
        imports: [],
        classes: [],
        exports: [],
        typeMap: new Map(),
    };
    walkSolidityNode(tree.rootNode, ctx);
    return ctx;
}
function walkSolidityNode(node, ctx) {
    switch (node.type) {
        case 'contract_declaration':
            handleContractDecl(node, ctx, 'class');
            break;
        case 'interface_declaration':
            handleContractDecl(node, ctx, 'interface');
            break;
        case 'library_declaration':
            handleContractDecl(node, ctx, 'module');
            break;
        case 'struct_declaration':
            handleStructDecl(node, ctx);
            break;
        case 'enum_declaration':
            handleEnumDecl(node, ctx);
            break;
        case 'function_definition':
            handleFunctionDef(node, ctx);
            break;
        case 'modifier_definition':
            handleModifierDef(node, ctx);
            break;
        case 'event_definition':
            handleEventDef(node, ctx);
            break;
        case 'error_declaration':
            handleErrorDecl(node, ctx);
            break;
        case 'state_variable_declaration':
            handleStateVarDecl(node, ctx);
            break;
        case 'import_directive':
            handleImportDirective(node, ctx);
            break;
        case 'call_expression':
        case 'function_call':
            handleCallExpression(node, ctx);
            break;
    }
    for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (child)
            walkSolidityNode(child, ctx);
    }
}
// ── Handlers ───────────────────────────────────────────────────────────────
const SOL_PARENT_TYPES = [
    'contract_declaration',
    'interface_declaration',
    'library_declaration',
];
function handleContractDecl(node, ctx, kind) {
    const nameNode = node.childForFieldName('name');
    if (!nameNode)
        return;
    const name = nameNode.text;
    const body = node.childForFieldName('body') || findChild(node, 'contract_body');
    const members = body ? extractContractMembers(body) : [];
    ctx.definitions.push({
        name,
        kind,
        line: nodeStartLine(node),
        endLine: nodeEndLine(node),
        children: members.length > 0 ? members : undefined,
    });
    extractInheritance(node, name, ctx);
}
/** Extract member declarations from a contract body node. */
function extractContractMembers(body) {
    const members = [];
    for (let i = 0; i < body.childCount; i++) {
        const child = body.child(i);
        if (!child)
            continue;
        const member = extractContractMember(child);
        if (member)
            members.push(member);
    }
    return members;
}
/** Map a single contract body child to a SubDeclaration, or null if not a recognized member. */
function extractContractMember(child) {
    const line = nodeStartLine(child);
    switch (child.type) {
        case 'function_definition': {
            const fnName = child.childForFieldName('name');
            return fnName ? { name: fnName.text, kind: 'method', line } : null;
        }
        case 'state_variable_declaration': {
            const varName = child.childForFieldName('name');
            return varName
                ? { name: varName.text, kind: 'property', line, visibility: extractSolVisibility(child) }
                : null;
        }
        case 'event_definition': {
            const evName = child.childForFieldName('name');
            return evName ? { name: evName.text, kind: 'property', decorators: ['event'], line } : null;
        }
        case 'error_declaration': {
            const errName = child.childForFieldName('name');
            return errName ? { name: errName.text, kind: 'property', decorators: ['error'], line } : null;
        }
        case 'modifier_definition': {
            const modName = child.childForFieldName('name');
            return modName
                ? { name: modName.text, kind: 'method', decorators: ['modifier'], line }
                : null;
        }
        default:
            return null;
    }
}
/**
 * Extract inheritance (extends) relationships from a contract node.
 *
 * Each parent in `contract A is B, C, D { }` is its own `inheritance_specifier`
 * sibling under the contract node (see tree-sitter-solidity grammar:
 * `_class_heritage: "is" commaSep1($.inheritance_specifier)`), so we must walk
 * all direct children rather than stopping at the first match.
 */
function extractInheritance(node, name, ctx) {
    for (let i = 0; i < node.childCount; i++) {
        const inheritance = node.child(i);
        if (inheritance?.type !== 'inheritance_specifier')
            continue;
        for (let j = 0; j < inheritance.childCount; j++) {
            const child = inheritance.child(j);
            if (!child)
                continue;
            if (child.type === 'user_defined_type' || child.type === 'identifier') {
                ctx.classes.push({ name, extends: child.text, line: nodeStartLine(node) });
            }
        }
    }
}
function handleStructDecl(node, ctx) {
    const nameNode = node.childForFieldName('name');
    if (!nameNode)
        return;
    const members = [];
    for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (child && child.type === 'struct_member') {
            const memberName = child.childForFieldName('name');
            if (memberName) {
                members.push({
                    name: memberName.text,
                    kind: 'property',
                    line: nodeStartLine(child),
                });
            }
        }
    }
    ctx.definitions.push({
        name: qualifyWithParent(node, nameNode.text),
        kind: 'struct',
        line: nodeStartLine(node),
        endLine: nodeEndLine(node),
        children: members.length > 0 ? members : undefined,
    });
}
function handleEnumDecl(node, ctx) {
    const nameNode = node.childForFieldName('name');
    if (!nameNode)
        return;
    const members = [];
    for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (child && child.type === 'enum_value') {
            members.push({ name: child.text, kind: 'constant', line: nodeStartLine(child) });
        }
    }
    ctx.definitions.push({
        name: qualifyWithParent(node, nameNode.text),
        kind: 'enum',
        line: nodeStartLine(node),
        endLine: nodeEndLine(node),
        children: members.length > 0 ? members : undefined,
    });
}
function handleFunctionDef(node, ctx) {
    const nameNode = node.childForFieldName('name');
    if (!nameNode)
        return;
    const parent = findParentNode(node, SOL_PARENT_TYPES);
    const fullName = parent ? `${parent}.${nameNode.text}` : nameNode.text;
    const kind = parent ? 'method' : 'function';
    const params = extractSolParams(node);
    ctx.definitions.push({
        name: fullName,
        kind,
        line: nodeStartLine(node),
        endLine: nodeEndLine(node),
        children: params.length > 0 ? params : undefined,
        visibility: extractSolVisibility(node),
    });
}
function handleModifierDef(node, ctx) {
    const nameNode = node.childForFieldName('name');
    if (!nameNode)
        return;
    ctx.definitions.push({
        name: qualifyWithParent(node, nameNode.text),
        kind: 'function',
        line: nodeStartLine(node),
        endLine: nodeEndLine(node),
        decorators: ['modifier'],
    });
}
function handleEventDef(node, ctx) {
    const nameNode = node.childForFieldName('name');
    if (!nameNode)
        return;
    ctx.definitions.push({
        name: qualifyWithParent(node, nameNode.text),
        kind: 'type',
        line: nodeStartLine(node),
        endLine: nodeEndLine(node),
        decorators: ['event'],
    });
}
function handleErrorDecl(node, ctx) {
    const nameNode = node.childForFieldName('name');
    if (!nameNode)
        return;
    ctx.definitions.push({
        name: qualifyWithParent(node, nameNode.text),
        kind: 'type',
        line: nodeStartLine(node),
        endLine: nodeEndLine(node),
        decorators: ['error'],
    });
}
function handleStateVarDecl(node, ctx) {
    const nameNode = node.childForFieldName('name');
    if (!nameNode)
        return;
    ctx.definitions.push({
        name: qualifyWithParent(node, nameNode.text),
        kind: 'variable',
        line: nodeStartLine(node),
        endLine: nodeEndLine(node),
        visibility: extractSolVisibility(node),
    });
}
/** Qualify `name` with the nearest contract/interface/library, if any. */
function qualifyWithParent(node, name) {
    const parent = findParentNode(node, SOL_PARENT_TYPES);
    return parent ? `${parent}.${name}` : name;
}
function handleImportDirective(node, ctx) {
    // import "path"; or import { X } from "path"; or import "path" as Alias;
    for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (!child)
            continue;
        if (child.type === 'string' || child.type === 'string_literal') {
            const source = stripQuotes(child.text);
            const names = [];
            // Look for imported symbols
            for (let j = 0; j < node.childCount; j++) {
                const sibling = node.child(j);
                if (sibling && sibling.type === 'identifier')
                    names.push(sibling.text);
                if (sibling && sibling.type === 'import_declaration') {
                    const id = findChild(sibling, 'identifier');
                    if (id)
                        names.push(id.text);
                }
            }
            // Preserve the explicit `['*']` fallback — pushImport's default uses the
            // source basename, but Solidity's convention here is to mark unqualified
            // imports as `*`.
            pushImport(ctx, node, source, names.length > 0 ? names : ['*']);
            return;
        }
        // source_import: handles `import * as X from "path"`
        if (child.type === 'source_import' || child.type === 'import_clause') {
            const strNode = findFirstChildOfTypes(child, ['string', 'string_literal']);
            if (strNode) {
                pushImport(ctx, node, stripQuotes(strNode.text), ['*']);
                return;
            }
        }
    }
}
function handleCallExpression(node, ctx) {
    const funcNode = node.childForFieldName('function') || node.childForFieldName('callee');
    if (!funcNode)
        return;
    let name = '';
    let receiver;
    if (funcNode.type === 'member_expression' || funcNode.type === 'member_access') {
        const prop = funcNode.childForFieldName('property') || funcNode.childForFieldName('member');
        const obj = funcNode.childForFieldName('object') || funcNode.childForFieldName('expression');
        if (prop)
            name = prop.text;
        if (obj)
            receiver = obj.text;
    }
    else {
        name = funcNode.text;
    }
    if (name)
        pushCall(ctx, node, name, receiver !== undefined ? { receiver } : {});
}
// ── Helpers ────────────────────────────────────────────────────────────────
function extractSolParams(funcNode) {
    const paramList = funcNode.childForFieldName('parameters') || findChild(funcNode, 'parameter_list');
    return extractSimpleParameters(paramList, { paramTypes: ['parameter'] });
}
function extractSolVisibility(node) {
    // Solidity visibility is embedded as child keywords or visibility nodes
    const vis = extractModifierVisibility(node);
    if (vis)
        return vis;
    for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (!child)
            continue;
        const t = child.text;
        if (t === 'public' || t === 'external')
            return 'public';
        if (t === 'private')
            return 'private';
        if (t === 'internal')
            return 'protected';
    }
    return undefined;
}
//# sourceMappingURL=solidity.js.map