"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;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CliEventHandler = void 0;
const mablApi_1 = require("../../mablApi");
const generation_types_1 = require("../sharedUtils/generation-types");
const browserLauncher_1 = require("../../browserLauncher");
const path = __importStar(require("path"));
const mimeTypes = __importStar(require("mime-types"));
const mablscript_step_types_1 = require("../sharedUtils/mablscript-step-types");
const ExecuteFlowStep_1 = __importDefault(require("../sharedUtils/mablscript/extended/ExecuteFlowStep"));
const ExecuteStepGroupStep_1 = __importDefault(require("../sharedUtils/mablscript/extended/ExecuteStepGroupStep"));
const stepIndexUtils_1 = require("../sharedUtils/stepIndexUtils");
const importer_1 = require("@mablhq/test-model/importer");
const EvaluateFlowStep_1 = require("@mablhq/test-model/steps/EvaluateFlowStep");
const assert_utils_1 = require("../sharedUtils/mablscript/canonical/assert-utils");
const chalk = require('chalk');
const MAX_FAILED_STEP_SCREENSHOTS = 5;
function describeFailedStep(mablStep, result) {
    if (result.stepDescription) {
        return result.stepDescription;
    }
    const description = mablStep.getDescription();
    if (description) {
        return description;
    }
    return mablStep.getStepName();
}
const MAX_ASSERT_VALUE_LENGTH = 200;
function formatAssertValue(value) {
    if (typeof value === 'string') {
        return value.length > MAX_ASSERT_VALUE_LENGTH
            ? `${value.slice(0, MAX_ASSERT_VALUE_LENGTH)}…`
            : value;
    }
    const serialized = JSON.stringify(value);
    if (serialized === undefined) {
        return String(value);
    }
    return serialized.length > MAX_ASSERT_VALUE_LENGTH
        ? `${serialized.slice(0, MAX_ASSERT_VALUE_LENGTH)}…`
        : serialized;
}
function describeFailureReason(result) {
    if (result.assertFailure) {
        const { shortReason, reason, expected, found } = result.assertFailure;
        const headline = shortReason ?? reason;
        if (headline && expected !== undefined && found !== undefined) {
            return `${headline} (expected ${formatAssertValue(expected)}, found ${formatAssertValue(found)})`;
        }
        if (headline) {
            return headline;
        }
    }
    if (result.logInfo) {
        return result.logInfo;
    }
    return `step failed with status ${result.status} but no log or assertion detail was attached`;
}
function formatFlowFailureMessage(flow, failedSteps, totalSteps) {
    const flowLabel = flow.description ?? flow.invariant_id ?? 'flow';
    const header = `Flow "${flowLabel}" had ${failedSteps.length} failed step(s) out of ${totalSteps} step(s) replayed:`;
    const lines = failedSteps.map((failure) => `- Step ${failure.stepPath} "${failure.stepDescription}": ${failure.reason}`);
    return [header, ...lines].join('\n');
}
class CliEventHandler {
    constructor(options) {
        this.screenshotCounter = 0;
        this.trackedDownloads = {};
        this.state = options.state;
        this.trainingContext = options.trainingContext;
        this.flowCache = options.flowCache;
        this.stepExecutor = options.stepExecutor;
        this.rl = options.readlineInterface;
        this.verbose = options.verbose ?? false;
        this.outputScreenshots = options.outputScreenshots ?? false;
        this.registerDownloadListeners();
    }
    emitScreenshot(base64Data, mimeType = 'image/png') {
        if (!this.outputScreenshots) {
            return;
        }
        this.screenshotCounter++;
        const screenshotOutput = {
            name: `step_${this.screenshotCounter}`,
            data: base64Data,
            mimeType,
        };
        process.stdout.write(`[SCREENSHOT] ${JSON.stringify(screenshotOutput)}\n`);
    }
    resumeStepLearning(session) {
        this.state.setActiveSession(session);
        this.state.setStatus(generation_types_1.TestGenerationStatus.LearningSteps);
        if (this.verbose) {
            console.log('Resuming step learning...');
        }
    }
    pauseStepLearning(reason) {
        this.state.setStatus(generation_types_1.TestGenerationStatus.Paused);
        if (this.verbose) {
            console.log(`Step learning paused: ${reason}`);
        }
    }
    failTestGeneration(error) {
        this.state.setStatus(generation_types_1.TestGenerationStatus.Failed);
        console.error(`Test generation failed: ${error}`);
    }
    failWithError(error, options) {
        this.failTestGeneration(error);
        if (options?.showInReplay) {
            this.setReplayResponse({
                level: 'error',
                message: error,
            });
        }
        if (options?.addToLog) {
            this.addAgentLogEntry({
                type: generation_types_1.AgentLogEntryType.TestGenerationPaused,
                reason: 'error',
                errorMessage: error,
            });
        }
    }
    stopLearningSuccess(reason) {
        this.addAgentLogEntry({
            type: generation_types_1.AgentLogEntryType.TestGenerationPaused,
            reason,
        });
        if (reason === 'completedTest') {
            this.state.setStatus(generation_types_1.TestGenerationStatus.Completed);
            console.log('Test generation complete!');
        }
        else {
            this.pauseStepLearning(reason);
            console.log('Task completed!');
        }
    }
    updateAgentState(agentState, publicState) {
        this.state.setAgentState(agentState);
        if (publicState) {
            this.state.setPublicState(publicState);
        }
    }
    updateStateFromResponse(response) {
        this.updateAgentState(response.state ?? {}, response.public_state);
    }
    updateAgentWillPlan(_willPlan) {
        if (this.verbose) {
            console.log(`Agent will plan: ${_willPlan}`);
        }
    }
    completeTask() {
        console.log('Task completed');
    }
    moveCursorIntoGroup() {
        this.state.moveCursorIntoGroup();
    }
    moveCursorOutOfGroup() {
        this.state.moveCursorOutOfGroup();
    }
    isInsideStepGroup() {
        return this.state.isInsideStepGroup();
    }
    isCurrentBranchInactive() {
        return this.stepExecutor
            .getExecutionContext()
            .conditionalContext.isInactive();
    }
    getNextStepIndex() {
        return this.state.getNextStepIndex();
    }
    setNextStepIndex(index) {
        this.state.setNextStepIndex(index);
    }
    getStepAtPathInState(path) {
        return (0, stepIndexUtils_1.getStepAtPath)(this.state.getSteps(), path, (step) => step instanceof ExecuteStepGroupStep_1.default ? step.stepsInGroup : undefined);
    }
    addAgentLogEntry(entry) {
        switch (entry.type) {
            case generation_types_1.AgentLogEntryType.ToolExecuted:
                if ('toolCall' in entry) {
                    const toolName = entry.toolCall?.toolName;
                    const success = entry.toolResult?.success;
                    const statusIcon = success ? '\u2713' : '\u2717';
                    console.log(`  ${statusIcon} ${toolName}`);
                    this.state.setLastToolsExecuted([
                        ...(this.state.getLastToolsExecuted() ?? []),
                        entry,
                    ]);
                }
                break;
            case generation_types_1.AgentLogEntryType.TestGenerationPaused:
                if ('reason' in entry) {
                    console.log(`Generation paused: ${entry.reason}`);
                }
                break;
            case generation_types_1.AgentLogEntryType.Message:
                if ('message' in entry && entry.message) {
                    console.log(`  Message: ${entry.message.message}`);
                }
                break;
            default:
                if (this.verbose) {
                    console.log(`Agent log: ${JSON.stringify(entry)}`);
                }
        }
    }
    clearActiveConversation() {
        return;
    }
    async requestHumanAction(agentMessage, _stopCondition) {
        const existingConversation = this.state
            .getConversations()
            .find((c) => c.id === agentMessage.conversationId);
        if (existingConversation) {
            existingConversation.chatHistory.push(agentMessage);
        }
        else {
            this.state.addConversation({
                id: agentMessage.conversationId,
                chatHistory: [agentMessage],
            });
        }
        console.log('\n' + chalk.gray('-------------------------------------------'));
        console.log(chalk.yellow.bold('The agent needs your help:'));
        console.log(chalk.cyan(`  ${agentMessage.message ?? 'Action needed'}`));
        console.log(chalk.gray('-------------------------------------------'));
        const response = await this.promptUser(chalk.magenta('Your response: '));
        const userMessage = {
            role: mablApi_1.ChatMessageRoleEnum.User,
            conversationId: agentMessage.conversationId,
            message: response,
            timestamp: Date.now(),
        };
        const conversation = this.state
            .getConversations()
            .find((c) => c.id === userMessage.conversationId);
        if (conversation) {
            conversation.chatHistory.push(userMessage);
        }
        return userMessage;
    }
    promptUser(question) {
        return new Promise((resolve) => {
            this.rl.question(question, (answer) => {
                resolve(answer);
            });
        });
    }
    deleteSteps(stepsToDelete) {
        const actualDeleted = this.state.deleteLastSteps(stepsToDelete);
        if (this.verbose) {
            console.log(`Deleted ${actualDeleted} steps (requested ${stepsToDelete})`);
        }
        return actualDeleted;
    }
    deleteStepAtPath(path) {
        const error = this.state.deleteStepAtPath(path);
        if (!error && this.verbose) {
            console.log(`Deleted step at path [${path.join(', ')}]`);
        }
        return error;
    }
    deleteStepsInRange(startPath, endPath) {
        const error = this.state.deleteStepsInRange(startPath, endPath);
        if (!error && this.verbose) {
            console.log(`Deleted steps in range [${startPath.join(', ')}] to [${endPath.join(', ')}]`);
        }
        return error;
    }
    async insertFlowAndReplay(flow, flowParameterOverrides, iterationConfig) {
        if (flow.invariant_id) {
            this.flowCache.setFlow(flow.invariant_id, flow);
        }
        console.log(`Executing flow: ${flow.description ?? 'Unknown'}`);
        const flowConfiguration = flowParameterOverrides && Object.keys(flowParameterOverrides).length > 0
            ? { parameters: flowParameterOverrides }
            : undefined;
        if (flow.invariant_id) {
            const flowStep = new ExecuteFlowStep_1.default({
                isError: false,
                flowInvariantId: flow.invariant_id,
                flow,
                flowConfiguration,
            });
            if (iterationConfig) {
                flowStep.iterationConfig = iterationConfig;
            }
            await this.dispatchSteps([flowStep]);
        }
        this.state.setLastFlowFailure(undefined);
        try {
            const { failedSteps, totalSteps } = await this.executeFlowSteps(flow, flowConfiguration);
            const allPassed = failedSteps.length === 0;
            this.state.setReplayResponse({
                status: allPassed ? mablscript_step_types_1.StepStatus.Passed : mablscript_step_types_1.StepStatus.Failed,
                message: allPassed
                    ? 'Flow executed successfully'
                    : formatFlowFailureMessage(flow, failedSteps, totalSteps),
                level: allPassed ? 'success' : 'error',
            });
            if (!allPassed) {
                const failedStepDetails = failedSteps.map((failure) => ({
                    stepPath: failure.stepPath,
                    stepDescription: failure.stepDescription,
                    reason: failure.reason,
                    screenshot: failure.screenshotBytes?.toString('base64'),
                }));
                this.state.setLastFlowFailure({ failedSteps: failedStepDetails });
            }
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : 'Unknown error';
            console.error(`  Flow execution error: ${errorMessage}`);
            this.state.setReplayResponse({
                status: mablscript_step_types_1.StepStatus.Failed,
                message: `Flow execution error: ${errorMessage}`,
                level: 'error',
            });
        }
    }
    async executeFlowSteps(flow, flowConfiguration, parentPath = []) {
        const variables = this.stepExecutor.getVariables();
        if (flow.parameters?.length) {
            variables.loadFlowParametersFromFlowContext(flow, flowConfiguration);
        }
        const mablSteps = (0, importer_1.parseMablScriptIntoSteps)(flow);
        if (this.verbose) {
            console.log(`  Flow has ${mablSteps.length} steps`);
        }
        const failedSteps = [];
        let totalSteps = 0;
        for (let index = 0; index < mablSteps.length; index++) {
            const mablStep = mablSteps[index];
            const stepNumber = index + 1;
            const stepPath = [...parentPath, String(stepNumber)];
            if (mablStep instanceof EvaluateFlowStep_1.EvaluateFlowStep) {
                const nestedFlowId = mablStep.flowDescriptor.invariant_id;
                if (this.verbose) {
                    console.log(`  Nested flow: ${nestedFlowId}`);
                }
                const nestedFlow = await this.flowCache.getFlow(nestedFlowId);
                if (!nestedFlow) {
                    console.error(`  Failed to load nested flow ${nestedFlowId}`);
                    totalSteps++;
                    const humanLabel = mablStep.getDescription();
                    failedSteps.push({
                        stepPath: stepPath.join('.'),
                        stepDescription: humanLabel
                            ? `Nested flow "${humanLabel}"`
                            : `Nested flow ${nestedFlowId}`,
                        reason: `Could not load nested flow ${nestedFlowId} — the flow may have been deleted or is unavailable in this workspace`,
                    });
                    continue;
                }
                if (this.verbose) {
                    console.log(`  Executing nested flow: ${nestedFlow.description ?? nestedFlowId}`);
                }
                const nestedResult = await this.executeFlowSteps(nestedFlow, undefined, stepPath);
                failedSteps.push(...nestedResult.failedSteps);
                totalSteps += nestedResult.totalSteps;
                continue;
            }
            const result = await this.stepExecutor.executeMablStep(mablStep);
            const stepPassed = (0, mablscript_step_types_1.isStepStatusSuccessful)(result.status);
            totalSteps++;
            if (this.verbose) {
                console.log(`  Step ${mablStep.getStepName()}: ${stepPassed ? 'passed' : 'failed'}`);
            }
            const isConditional = (0, assert_utils_1.isIfConditionStep)(mablStep) || (0, assert_utils_1.isElseIfConditionStep)(mablStep);
            if (!stepPassed && !isConditional) {
                const screenshotBytes = await this.captureFailureScreenshot(failedSteps);
                failedSteps.push({
                    stepPath: stepPath.join('.'),
                    stepDescription: describeFailedStep(mablStep, result),
                    reason: describeFailureReason(result),
                    screenshotBytes,
                });
            }
        }
        if (flow.parameters?.length) {
            variables.removeFlowParameters();
        }
        return { failedSteps, totalSteps };
    }
    async captureFailureScreenshot(failuresSoFar) {
        if (failuresSoFar.length >= MAX_FAILED_STEP_SCREENSHOTS) {
            return undefined;
        }
        try {
            return await this.trainingContext.takeScreenshot();
        }
        catch (error) {
            if (this.verbose) {
                console.log(`  Failed to capture screenshot for failed step: ${error instanceof Error ? error.message : 'unknown error'}`);
            }
            return undefined;
        }
    }
    recordStep(_eventMessage, _contextInfo, _index, _synthetic, _replayOnRecord, _descriptionOverride) {
        if (this.verbose) {
            console.log('Recording step from event message');
        }
        return Promise.resolve();
    }
    async dispatchSteps(steps, options) {
        const cursor = this.state.getNextStepIndex();
        if (cursor.length > 1 && !options?.skipActiveGroupInsertion) {
            const containerPath = cursor.slice(0, -1);
            const stepGroup = this.getStepAtPathInState(containerPath);
            if ((0, mablscript_step_types_1.isStepGroupStep)(stepGroup)) {
                const insertIdx = cursor[cursor.length - 1];
                stepGroup.stepsInGroup.splice(insertIdx, 0, ...steps);
                this.state.setNextStepIndex([
                    ...containerPath,
                    insertIdx + steps.length,
                ]);
            }
            else {
                this.state.addSteps(steps);
            }
        }
        else if (options?.index) {
            this.state.insertStepsAtCursor(steps);
        }
        else {
            this.state.addSteps(steps);
        }
        if (options?.description) {
            console.log(`  ${options.description}`);
        }
        if (options?.skipExecution) {
            return;
        }
        for (const step of steps) {
            if ((0, mablscript_step_types_1.isStepGroupStep)(step) || (0, mablscript_step_types_1.isStepWithReusableFlow)(step)) {
                if (this.verbose) {
                    console.log(`  Step ${step.stepType}: container step, skipping execution`);
                }
                continue;
            }
            this.resetReplayResponse();
            const result = await this.runSingleStep(step);
            if (this.verbose) {
                console.log(`  Step ${step.stepType} executed: ${result.success ? 'success' : 'failed'}`);
            }
            this.syncVariablesFromExecutionContext();
            this.state.setReplayResponse({
                status: result.success ? mablscript_step_types_1.StepStatus.Passed : mablscript_step_types_1.StepStatus.Failed,
                message: result.message,
                level: result.level,
            });
        }
        if (options?.skipActiveGroupInsertion && steps.length > 0) {
            const lastStep = steps[steps.length - 1];
            if ((0, mablscript_step_types_1.isStepGroupStep)(lastStep) && lastStep.stepsInGroup.length === 0) {
                this.moveCursorIntoGroup();
            }
        }
    }
    syncVariablesFromExecutionContext() {
        const executionVars = this.stepExecutor.getVariables();
        const allVars = executionVars.getAll();
        for (const [name, variable] of Object.entries(allVars)) {
            if (variable && name.startsWith('user.')) {
                this.state.setVariable(name, variable);
            }
        }
    }
    saveVariable(variable) {
        if (variable.name) {
            this.state.setVariable(variable.name, variable);
            if (this.verbose) {
                console.log(`Saved variable: ${variable.humanizedName ?? variable.name}`);
            }
        }
    }
    setReplayResponse(response) {
        this.state.setReplayResponse(response);
        if (response.level === 'error' && response.message) {
            console.error(`Error: ${response.message}`);
        }
    }
    resetReplayResponse() {
        this.state.setReplayResponse({ status: mablscript_step_types_1.StepStatus.Passed, message: '' });
    }
    trackAnalyticsEvent(eventName, properties) {
        if (this.verbose) {
            console.log(`Analytics: ${eventName}`, properties);
        }
    }
    beforeFinds() {
        this.state.setLastToolsExecuted(undefined);
        if (this.verbose) {
            console.log('Starting element finds...');
        }
    }
    afterFinds() {
        if (this.verbose) {
            console.log('Element finds complete');
        }
    }
    onCleanup() {
        this.state.setLastToolsExecuted(undefined);
        if (this.verbose) {
            console.log('Cleaning up...');
        }
    }
    setLastVisitUrl(url) {
        this.state.setLastVisitUrl(url);
    }
    async sendMessageToAllFrames(message) {
        await this.trainingContext.sendMessageToAllFramesOfActiveTabs(message);
    }
    uploadFiles(_orgId, _eventMessage) {
        console.warn('File upload not yet implemented for CLI');
        return Promise.resolve([]);
    }
    downloadUploadedFile(file) {
        const fileRef = file;
        return this.stepExecutor.downloadFileUploadsAndSetReference([fileRef]);
    }
    registerSnippet(snippet) {
        this.stepExecutor.setSnippet(snippet);
    }
    async runSingleStep(step, _options) {
        try {
            const mablscript = step.generateMablscript();
            if (this.verbose) {
                console.log(`  Executing step: ${step.stepType}`);
            }
            const result = await this.stepExecutor.executeSingleStep(mablscript);
            const success = result.status === mablscript_step_types_1.StepStatus.Passed;
            const message = result.logInfo ?? (success ? 'Step executed' : 'Step failed');
            this.state.setReplayResponse({
                status: success ? mablscript_step_types_1.StepStatus.Passed : mablscript_step_types_1.StepStatus.Failed,
                message,
            });
            return {
                success,
                level: success ? 'success' : 'error',
                message,
            };
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : 'Unknown error';
            if (this.verbose) {
                console.error(`  Step execution error: ${errorMessage}`);
            }
            return {
                success: false,
                level: 'error',
                message: errorMessage,
            };
        }
    }
    maybeSwitchTabAndFrame(newFrameId, newTabId) {
        const originalFrameId = this.trainingContext.getCurrentFrame();
        const originalTabId = this.trainingContext.getCurrentTab();
        if (newFrameId !== undefined) {
            this.trainingContext.setCurrentFrame(newFrameId);
        }
        if (newTabId !== undefined) {
            this.trainingContext.setCurrentTab(newTabId);
        }
        return { originalFrameId, originalTabId };
    }
    getRecentDownload() {
        return this.recentDownload;
    }
    registerDownloadListeners() {
        const browser = this.trainingContext.getBrowserSync();
        if (!browser) {
            return;
        }
        browser.on(browserLauncher_1.BrowserEvent.DownloadWillBegin, (event) => {
            this.trackedDownloads[event.guid] = {
                beginEvent: event,
                startTime: new Date().toISOString(),
            };
        });
        browser.on(browserLauncher_1.BrowserEvent.DownloadProgress, (event) => {
            const tracked = this.trackedDownloads[event.guid];
            if (!tracked || event.state !== 'completed') {
                return;
            }
            const fileName = tracked.beginEvent.suggestedFilename;
            const mimeType = mimeTypes.lookup(fileName) || '';
            this.recentDownload = {
                end_time: new Date().toISOString(),
                file_mime_type: mimeType,
                file_name: fileName,
                file_path_local: path.join(process.cwd(), 'downloads', fileName),
                file_size_bytes: event.totalBytes,
                final_url: tracked.beginEvent.url,
                id: event.guid,
                mod_time: Date.now(),
                referrer: '',
                start_time: tracked.startTime,
                state: 'complete',
            };
            delete this.trackedDownloads[event.guid];
        });
    }
}
exports.CliEventHandler = CliEventHandler;
