import { parse } from "@babel/parser";
import * as t from "@babel/types";
import { EXTERNAL_LIBRARY_OPTIONS } from "./parsing/imports.js";
import { traverse } from "./traverse.js";
const LIBRARY_IMPORT_PATH = EXTERNAL_LIBRARY_OPTIONS.find((option) => option.library === "@superblocksteam/library")?.library ?? "@superblocksteam/library";
const LIBRARY_APP_EXPORT_NAME = "App";
const REACT_ROUTER_IMPORT_PATH = "react-router";
const REACT_ROUTER_OUTLET_EXPORT_NAME = "Outlet";
function isJsxIdentifier(name) {
    return name.type === "JSXIdentifier";
}
function getAppProviderLocalNameFromImports(programPath) {
    for (const stmt of programPath.get("body")) {
        if (!stmt.isImportDeclaration())
            continue;
        if (stmt.node.source.value !== LIBRARY_IMPORT_PATH)
            continue;
        for (const spec of stmt.get("specifiers")) {
            if (!spec.isImportSpecifier())
                continue;
            const imported = spec.get("imported");
            const local = spec.get("local");
            if (imported.isIdentifier() &&
                imported.node.name === LIBRARY_APP_EXPORT_NAME) {
                return local.isIdentifier() ? local.node.name : null;
            }
        }
    }
    return null;
}
function getOutletLocalNameFromImports(programPath) {
    for (const stmt of programPath.get("body")) {
        if (!stmt.isImportDeclaration())
            continue;
        if (stmt.node.source.value !== REACT_ROUTER_IMPORT_PATH)
            continue;
        for (const spec of stmt.get("specifiers")) {
            if (!spec.isImportSpecifier())
                continue;
            const imported = spec.get("imported");
            const local = spec.get("local");
            if (imported.isIdentifier() &&
                imported.node.name === REACT_ROUTER_OUTLET_EXPORT_NAME) {
                return local.isIdentifier() ? local.node.name : null;
            }
        }
    }
    return null;
}
function resolveDefaultExportedFunction(programPath) {
    const body = programPath.get("body");
    const defaultExport = body.find((p) => p.isExportDefaultDeclaration());
    if (!defaultExport || !defaultExport.isExportDefaultDeclaration()) {
        return null;
    }
    const decl = defaultExport.get("declaration");
    if (decl.isFunctionDeclaration()) {
        return decl;
    }
    if (decl.isArrowFunctionExpression() || decl.isFunctionExpression()) {
        return decl;
    }
    if (decl.isIdentifier()) {
        const binding = defaultExport.scope.getBinding(decl.node.name);
        if (!binding) {
            return null;
        }
        const bindingPath = binding.path;
        if (bindingPath.isFunctionDeclaration()) {
            return bindingPath;
        }
        if (bindingPath.isVariableDeclarator()) {
            const init = bindingPath.get("init");
            if (init.isArrowFunctionExpression() || init.isFunctionExpression()) {
                return init;
            }
        }
    }
    return null;
}
function findFirstReturnedJsxExpressionNoMutate(fnPath) {
    const body = fnPath.get("body");
    // Arrow function expression-body returning JSX
    if (!body.isBlockStatement()) {
        if (fnPath.isArrowFunctionExpression() &&
            (body.isJSXElement() || body.isJSXFragment())) {
            return body.node;
        }
        return null;
    }
    const returnPath = body.get("body").find((s) => s.isReturnStatement());
    if (!returnPath)
        return null;
    const arg = returnPath.get("argument");
    if (!arg || Array.isArray(arg) || !arg.isExpression())
        return null;
    if (!arg.isJSXElement() && !arg.isJSXFragment())
        return null;
    return arg.node;
}
function hasOutletInReturnedJsx(returnedJsx, outletLocalName) {
    let found = false;
    const visit = (node) => {
        if (found)
            return;
        if (t.isJSXElement(node)) {
            const name = node.openingElement.name;
            if (isJsxIdentifier(name) && name.name === outletLocalName) {
                found = true;
                return;
            }
            for (const child of node.children) {
                visit(child);
                if (found)
                    return;
            }
            return;
        }
        if (t.isJSXFragment(node)) {
            for (const child of node.children) {
                visit(child);
                if (found)
                    return;
            }
        }
    };
    visit(returnedJsx);
    return found;
}
function hasUnwrappedOutletInReturnedJsx(returnedJsx, providerLocalName, outletLocalName) {
    let foundUnwrappedOutlet = false;
    const visit = (node, underProvider) => {
        if (foundUnwrappedOutlet)
            return;
        if (t.isJSXElement(node)) {
            const name = node.openingElement.name;
            const isProvider = isJsxIdentifier(name) && name.name === providerLocalName;
            const isOutlet = isJsxIdentifier(name) && name.name === outletLocalName;
            const nextUnderProvider = underProvider || isProvider;
            if (isOutlet && !nextUnderProvider) {
                foundUnwrappedOutlet = true;
                return;
            }
            for (const child of node.children) {
                visit(child, nextUnderProvider);
                if (foundUnwrappedOutlet)
                    return;
            }
            return;
        }
        if (t.isJSXFragment(node)) {
            for (const child of node.children) {
                visit(child, underProvider);
                if (foundUnwrappedOutlet)
                    return;
            }
        }
    };
    visit(returnedJsx, false);
    return foundUnwrappedOutlet;
}
export function needsAppProviderFixInAppTsx(sourceCode) {
    // Fast pre-check: most files shouldn't be parsed.
    const hasLiteralOutletTag = sourceCode.includes("<Outlet");
    const mightHaveAliasedOutlet = sourceCode.includes(REACT_ROUTER_IMPORT_PATH) &&
        sourceCode.includes(REACT_ROUTER_OUTLET_EXPORT_NAME) &&
        sourceCode.includes("<");
    if (!hasLiteralOutletTag && !mightHaveAliasedOutlet) {
        return false;
    }
    let ast;
    try {
        ast = parse(sourceCode, {
            sourceType: "module",
            plugins: ["typescript", "jsx"],
        });
    }
    catch {
        return false;
    }
    let programPath = null;
    traverse(ast, {
        Program(path) {
            programPath = path;
            path.stop();
        },
    });
    if (!programPath)
        return false;
    const defaultFn = resolveDefaultExportedFunction(programPath);
    if (!defaultFn)
        return false;
    const returnedJsx = findFirstReturnedJsxExpressionNoMutate(defaultFn);
    if (!returnedJsx)
        return false;
    const outletLocalName = getOutletLocalNameFromImports(programPath) ??
        REACT_ROUTER_OUTLET_EXPORT_NAME;
    // Strict: if there is no Outlet in the returned JSX, we should not complain.
    if (!hasOutletInReturnedJsx(returnedJsx, outletLocalName)) {
        return false;
    }
    const providerLocalName = getAppProviderLocalNameFromImports(programPath);
    if (!providerLocalName) {
        // If Outlet is present but the library App import isn't, Clark must fix App.tsx.
        return true;
    }
    return hasUnwrappedOutletInReturnedJsx(returnedJsx, providerLocalName, outletLocalName);
}
//# sourceMappingURL=ensure-app-provider.js.map