import { parse } from "@babel/parser";
import * as t from "@babel/types";
import { NO_SELECT_ATTRIBUTE } from "@superblocksteam/library-shared";
import { generate } from "./generate.js";
import { getTagName } from "./parsing/util.js";
import { traverse } from "./traverse.js";
function injectIntoFunction(funcPath) {
    funcPath.traverse({
        JSXOpeningElement(path) {
            const tagName = getTagName(path.node.name);
            if (!tagName) {
                return;
            }
            const hasAttribute = path.node.attributes.some((attr) => t.isJSXAttribute(attr) &&
                t.isJSXIdentifier(attr.name) &&
                attr.name.name === NO_SELECT_ATTRIBUTE);
            if (hasAttribute) {
                return;
            }
            // path.node.attributes.push(
            //   t.jsxAttribute(t.jsxIdentifier(NO_SELECT_ATTRIBUTE)),
            // );
        },
    });
}
function isComponentFunction(funcPath) {
    let hasJSX = false;
    funcPath.traverse({
        JSXElement() {
            hasJSX = true;
        },
        JSXFragment() {
            hasJSX = true;
        },
    });
    return hasJSX;
}
/**
 * Injects data-no-select attribute to all native HTML tags inside registered components.
 */
export function injectNoSelectAttribute(code, componentName) {
    const ast = parse(code, {
        sourceType: "module",
        plugins: ["typescript", "jsx"],
    });
    const registeredComponentNames = new Set();
    function extractComponentNames(node, nodePath) {
        if (Array.isArray(nodePath)) {
            return;
        }
        if (t.isIdentifier(node)) {
            registeredComponentNames.add(node.name);
        }
        else if (t.isFunctionExpression(node) ||
            t.isArrowFunctionExpression(node)) {
            // Handle inline function: registerComponent(() => <div/>, {})
            if (isComponentFunction(nodePath)) {
                injectIntoFunction(nodePath);
            }
        }
        else if (t.isCallExpression(node)) {
            // Handle HOC: withTheme(MyComp) or nested HOCs: withRouter(withTheme(MyComp))
            for (let i = 0; i < node.arguments.length; i++) {
                const arg = node.arguments[i];
                if (!arg || t.isSpreadElement(arg) || t.isArgumentPlaceholder(arg)) {
                    continue;
                }
                const argPath = nodePath.get(`arguments.${i}`);
                extractComponentNames(arg, argPath);
            }
        }
    }
    traverse(ast, {
        CallExpression(path) {
            const callee = path.node.callee;
            if (t.isIdentifier(callee) && callee.name === "registerComponent") {
                const firstArg = path.node.arguments[0];
                if (!firstArg ||
                    t.isSpreadElement(firstArg) ||
                    t.isArgumentPlaceholder(firstArg)) {
                    return;
                }
                const firstArgPath = path.get("arguments.0");
                extractComponentNames(firstArg, firstArgPath);
            }
        },
    });
    traverse(ast, {
        FunctionDeclaration(path) {
            if (path.node.id?.name &&
                registeredComponentNames.has(path.node.id.name)) {
                injectIntoFunction(path);
            }
        },
        VariableDeclarator(path) {
            const id = path.get("id");
            if (id.isIdentifier() && registeredComponentNames.has(id.node.name)) {
                const init = path.get("init");
                if (init.isArrowFunctionExpression() || init.isFunctionExpression()) {
                    injectIntoFunction(init);
                }
                else if (init.isCallExpression()) {
                    // Handle forwardRef and other HOCs: const Button = forwardRef((props, ref) => ...)
                    const args = init.get("arguments");
                    for (const arg of args) {
                        if (Array.isArray(arg)
                            ? false
                            : arg.isArrowFunctionExpression() || arg.isFunctionExpression()) {
                            injectIntoFunction(arg);
                        }
                    }
                }
            }
        },
    });
    const output = generate(ast, {
        retainLines: false,
        compact: false,
        sourceMaps: true,
        sourceFileName: componentName,
    }, code);
    return {
        code: output.code,
        map: output.map,
    };
}
/**
 * Injects data-no-select attribute to all native HTML tags inside all component functions in the file.
 */
export function injectNoSelectAttributeAll(code, sourceFileName) {
    const ast = parse(code, {
        sourceType: "module",
        plugins: ["typescript", "jsx"],
    });
    traverse(ast, {
        // Look for all function declarations that return JSX
        FunctionDeclaration(path) {
            if (isComponentFunction(path)) {
                injectIntoFunction(path);
            }
        },
        // Look for all arrow/regular functions assigned to variables
        VariableDeclarator(path) {
            const init = path.get("init");
            if ((init.isArrowFunctionExpression() || init.isFunctionExpression()) &&
                isComponentFunction(init)) {
                injectIntoFunction(init);
            }
            else if (init.isCallExpression()) {
                // Handle forwardRef and other HOCs: const Button = forwardRef((props, ref) => ...)
                const args = init.get("arguments");
                for (const arg of args) {
                    if (Array.isArray(arg)
                        ? false
                        : arg.isArrowFunctionExpression() || arg.isFunctionExpression()) {
                        if (isComponentFunction(arg)) {
                            injectIntoFunction(arg);
                        }
                    }
                }
            }
        },
    });
    const output = generate(ast, {
        retainLines: false,
        compact: false,
        sourceMaps: true,
        sourceFileName,
    }, code);
    return {
        code: output.code,
        map: output.map,
    };
}
//# sourceMappingURL=inject-no-select.js.map