/**
 * Maximum recursion depth for tree-sitter AST walkers.
 * Shared across all language extractors to prevent stack overflow on deeply nested ASTs.
 */
export const MAX_WALK_DEPTH = 200;
/** Convert a tree-sitter node's start row to a 1-based source line. */
export function nodeStartLine(node) {
    return node.startPosition.row + 1;
}
export function nodeEndLine(node) {
    return node.endPosition.row + 1;
}
export function findChild(node, type) {
    for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (child && child.type === type)
            return child;
    }
    return null;
}
/**
 * Find the first child whose type is in `types`. Useful when several grammar
 * variants name the same conceptual node differently (e.g. `string` vs
 * `string_literal`). Returns the first match in document order, or null.
 */
export function findFirstChildOfTypes(node, types) {
    for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (child && types.includes(child.type))
            return child;
    }
    return null;
}
/**
 * Iterate the direct children of `node` in document order, skipping nulls and
 * tokens whose type appears in `skipTypes`. Mirrors the common
 * `for (let i = 0; i < node.childCount; i++) { const c = node.child(i); if (...) continue; ... }`
 * idiom while letting callers filter out grammar punctuation (`,`, `(`, `{`, etc.).
 */
export function* iterChildren(node, skipTypes = EMPTY_SKIP_SET) {
    for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (!child)
            continue;
        if (skipTypes.has(child.type))
            continue;
        yield child;
    }
}
const EMPTY_SKIP_SET = new Set();
/** Common punctuation tokens — handy as a `skipTypes` set for `iterChildren`. */
export const PUNCTUATION_TOKENS = new Set([
    ',',
    ';',
    '(',
    ')',
    '[',
    ']',
    '{',
    '}',
    ':',
    '.',
]);
/**
 * Merge a type-map entry, keeping the higher-confidence one.
 * Shared across all language extractors that build type maps for call resolution.
 */
export function setTypeMapEntry(typeMap, name, type, confidence) {
    const existing = typeMap.get(name);
    if (!existing || confidence > existing.confidence) {
        typeMap.set(name, { type, confidence });
    }
}
/**
 * Extract visibility from a node by scanning its children for modifier keywords.
 * Works for Java, C#, PHP, and similar languages where modifiers are child nodes.
 * @param {object} node - tree-sitter node
 * @param {Set<string>} [modifierTypes] - node types that indicate modifiers
 * @returns {'public'|'private'|'protected'|undefined}
 */
const DEFAULT_MODIFIER_TYPES = new Set([
    'modifiers',
    'modifier',
    'visibility_modifier',
    'accessibility_modifier',
]);
const VISIBILITY_KEYWORDS = new Set(['public', 'private', 'protected']);
/**
 * Python convention: __name → private, _name → protected, else undefined.
 */
export function pythonVisibility(name) {
    if (name.startsWith('__') && name.endsWith('__'))
        return undefined; // dunder — public
    if (name.startsWith('__'))
        return 'private';
    if (name.startsWith('_'))
        return 'protected';
    return undefined;
}
/**
 * Go convention: uppercase first letter → public, lowercase → private.
 */
export function goVisibility(name) {
    if (!name)
        return undefined;
    // Strip receiver prefix (e.g., "Receiver.Method" → check "Method")
    const bare = name.includes('.') ? (name.split('.').pop() ?? name) : name;
    if (!bare)
        return undefined;
    const first = bare[0];
    if (!first)
        return undefined;
    return first === first.toUpperCase() && first !== first.toLowerCase() ? 'public' : 'private';
}
/**
 * Rust: check for `visibility_modifier` child (pub, pub(crate), etc.).
 */
export function rustVisibility(node) {
    for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (!child)
            continue;
        if (child.type === 'visibility_modifier') {
            return 'public'; // pub, pub(crate), pub(super) all mean "visible"
        }
    }
    return 'private';
}
// ── Parser abstraction helpers ─────────────────────────────────────────────
/**
 * Walk up the parent chain to find an enclosing node whose type is in `typeNames`.
 * Returns the text of `nameField` (default `'name'`) on the matching ancestor, or null.
 *
 * Replaces per-language `findParentClass` / `findParentType` / `findCurrentImpl` helpers.
 */
export function findParentNode(node, typeNames, nameField = 'name') {
    let current = node.parent;
    while (current) {
        if (typeNames.includes(current.type)) {
            const nameNode = current.childForFieldName(nameField);
            return nameNode ? nameNode.text : null;
        }
        current = current.parent;
    }
    return null;
}
/**
 * Resolve a container's body node by trying each field name in order.
 */
function resolveBodyNode(containerNode, bodyFields) {
    for (const field of bodyFields) {
        const body = containerNode.childForFieldName(field) || findChild(containerNode, field);
        if (body)
            return body;
    }
    return null;
}
/**
 * Extract child declarations from a container node's body.
 * Finds the body via `bodyFields` (tries childForFieldName then findChild for each),
 * iterates its children, filters by `memberType`, extracts `nameField`, and returns SubDeclarations.
 *
 * Replaces per-language extractStructFields / extractEnumVariants / extractEnumConstants helpers
 * for the common case where each member has a direct name field.
 */
