import { hashInlineId, buildExportMap } from "./expose-id-utils.ts";
function isDirectivePrologueStatement(node) {
    return (node?.type === "ExpressionStatement" &&
        typeof node.directive === "string");
}
/**
 * Find where generated imports should be inserted:
 * after the directive prologue and any contiguous import declarations.
 */
function findImportInsertionPos(code, parseAst) {
    let program;
    try {
        program = parseAst(code, { jsx: true });
    }
    catch {
        return 0;
    }
    const body = program.body;
    let i = 0;
    let insertionPos = 0;
    while (i < body.length && isDirectivePrologueStatement(body[i])) {
        insertionPos = body[i].end;
        i++;
    }
    while (i < body.length && body[i]?.type === "ImportDeclaration") {
        insertionPos = body[i].end;
        i++;
    }
    return insertionPos;
}
// ---------------------------------------------------------------------------
// AST walking helper
// ---------------------------------------------------------------------------
/**
 * Recursively walk an ESTree AST node, calling `enter` on each node.
 * Parent is passed for context.
 */
function walkNode(node, parent, ancestors, enter) {
    if (!node || typeof node !== "object")
        return;
    if (typeof node.type !== "string")
        return;
    ancestors.push(node);
    enter(node, parent, ancestors);
    for (const key of Object.keys(node)) {
        if (key === "type" || key === "start" || key === "end")
            continue;
        const child = node[key];
        if (Array.isArray(child)) {
            for (const item of child) {
                if (item && typeof item === "object" && typeof item.type === "string") {
                    walkNode(item, node, ancestors, enter);
                }
            }
        }
        else if (child && typeof child === "object" && typeof child.type === "string") {
            walkNode(child, node, ancestors, enter);
        }
    }
    ancestors.pop();
}
// ---------------------------------------------------------------------------
// AST analysis
// ---------------------------------------------------------------------------
/**
 * Parse the file with Vite's parseAst and find all calls to `fnName`.
 * Distinguishes between `export const X = fnName(...)` (exportInfo set)
 * and inline calls like `layout(fnName(...))` (exportInfo null).
 */
export function findHandlerCalls(code, fnName, parseAst) {
    let program;
    try {
        program = parseAst(code, { jsx: true });
    }
    catch {
        return [];
    }
    const sites = [];
    const localNames = getImportedLocalNamesFromProgram(program, fnName);
    const exportedNamesByLocal = buildExportMap(program);
    walkNode(program, null, [], (node, parent, ancestors) => {
        if (node.type !== "CallExpression" ||
            node.callee?.type !== "Identifier" ||
            !localNames.has(node.callee.name)) {
            return;
        }
        const callStart = node.start;
        const callEnd = node.end;
        const argCount = node.arguments?.length ?? 0;
        const calleeName = node.callee.name;
        // Compute 1-based line number
        let lineNumber = 1;
        for (let i = 0; i < callStart && i < code.length; i++) {
            if (code[i] === "\n")
                lineNumber++;
        }
        // Check if this is an export const pattern:
        // ExportNamedDeclaration > VariableDeclaration > VariableDeclarator(init=CallExpression)
        let exportInfo = null;
        if (parent?.type === "VariableDeclarator" && parent.init === node) {
            // ancestors: [..., ExportNamedDecl, VarDecl, VarDeclarator, CallExpr]
            const grandParent = ancestors.length >= 3 ? ancestors[ancestors.length - 3] : null;
            const greatGrandParent = ancestors.length >= 4 ? ancestors[ancestors.length - 4] : null;
            if (grandParent?.type === "VariableDeclaration" &&
                greatGrandParent?.type === "ExportNamedDeclaration") {
                const exportName = parent.id?.name;
                if (exportName) {
                    exportInfo = {
                        exportName,
                        statementEnd: greatGrandParent.end,
                    };
                }
            }
            else if (grandParent?.type === "VariableDeclaration" &&
                parent.id?.type === "Identifier") {
                const exportedNames = exportedNamesByLocal.get(parent.id.name);
                if (exportedNames && exportedNames.length > 0) {
                    exportInfo = {
                        exportName: exportedNames[0],
                        statementEnd: grandParent.end,
                    };
                }
            }
        }
        sites.push({
            callStart,
            callEnd,
            argCount,
            lineNumber,
            calleeName,
            exportInfo,
        });
    });
    return sites;
}
function getImportedLocalNamesFromProgram(program, importedName) {
    const localNames = new Set();
    const body = program.body;
    for (const node of body) {
        if (node?.type !== "ImportDeclaration")
            continue;
        const source = node.source?.value;
        if (typeof source !== "string")
            continue;
        if (!source.startsWith("@rangojs/router"))
            continue;
        const specifiers = Array.isArray(node.specifiers) ? node.specifiers : [];
        for (const spec of specifiers) {
            if (spec?.type !== "ImportSpecifier")
                continue;
            if (spec.imported?.type !== "Identifier")
                continue;
            if (spec.imported.name !== importedName)
                continue;
            if (spec.local?.type === "Identifier") {
                localNames.add(spec.local.name);
            }
        }
    }
    return localNames;
}
export function getImportedLocalNames(code, importedName, parseAst) {
    try {
        const program = parseAst(code, { jsx: true });
        return getImportedLocalNamesFromProgram(program, importedName);
    }
    catch {
        return new Set();
    }
}
/**
 * Extract all import declarations from the source as raw text slices.
 * Copies ALL imports -- Rollup tree-shakes unused ones from virtual modules.
 */
