"use strict";
/**
 * Copyright: (c) Myia SAS 2026.
 * This file and its contents are licensed under the AGPLv3 License.
 * Please see the LICENSE file at the root of this repository
 */
const traced_file_matcher_1 = require("../../domain/code-hook-injector/service/traced-file-matcher");
const cjs_artifact_detector_1 = require("../../domain/code-hook-injector/service/statement-analysis/cjs-artifact-detector");
const leaf_statement_classifier_1 = require("../../domain/code-hook-injector/service/statement-analysis/leaf-statement-classifier");
const non_wrappable_statement_1 = require("../../domain/code-hook-injector/service/statement-analysis/non-wrappable-statement");
const heal_ignore_directive_1 = require("../../domain/code-hook-injector/service/statement-analysis/heal-ignore-directive");
const for_head_declaration_detector_1 = require("../../domain/code-hook-injector/service/statement-analysis/for-head-declaration-detector");
const async_enclosing_function_detector_1 = require("../../domain/code-hook-injector/service/statement-analysis/async-enclosing-function-detector");
const enclosing_scope_labeler_1 = require("../../domain/code-hook-injector/service/meta-fields/enclosing-scope-labeler");
const source_snippet_extractor_1 = require("../../domain/code-hook-injector/service/meta-fields/source-snippet-extractor");
const leading_comment_extractor_1 = require("../../domain/code-hook-injector/service/meta-fields/leading-comment-extractor");
const relative_file_path_1 = require("../../domain/code-hook-injector/service/meta-fields/relative-file-path");
const playwright_import_rewriter_1 = require("../../domain/code-hook-injector/service/playwright-import-rewriter");
const global_trace_call_1 = require("../../domain/code-hook-injector/service/trace-hook/global-trace-call");
const enter_meta_literal_1 = require("../../domain/code-hook-injector/service/trace-hook/enter-meta-literal");
const try_finally_wrapper_1 = require("../../domain/code-hook-injector/service/trace-hook/try-finally-wrapper");
const variable_declaration_hoister_1 = require("../../domain/code-hook-injector/service/trace-hook/variable-declaration-hoister");
const preprocess_call_1 = require("../../domain/code-hook-injector/service/trace-hook/preprocess-call");
const global_names_1 = require("../../domain/trace-event-recorder/model/global-names");
function codeHookInjector(api, opts = {}) {
    const t = api.types;
    const CWD = opts.rootDir || process.cwd();
    const matches = (0, traced_file_matcher_1.buildMatcher)(opts.include);
    const { isGeneratedModuleStatement } = (0, cjs_artifact_detector_1.createCjsArtifactDetector)(t);
    const { isLeafStatement, kindOf, containsAwait } = (0, leaf_statement_classifier_1.createLeafStatementClassifier)(t);
    const findScopeName = (0, enclosing_scope_labeler_1.createEnclosingScopeLabeler)(t);
    const isNonWrappableStatement = (0, non_wrappable_statement_1.createNonWrappableStatementPredicate)(t, isGeneratedModuleStatement);
    const isForHeadDeclaration = (0, for_head_declaration_detector_1.createForHeadDeclarationDetector)(t);
    const isAsyncEnclosing = (0, async_enclosing_function_detector_1.createAsyncEnclosingFunctionDetector)(t);
    const rewritePlaywrightImports = (0, playwright_import_rewriter_1.createPlaywrightImportRewriter)(t);
    const callStmt = (0, global_trace_call_1.createGlobalTraceCallBuilder)(t);
    const buildMeta = (0, enter_meta_literal_1.createEnterMetaLiteralBuilder)(t, {
        kindOf,
        containsAwait,
        findScopeName,
        extractSource: source_snippet_extractor_1.extractSource,
        extractLeadingComment: leading_comment_extractor_1.extractLeadingComment,
        relFile: relative_file_path_1.relFile,
    });
    const { buildTryFinally, buildThrewDecl } = (0, try_finally_wrapper_1.createTryFinallyWrapperBuilder)(t, callStmt);
    const hoistVariableDeclaration = (0, variable_declaration_hoister_1.createVariableDeclarationHoister)(t);
    const buildPreprocessCall = (0, preprocess_call_1.createPreprocessCallBuilder)(t);
    return {
        name: 'heal-playwright-tracer',
        visitor: {
            // Rewrite `@playwright/test` → `@heal-dev/heal-playwright-tracer` at
            // the top of every matched file. Runs before the Statement visitor
            // because Program.enter fires first. The user keeps the standard
            // Playwright import; our auto-fixture loads transparently.
            Program(programPath, state) {
                if (!matches(state.file.opts.filename || ''))
                    return;
                rewritePlaywrightImports(programPath.node);
            },
            Statement(stmtPath, state) {
                const node = stmtPath.node;
                // Don't recurse into our own generated wrapper.
                if (node._traced)
                    return;
                // Babel sometimes hands us synthetic nodes without source locations.
                if (!node.loc)
                    return;
                // File-level filter: only touch files the consumer opted in to.
                if (!matches(state.file.opts.filename || ''))
                    return;
                // Author opt-out: a `// @heal-tracer-ignore` directive on this
                // statement or any enclosing function/block. Checked before
                // any hook family so an ignored statement emits no
                // enter/ok/throw and no preprocess.
                if ((0, heal_ignore_directive_1.isUnderHealIgnore)(stmtPath))
                    return;
                // Skip statement kinds that no hook family should wrap.
                if (isNonWrappableStatement(stmtPath))
                    return;
                // Only leaf statements get wrapped. Compound statements
                // (if/for/while/switch/try) are transparent — their inner
                // leaves will be visited independently.
                if (!isLeafStatement(node))
                    return;
                // From here on it's trace-hook-specific. When a second hook
                // family lands, extract the block below into a
                // `applyTraceHookIfLeaf(stmtPath, state)` function in
                // ./trace-hook/ and call it alongside the future families.
                const meta = buildMeta(state, stmtPath, node, CWD);
                // Optional pre-processor call inserted at the very start of
                // the try body. Gated on the enclosing function being async
                // because the call itself is `await __heal_preprocess?.(meta)`
                // — a syntax error in sync functions. Sync statements skip
                // the preprocess phase entirely and are still traced.
                // `cloneNode(meta)` so the same literal isn't shared between
                // the __heal_enter call and this one.
                const preTryStmts = isAsyncEnclosing(stmtPath)
                    ? [buildPreprocessCall(global_names_1.HEAL_PREPROCESS, t.cloneNode(meta))]
                    : [];
                if (t.isVariableDeclaration(node)) {
                    // `const x = EXPR` can't be wrapped as-is — the binding
                    // would be scoped to the try block and invisible downstream.
                    // Hoist the bindings out, assign inside the try, pass a
                    // vars object to __ok so the recorder can snapshot values.
                    if (isForHeadDeclaration(node, stmtPath.parent))
                        return;
                    const { hoistDecl, assignments, varsObject, bindingNames } = hoistVariableDeclaration(node);
                    const okArgs = bindingNames.size ? [varsObject] : [];
                    const { threwId, tryStmt } = buildTryFinally(stmtPath.scope, [...preTryStmts, ...assignments], okArgs);
                    stmtPath.replaceWithMultiple([
                        callStmt(global_names_1.HEAL_ENTER, [meta]),
                        hoistDecl,
                        buildThrewDecl(threwId),
                        tryStmt,
                    ]);
                    return;
                }
                // Every other leaf statement: wrap in a block with the
                // original statement inside the try body.
                node._traced = true;
                const { threwId, tryStmt } = buildTryFinally(stmtPath.scope, [...preTryStmts, node]);
                const wrapper = t.blockStatement([
                    callStmt(global_names_1.HEAL_ENTER, [meta]),
                    buildThrewDecl(threwId),
                    tryStmt,
                ]);
                stmtPath.replaceWith(wrapper);
            },
        },
    };
}
module.exports = codeHookInjector;