export function extractBodyMembers(containerNode, bodyFields, memberType, kind, nameField = 'name', visibility) {
    const body = resolveBodyNode(containerNode, bodyFields);
    if (!body)
        return [];
    const members = [];
    for (let i = 0; i < body.childCount; i++) {
        const member = body.child(i);
        if (!member || member.type !== memberType)
            continue;
        const nn = member.childForFieldName(nameField);
        if (!nn)
            continue;
        const entry = { name: nn.text, kind, line: member.startPosition.row + 1 };
        if (visibility)
            entry.visibility = visibility(member);
        members.push(entry);
    }
    return members;
}
/**
 * Strip leading/trailing quotes (single, double, or backtick) from a string.
 * Strips only the leading/trailing delimiter; interior quotes are untouched.
 */
export function stripQuotes(text) {
    return text.replace(/^['"`]|['"`]$/g, '');
}
/**
 * Extract the last segment of a delimited path.
 * e.g. `lastPathSegment('java.util.List', '.')` → `'List'`
 */
export function lastPathSegment(path, separator = '/') {
    return path.split(separator).pop() ?? path;
}
/**
 * Parse visibility from a modifier node's text content.
 */
function parseModifierText(text) {
    if (VISIBILITY_KEYWORDS.has(text))
        return text;
    // C# 'private protected' — accessible to derived types in same assembly → protected
    if (text === 'private protected')
        return 'protected';
    // Compound modifiers node (Java: "public static") — scan its text for a keyword
    for (const kw of VISIBILITY_KEYWORDS) {
        if (text.includes(kw))
            return kw;
    }
    return undefined;
}
export function extractModifierVisibility(node, modifierTypes = DEFAULT_MODIFIER_TYPES) {
    for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (!child || !modifierTypes.has(child.type))
            continue;
        const result = parseModifierText(child.text);
        if (result)
            return result;
    }
    return undefined;
}
// ── Output-push helpers ────────────────────────────────────────────────────
//
// Most extractors finish with `ctx.calls.push({ name, line: node.startPosition.row + 1 })`
// or `ctx.imports.push({ source, names, line: node.startPosition.row + 1 })`.
// Centralising the construction keeps `line` derivation consistent and removes
// the ~108 hand-rolled `startPosition.row + 1` literals scattered across
// language extractors.
/**
 * Append a `Call` to the extractor output. `line` defaults to the start line of
 * `node`; pass `extra` for `receiver` / `dynamic` flags.
 */
export function pushCall(ctx, node, name, extra = {}) {
    if (!name)
        return;
    const call = { name, line: nodeStartLine(node) };
    if (extra.receiver !== undefined)
        call.receiver = extra.receiver;
    if (extra.dynamic !== undefined)
        call.dynamic = extra.dynamic;
    ctx.calls.push(call);
}
/**
 * Append an `Import` to the extractor output. `line` defaults to the start
 * line of `node`. If `names` is empty, the source basename (split on `/`) is
 * used as a single-name fallback — matching the convention in gleam, julia,
 * and similar module-path imports.
 */
export function pushImport(ctx, node, source, names, flags = {}) {
    if (!source)
        return;
    const resolved = names.length > 0 ? names : [lastPathSegment(source, '/') || source];
    const entry = { source, names: resolved, line: nodeStartLine(node) };
    Object.assign(entry, flags);
    ctx.imports.push(entry);
}
/**
 * Extract parameters from a parameter-list node using a uniform pattern.
 *
 * This collapses the boilerplate in `extract*Params` helpers across
 * Java/Julia/Gleam/Solidity/R/etc. — each one walks the parameter list,
 * matches a parameter node type, reads the `name` field, and pushes a
 * `SubDeclaration` with `kind: 'parameter'`.
 */
export function extractSimpleParameters(paramListNode, options) {
    const params = [];
    if (!paramListNode)
        return params;
    const { paramTypes, nameField = 'name', fallbackToIdentifier = false } = options;
    for (let i = 0; i < paramListNode.childCount; i++) {
        const param = paramListNode.child(i);
        if (!param || !paramTypes.includes(param.type))
            continue;
        const nameNode = resolveParamName(param, nameField, fallbackToIdentifier);
        if (!nameNode)
            continue;
        params.push({ name: nameNode.text, kind: 'parameter', line: nodeStartLine(param) });
        recordParamType(param, nameNode.text, options);
    }
    return params;
}
/** Record a parameter's declared type into the type-map sink, if configured. */
function recordParamType(param, paramName, options) {
    const { typeMap, resolveType, typeMapConfidence = 0.9 } = options;
    if (!typeMap)
        return;
    const typeNode = param.childForFieldName('type');
    if (!typeNode)
        return;
    const typeText = resolveType ? resolveType(typeNode) : typeNode.text;
    if (!typeText)
        return;
    setTypeMapEntry(typeMap, paramName, typeText, typeMapConfidence);
}
/**
 * Resolve the identifier node that names a parameter. Used by
 * {@link extractSimpleParameters}; exposed so language-specific extractors
 * can reuse the same lookup logic in custom loops.
 */
export function resolveParamName(paramNode, nameField, fallbackToIdentifier) {
    if (nameField === null) {
        return paramNode;
    }
    const named = paramNode.childForFieldName(nameField);
    if (named)
        return named;
    if (fallbackToIdentifier) {
        return findChild(paramNode, 'identifier');
    }
    return null;
}
//# sourceMappingURL=helpers.js.map