export function extractImportDeclarations(code, parseAst) {
    let program;
    try {
        program = parseAst(code, { jsx: true });
    }
    catch {
        return [];
    }
    const imports = [];
    for (const node of program.body) {
        if (node.type === "ImportDeclaration") {
            imports.push(code.slice(node.start, node.end));
        }
    }
    return imports;
}
// ---------------------------------------------------------------------------
// Transform
// ---------------------------------------------------------------------------
/**
 * Transform inline handler calls by extracting them into virtual modules.
 * Only processes inline calls (exportInfo === null); export const calls are
 * handled by the existing regex fast path.
 *
 * Always extracts (dev and build) to keep server-only imports out of non-RSC
 * environments. The virtual module goes through the standard transform pipeline
 * automatically -- the existing export const regex path handles it.
 *
 * Returns true if any inline calls were transformed.
 */
export function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtualRegistry, moduleId, parseAst) {
    const sites = findHandlerCalls(code, fnName, parseAst);
    const inlineSites = sites.filter((site) => site.exportInfo === null);
    if (inlineSites.length === 0)
        return false;
    const imports = extractImportDeclarations(code, parseAst);
    // Track line occurrences for same-line collision handling
    const lineCounts = new Map();
    // Collect all import statements to prepend
    const importStatements = [];
    for (const site of inlineSites) {
        const lineCount = lineCounts.get(site.lineNumber) ?? 0;
        lineCounts.set(site.lineNumber, lineCount + 1);
        const hash = hashInlineId(filePath, site.lineNumber, lineCount);
        const exportName = `__sh_${hash}`;
        const virtualId = `\0${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
        // Extract the full handler call expression text
        const handlerCode = code.slice(site.callStart, site.callEnd);
        // Register virtual module
        virtualRegistry.set(virtualId, {
            originalModuleId: moduleId,
            imports,
            handlerCode,
            exportName,
        });
        // Replace inline call with the import name
        s.overwrite(site.callStart, site.callEnd, exportName);
        // Build the import specifier for this virtual module
        const importId = `${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
        importStatements.push(`import { ${exportName} } from "${importId}";`);
    }
    // Insert imports after directive prologue + existing import block
    if (importStatements.length > 0) {
        const importBlock = importStatements.join("\n") + "\n";
        const insertionPos = findImportInsertionPos(code, parseAst);
        if (insertionPos === 0) {
            s.prepend(importBlock);
        }
        else {
            s.appendLeft(insertionPos, "\n" + importBlock);
        }
    }
    return true;
}
//# sourceMappingURL=ast-handler-extract.js.map