"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeDebugStep = executeDebugStep;
exports.syncDebugStateAfterStep = syncDebugStateAfterStep;
const path = __importStar(require("path"));
const playwright_core_1 = require("playwright-core");
const uuid_1 = require("uuid");
const IfConditionStep_1 = require("@mablhq/test-model/steps/IfConditionStep");
const ElseIfConditionStep_1 = require("@mablhq/test-model/steps/ElseIfConditionStep");
const DebugSession_1 = require("./DebugSession");
const DebugFlowRunner_1 = require("./DebugFlowRunner");
const expandSteps_1 = require("./expandSteps");
const buildDebugSessionState_1 = require("./buildDebugSessionState");
const debugVariableBridge_1 = require("./debugVariableBridge");
const stepIndexUtils_1 = require("../trainer/sharedUtils/stepIndexUtils");
const browserTypes_1 = require("../core/browserTypes");
const messaging_1 = require("../core/messaging/messaging");
const browserLauncher_1 = require("../browserLauncher");
const execution_1 = require("../execution");
const mablApiClientFactory_1 = require("../api/mablApiClientFactory");
function locateStep(snapshot, stepRef) {
    const expanded = (0, expandSteps_1.expandSessionSteps)(snapshot);
    for (let i = 0; i < expanded.length; i++) {
        const entry = expanded[i];
        if (entry.step.stepId() === stepRef ||
            (0, expandSteps_1.addressableStepId)(entry, i) === stepRef) {
            return {
                flow: entry.flow,
                step: entry.step,
                globalIndex: i,
                subtreeEndIndex: entry.subtreeEndIndex,
                totalSteps: expanded.length,
            };
        }
    }
    throw new Error(`Step with id "${stepRef}" not found in session snapshot`);
}
function restoreVariables(executionContext, variables) {
    for (const [name, value] of Object.entries(variables)) {
        executionContext.variables.setTestVariable(name, value);
    }
}
function snapshotVariables(executionContext) {
    const flat = {};
    const visit = (prefix, node) => {
        if (node === undefined) {
            return;
        }
        if (typeof node === 'object' && !Array.isArray(node)) {
            for (const key of Object.keys(node)) {
                const next = node[key];
                const path = prefix ? `${prefix}.${key}` : key;
                if (next !== undefined &&
                    typeof next === 'object' &&
                    !Array.isArray(next) &&
                    Object.getPrototypeOf(next) === Object.prototype) {
                    visit(path, next);
                }
                else {
                    flat[path] = typeof next === 'string' ? next : JSON.stringify(next);
                }
            }
        }
        else {
            flat[prefix] = typeof node === 'string' ? node : JSON.stringify(node);
        }
    };
    try {
        const all = executionContext.variables.getAll(true);
        visit('', all);
    }
    catch {
    }
    return flat;
}
function buildBrowserConfig(session) {
    const cfg = session.snapshot.browserConfig;
    return {
        browserName: cfg.browser,
        browserType: browserTypes_1.BrowserType.Chrome,
        headmode: cfg.headless ? execution_1.Headmode.Headless : execution_1.Headmode.Headful,
        height: cfg.height,
        width: cfg.width,
        localizationOptions: {
            locale: cfg.locale,
            timezone_identifier: cfg.timezone,
        },
        userAgent: cfg.userAgent,
    };
}
async function executeDebugStep(sessionId, stepId) {
    const session = (0, DebugSession_1.readSession)(sessionId);
    const located = locateStep(session.snapshot, stepId);
    const cdpBrowser = await playwright_core_1.chromium.connectOverCDP(session.wsEndpoint);
    const startTime = Date.now();
    let result;
    let executionError;
    let executionContext;
    try {
        const contexts = cdpBrowser.contexts();
        const context = contexts.length > 0 ? contexts[0] : await cdpBrowser.newContext();
        const downloadDirectory = path.join((0, DebugSession_1.sessionDir)(session.sessionId), 'downloads');
        const mablBrowser = await browserLauncher_1.PlaywrightBrowser.create(browserTypes_1.BrowserType.Chrome, playwright_core_1.chromium, context, downloadDirectory, session.wsEndpoint, session.userDataDir);
        const resolvedUrl = session.snapshot.urlOverride ?? session.snapshot.test.url ?? '';
        const testContext = new execution_1.TestContext(session.snapshot.test, resolvedUrl, session.snapshot.flows, []);
        if (session.snapshot.environment) {
            testContext.environment = session.snapshot.environment;
        }
        const apiClientFactory = mablApiClientFactory_1.MablApiClientFactory.createApiClient;
        if (session.snapshot.credentialsId) {
            testContext.credentials = await fetchCredential(apiClientFactory, session.snapshot.credentialsId);
        }
        if (session.snapshot.basicAuthCredentialsId) {
            testContext.basicAuthCredentials = await fetchCredential(apiClientFactory, session.snapshot.basicAuthCredentialsId);
        }
        const branchName = session.snapshot.branchName;
        testContext.setFlowProvider(buildFlowProvider(session.snapshot.flows, apiClientFactory, branchName));
        const organizationId = session.snapshot.test.organization_id;
        if (organizationId) {
            testContext.setSnippetProvider(buildSnippetProvider(apiClientFactory, organizationId));
        }
        const browserConfig = buildBrowserConfig(session);
        const eventEmitter = new messaging_1.MablCoreEventEmitter();
        const mablApiClient = await apiClientFactory();
        executionContext = new execution_1.BrowserTestExecutionContext(mablApiClient, browserConfig, testContext, eventEmitter);
        executionContext.browser = mablBrowser;
        try {
            await executionContext.pageFrameContext.initializeContextTracker(mablBrowser, false);
        }
        catch (initErr) {
            executionContext.log.warning(`Failed to initialize page frame context: ${initErr instanceof Error ? initErr.message : String(initErr)}`);
        }
        restoreVariables(executionContext, session.variables);
        const flowController = new execution_1.FlowController(executionContext, located.flow, [], execution_1.OBSERVER_TIMEOUT_WARNING);
        const flowRunner = new DebugFlowRunner_1.DebugFlowRunner(flowController);
        const stepRunId = (0, uuid_1.v4)();
        executionContext.executionIndexTracker.setStep(located.step, stepRunId);
        executionContext.stepLogCollector.start();
        try {
            result = await flowRunner.executeSingleStep(located.step);
        }
        finally {
            executionContext.stepLogCollector.stop();
            executionContext.executionIndexTracker.clearStep();
        }
    }
    catch (e) {
        executionError = e instanceof Error ? e : new Error(String(e));
    }
    finally {
        await cdpBrowser.close();
    }
    const durationMs = Date.now() - startTime;
    const isConditional = (0, IfConditionStep_1.isIfConditionStep)(located.step) || (0, ElseIfConditionStep_1.isElseIfConditionStep)(located.step);
    const runtimeReportedFailure = !executionError && result?.status !== execution_1.StepStatus.Passed;
    const status = !executionError && (result?.status === execution_1.StepStatus.Passed || isConditional)
        ? 'passed'
        : 'failed';
    const conditionResult = isConditional
        ? result?.status === execution_1.StepStatus.Passed
        : undefined;
    const errorMessage = executionError
        ? executionError.message
        : status === 'failed' && runtimeReportedFailure
            ? result?.logInfo
            : undefined;
    const stepResult = {
        stepId,
        status,
        durationMs,
        error: errorMessage,
    };
    const persisted = (0, DebugSession_1.readSession)(sessionId);
    persisted.stepResults.push(stepResult);
    if (status === 'passed') {
        persisted.currentStepIndex = located.subtreeEndIndex + 1;
    }
    if (executionContext) {
        persisted.variables = snapshotVariables(executionContext);
    }
    (0, DebugSession_1.writeSession)(persisted);
    await syncDebugStateAfterStep(sessionId, persisted.snapshot, persisted.currentStepIndex, persisted.variables);
    return {
        status,
        stepId,
        durationMs,
        error: errorMessage,
        currentStepIndex: persisted.currentStepIndex,
        remainingSteps: Math.max(0, located.totalSteps - persisted.currentStepIndex),
        conditionResult,
    };
}
function buildFlowProvider(snapshotFlows, apiClient, branchName) {
    const byInvariant = new Map();
    for (const flow of snapshotFlows) {
        if (flow.invariant_id) {
            byInvariant.set(flow.invariant_id, flow);
        }
    }
    return {
        async fetchFlow(invariantId) {
            const cached = byInvariant.get(invariantId);
            if (cached) {
                return cached;
            }
            const client = await apiClient();
            const fetched = await client.getFlow(invariantId, branchName);
            if (fetched.invariant_id) {
                byInvariant.set(fetched.invariant_id, fetched);
            }
            return fetched;
        },
    };
}
function buildSnippetProvider(apiClient, organizationId) {
    const cache = new Map();
    return {
        async fetchSnippet(invariantId) {
            const cached = cache.get(invariantId);
            if (cached) {
                return cached;
            }
            const client = await apiClient();
            const fetched = await client.getSnippet(invariantId, false, true, organizationId);
            cache.set(invariantId, fetched);
            return fetched;
        },
    };
}
async function fetchCredential(apiClientFactory, credentialsId) {
    try {
        const client = await apiClientFactory();
        return await client.getCredential(credentialsId);
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        throw new Error(`Failed to resolve credentials [${credentialsId}]: ${message}. ` +
            'If your auth token has expired, run `mabl auth login` and try again.');
    }
}
async function syncDebugStateAfterStep(sessionId, snapshot, newFlatIndex, flatVariables) {
    await (0, buildDebugSessionState_1.mutateDebugState)(sessionId, (state) => {
        const getNestedSteps = (0, stepIndexUtils_1.createNestedStepResolver)((0, buildDebugSessionState_1.buildFlowStepsResolverFromSnapshot)(snapshot, state));
        const expanded = (0, expandSteps_1.expandSessionSteps)(snapshot);
        if (newFlatIndex >= expanded.length) {
            state.setNextStepIndex([state.getSteps().length]);
        }
        else {
            const nextStepId = expanded[newFlatIndex].step.stepId();
            const resolved = nextStepId !== undefined
                ? (0, stepIndexUtils_1.findStepById)(state.getSteps(), nextStepId, getNestedSteps)
                : undefined;
            if (resolved && !('ambiguous' in resolved)) {
                state.setNextStepIndex(resolved.path);
            }
            else {
                state.setNextStepIndex([
                    topLevelStateIndexForFlatIndex(expanded, newFlatIndex),
                ]);
            }
        }
        state.replaceVariables((0, debugVariableBridge_1.flatVariablesToNamespace)(flatVariables));
    });
}
function topLevelStateIndexForFlatIndex(expanded, flatIndex) {
    let topLevelCount = 0;
    let i = 0;
    while (i < expanded.length) {
        if (flatIndex <= expanded[i].subtreeEndIndex) {
            return topLevelCount;
        }
        topLevelCount++;
        i = expanded[i].subtreeEndIndex + 1;
    }
    return topLevelCount;
}
