import { parse } from "@babel/parser";
import * as t from "@babel/types";
import { ComponentsManager } from "../components-manager.js";
import { isAppFilePath, isPageFilePath } from "../file-system-helpers.js";
import { generatePredictableId } from "../ids.js";
import { traverse } from "../traverse.js";
import { getErrorMeta, getLogger } from "../util/logger.js";
import { getSbElementId, supplementElementIds } from "./ids.js";
import { buildNodes, getDescriptiveName, getProperties, getScopeFunctionName, } from "./jsx.js";
import { getIdentiferName, getTagName } from "./util.js";
export function getRootNodes(fileName, file) {
    let ast;
    let shouldModifyAst = true;
    if (!file) {
        throw new Error("No file was provided for " + fileName);
    }
    // if we are passing an ast in, we need to clone it so we do not mutate the original
    if (file.ast) {
        try {
            ast = t.cloneNode(file.ast, true, false);
            shouldModifyAst = false;
        }
        catch (e) {
            getLogger().error("Error cloning AST for file", getErrorMeta(e));
            return [];
        }
    }
    else {
        ast = parse(file.code, {
            sourceType: "module",
            sourceFilename: fileName,
            plugins: ["jsx", "typescript"],
        });
    }
    if (!ast) {
        throw new Error("Failed to parse AST for file " + fileName);
    }
    try {
        supplementElementIds({ fileName, ast, shouldModifyAst });
        const { componentBindings, libraryComponents, jsxVariables } = setupParsingContext(ast);
        // Use the same parsing logic for both App and Page files
        return parseFile(ast, fileName, componentBindings, libraryComponents, jsxVariables);
    }
    catch (e) {
        getLogger().error("Error parsing file", getErrorMeta(e));
        return [];
    }
}
function setupParsingContext(ast) {
    const componentBindings = [];
    const jsxVariables = [];
    const libraryComponents = [];
    traverse(ast, {
        Program: {
            enter(path) {
                // Collect library components
                const importNode = path.node.body.find((n) => n.type == "ImportDeclaration" &&
                    n.source.value == "@superblocksteam/library");
                const declarations = (importNode?.specifiers || [])
                    .map((n) => getIdentiferName(n.local))
                    .filter((n) => n !== null);
                libraryComponents.push(...declarations);
                // Collect component bindings
                Object.values(path.scope.getProgramParent().getAllBindingsOfKind("hoisted")).filter((binding) => {
                    let isJSXComponent = false;
                    binding.path.traverse({
                        JSXElement(path) {
                            if (!isJSXComponent) {
                                path.skip();
                                return;
                            }
                            const sbElementId = getSbElementId(path.node.openingElement);
                            if (sbElementId) {
                                componentBindings.push({
                                    binding,
                                    sbElementId,
                                });
                                path.skip();
                            }
                        },
                        ReturnStatement(path) {
                            if (path.get("argument").isJSXElement()) {
                                isJSXComponent = true;
                            }
                        },
                    });
                    return isJSXComponent;
                });
            },
        },
        // Collect component bindings from variable declarations (const/let)
        VariableDeclarator(path) {
            const id = path.get("id");
            const init = path.get("init");
            // Look for const/let Component = () => JSX or const/let Component = function() { return JSX }
            if (id.isIdentifier() &&
                (init.isArrowFunctionExpression() || init.isFunctionExpression())) {
                const binding = path.scope.getBinding(id.node.name);
                if (!binding)
                    return;
                let isJSXComponent = false;
                let foundJSXElement = null;
                // Check for arrow function with direct JSX expression: () => (<JSX />)
                if (init.isArrowFunctionExpression()) {
                    const body = init.get("body");
                    if (body.isJSXElement()) {
                        isJSXComponent = true;
                        foundJSXElement = body;
                    }
                }
                // Also traverse for return statements and other JSX elements
                init.traverse({
                    JSXElement(jsxPath) {
                        if (!isJSXComponent) {
                            return;
                        }
                        const sbElementId = getSbElementId(jsxPath.node.openingElement);
                        if (sbElementId) {
                            componentBindings.push({
                                binding,
                                sbElementId,
                            });
                            jsxPath.skip();
                        }
                    },
                    ReturnStatement(returnPath) {
                        if (returnPath.get("argument").isJSXElement()) {
                            isJSXComponent = true;
                        }
                    },
                });
                if (foundJSXElement && foundJSXElement.isJSXElement()) {
                    const sbElementId = getSbElementId(foundJSXElement.node.openingElement);
                    if (sbElementId) {
                        componentBindings.push({
                            binding,
                            sbElementId,
                        });
                    }
                }
            }
            // Look for const/let variable = (<JSX />) - JSX variable declarations
            if (id.isIdentifier() && init.isJSXElement()) {
                const binding = path.scope.getBinding(id.node.name);
                if (binding) {
                    jsxVariables.push({
                        binding,
                        jsxElement: init,
                    });
                }
            }
            // Look for const/let variable = useMemo(() => <JSX />, []) or similar function calls
            if (id.isIdentifier() && init.isCallExpression()) {
                const binding = path.scope.getBinding(id.node.name);
                if (binding) {
                    init.traverse({
                        JSXElement(jsxPath) {
                            jsxVariables.push({
                                binding,
                                jsxElement: jsxPath,
                            });
                            jsxPath.skip();
                        },
                    });
                }
            }
            // Look for const/let variable = () => <JSX /> - arrow functions and regular functions
            if (id.isIdentifier() &&
                (init.isArrowFunctionExpression() || init.isFunctionExpression())) {
                const binding = path.scope.getBinding(id.node.name);
                if (binding) {
                    init.traverse({
                        JSXElement(jsxPath) {
                            jsxVariables.push({
                                binding,
                                jsxElement: jsxPath,
                            });
                            jsxPath.skip();
                        },
                    });
                }
            }
        },
    });
    return {
        libraryComponents,
        componentBindings,
        jsxVariables,
    };
}
function parseFile(ast, fileName, componentBindings, libraryComponents, jsxVariables) {
    const roots = [];
    const isPageFile = isPageFilePath(fileName);
    const isAppFile = isAppFilePath(fileName);
    const onlyOneRoot = countRoots(ast) === 1;
    const traverseOptions = {
        JSXElement(path) {
            const tagName = getTagName(path.node);
            if (!tagName) {
                path.skip();
                return;
            }
            const isPage = isPageFile && (onlyOneRoot || isDefaultExport(path, ast));
            const isApp = isAppFile && (onlyOneRoot || isDefaultExport(path, ast));
            const rootKind = isPage ? "page" : isApp ? "app" : "component";
            const isCustomComponentRoot = ComponentsManager.getInstance().isCustomComponentFile(fileName);
            const rootName = isCustomComponentRoot
                ? (getScopeFunctionName(path) ??
                    ComponentsManager.getInstance().getComponentName(fileName) ??
                    tagName)
                : isPage || isApp
                    ? tagName
                    : (getScopeFunctionName(path) ?? getDescriptiveName(path.node));
            if (!rootName) {
                path.skip();
                return;
            }
            const rootId = generatePredictableId(`${rootName}-${fileName}`);
            const rootNode = {
                name: rootName,
                id: rootId,
                type: "RootNode",
                kind: rootKind,
                root: {
                    type: "WidgetNode",
                    name: tagName,
                    id: getSbElementId(path.node.openingElement) ??
                        `UNKNOWN_ROOT_ID-${rootId}`,
                    tagName,
                    properties: getProperties(path, {
                        bindings: componentBindings,
                        jsxVariables,
                        libraryComponents,
                    }),
                    children: [],
                },
            };
            for (const child of path.get("children")) {
                buildNodes(child, rootNode.root, {
                    bindings: componentBindings,
                    jsxVariables,
                    libraryComponents,
                });
            }
            roots.push(rootNode);
            path.skip();
        },
    };
    traverse(ast, {
        ReturnStatement(path) {
            const argument = path.get("argument");
            if (argument.isJSXElement() || argument.isJSXFragment()) {
                path.traverse(traverseOptions);
                path.skip();
                return;
            }
        },
        ArrowFunctionExpression(path) {
            const body = path.get("body");
            if (body.isJSXElement() || body.isJSXFragment()) {
                body.traverse(traverseOptions);
                path.skip();
                return;
            }
        },
    });
    return roots;
}
function getDefaultExport(ast) {
    const result = { name: "", hasAnonymous: false };
    const traverseFn = traverse.default || traverse;
    traverseFn(ast, {
        ExportDefaultDeclaration(path) {
            const declaration = path.node.declaration;
            if (declaration.type === "Identifier") {
                result.name = declaration.name;
            }
            else if (declaration.type === "FunctionDeclaration") {
                if (declaration.id) {
                    result.name = declaration.id.name;
                }
                else {
                    result.hasAnonymous = true;
                }
            }
            else if (declaration.type === "ArrowFunctionExpression" ||
                declaration.type === "FunctionExpression") {
                result.hasAnonymous = true;
            }
            path.stop();
        },
    });
    return result;
}
function isDefaultExport(path, ast) {
    const defaultExport = getDefaultExport(ast);
    const scopeFunctionName = getScopeFunctionName(path);
    const isNamedDefaultExport = scopeFunctionName && scopeFunctionName === defaultExport.name;
    const isAnonymousDefaultExport = defaultExport.hasAnonymous && !scopeFunctionName;
    return isNamedDefaultExport || isAnonymousDefaultExport;
}
function countRoots(ast) {
    let count = 0;
    const traverseFn = traverse.default || traverse;
    traverseFn(ast, {
        ReturnStatement(path) {
            const argument = path.get("argument");
            if (argument.isJSXElement() || argument.isJSXFragment()) {
                count++;
                path.skip();
            }
        },
        ArrowFunctionExpression(path) {
            const body = path.get("body");
            if (body.isJSXElement() || body.isJSXFragment()) {
                count++;
                path.skip();
            }
        },
    });
    return count;
}
//# sourceMappingURL=page.js.map