"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.runCliTrainer = runCliTrainer;
const readline = __importStar(require("readline"));
const fs = __importStar(require("fs"));
const inquirer = __importStar(require("inquirer"));
const browserTypes_1 = require("../../core/browserTypes");
const execution_1 = require("../../execution");
const createTrainingTestContext_1 = require("../sharedUtils/createTrainingTestContext");
const CliTestGenerationState_1 = require("./CliTestGenerationState");
const CliTrainingContext_1 = require("./CliTrainingContext");
const CliStateProvider_1 = require("./CliStateProvider");
const CliEventHandler_1 = require("./CliEventHandler");
const CliRecordingHandler_1 = require("./CliRecordingHandler");
const CliTrainerDisplay_1 = require("./CliTrainerDisplay");
const FlowCache_1 = require("./FlowCache");
const SnippetCache_1 = require("./SnippetCache");
const ContentJsWatcher_1 = require("./ContentJsWatcher");
const saveGeneratedTest_1 = require("../generation/saveGeneratedTest");
const constants_1 = require("../../core/constants");
const applicationUtil_1 = require("../../util/applicationUtil");
const ViewportStep_1 = __importDefault(require("../sharedUtils/mablscript/canonical/ViewportStep"));
const VisitUrlStep_1 = __importDefault(require("../sharedUtils/mablscript/canonical/VisitUrlStep"));
const chalk = require('chalk');
class TrainerLogger {
    constructor(logFile) {
        this.originalConsoleLog = console.log;
        this.originalConsoleDebug = console.debug;
        this.originalConsoleError = console.error;
        this.originalConsoleWarn = console.warn;
        if (logFile) {
            this.logStream = fs.createWriteStream(logFile, { flags: 'w' });
            this.logStream.write(`=== CLI Trainer Log - ${new Date().toISOString()} ===\n\n`);
        }
    }
    redirectToFile() {
        if (!this.logStream) {
            return;
        }
        const stream = this.logStream;
        const formatArgs = (...args) => args
            .map((arg) => typeof arg === 'object'
            ? JSON.stringify(arg, undefined, 2)
            : String(arg))
            .join(' ') + '\n';
        console.log = (...args) => stream.write(formatArgs('[LOG]', ...args));
        console.debug = (...args) => stream.write(formatArgs('[DEBUG]', ...args));
        console.error = (...args) => stream.write(formatArgs('[ERROR]', ...args));
        console.warn = (...args) => stream.write(formatArgs('[WARN]', ...args));
    }
    restoreConsole() {
        console.log = this.originalConsoleLog;
        console.debug = this.originalConsoleDebug;
        console.error = this.originalConsoleError;
        console.warn = this.originalConsoleWarn;
    }
    terminal(...args) {
        this.originalConsoleLog(...args);
    }
    file(...args) {
        if (this.logStream) {
            const message = args
                .map((arg) => typeof arg === 'object'
                ? JSON.stringify(arg, undefined, 2)
                : String(arg))
                .join(' ') + '\n';
            this.logStream.write(message);
        }
    }
    hasLogFile() {
        return this.logStream !== undefined;
    }
    close() {
        if (this.logStream) {
            this.logStream.write(`\n=== Log ended - ${new Date().toISOString()} ===\n`);
            this.logStream.end();
            this.logStream = undefined;
        }
        this.restoreConsole();
    }
}
var PostTrainingAction;
(function (PostTrainingAction) {
    PostTrainingAction["Discard"] = "Discard without saving";
    PostTrainingAction["Save"] = "Save the test";
})(PostTrainingAction || (PostTrainingAction = {}));
async function runCliTrainer(options) {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    });
    const logger = new TrainerLogger(options.logFile);
    let browser;
    let stepExecutor;
    let contentWatcher;
    try {
        printStartupInfo(options, logger);
        logger.redirectToFile();
        const { browser: b, page, executor, } = await createBrowserAndNavigate(options, logger);
        browser = b;
        stepExecutor = executor;
        const { state, trainingContext, stateProvider, eventHandler } = await createTrainingContext(options, page, browser, stepExecutor, rl);
        recordInitialSteps(state, options);
        const display = new CliTrainerDisplay_1.CliTrainerDisplay({
            testName: options.testName,
            url: options.url,
            logger,
        });
        const recordingHandler = new CliRecordingHandler_1.CliRecordingHandler({
            stateProvider,
            eventHandler,
            state,
            onStepRecorded: (_step, humanized, stepIndex) => {
                display.addStep(stepIndex, humanized);
                display.setStatus('recording');
            },
        });
        configureTrainingContextForRecording(trainingContext, recordingHandler);
        display.initialize();
        display.setStatus('waiting', 'Interact with the browser to record steps');
        const steps = state.getSteps();
        for (let i = 0; i < steps.length; i++) {
            const humanized = recordingHandler.getHumanizedSteps()[i];
            display.addStep(i + 1, humanized);
        }
        if (options.watch) {
            logger.terminal('');
            contentWatcher = new ContentJsWatcher_1.ContentJsWatcher({
                onRebuilt: async () => {
                    await trainingContext.reinjectContentScript();
                    display.render();
                },
                log: (...args) => logger.terminal(...args),
            });
            contentWatcher.start();
        }
        logger.terminal('');
        await display.waitForEnter(rl);
        recordingHandler.stopRecording();
        display.setStatus('paused', 'Recording stopped');
        const humanizedSteps = recordingHandler.getHumanizedSteps();
        const rawSteps = recordingHandler.getSteps();
        display.printSummary();
        logger.restoreConsole();
        const trainingSessionId = state.getTrainingSessionId();
        let saveResult;
        if (options.autoSave) {
            saveResult = await saveTest(options, rawSteps, humanizedSteps, logger, trainingSessionId);
        }
        else {
            const action = await promptPostTrainingAction();
            if (action === PostTrainingAction.Save) {
                saveResult = await saveTest(options, rawSteps, humanizedSteps, logger, trainingSessionId);
            }
            else {
                logger.terminal(chalk.yellow('Test discarded without saving.'));
            }
        }
        return {
            success: true,
            steps: humanizedSteps,
            rawSteps,
            trainingSessionId: state.getTrainingSessionId(),
            saveResult,
        };
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : 'Unknown error';
        logger.restoreConsole();
        logger.terminal(chalk.red(`Error: ${errorMessage}`));
        logger.file('Error:', errorMessage);
        return {
            success: false,
            steps: [],
            trainingSessionId: '',
            error: errorMessage,
        };
    }
    finally {
        if (contentWatcher) {
            contentWatcher.stop();
        }
        rl.close();
        logger.close();
        if (browser) {
            await browser.close();
        }
    }
}
function printStartupInfo(options, logger) {
    logger.terminal(chalk.bold.cyan('Starting CLI Trainer...'));
    logger.terminal(chalk.gray('  URL:'), chalk.white(options.url));
    logger.terminal(chalk.gray('  Test Name:'), chalk.white(options.testName));
    if (options.logFile) {
        logger.terminal(chalk.gray('  Log File:'), chalk.white(options.logFile));
    }
    if (options.watch) {
        logger.terminal(chalk.gray('  Watch Mode:'), chalk.green('enabled'));
    }
    logger.terminal();
}
async function createBrowserAndNavigate(options, logger) {
    const { testContext } = await (0, createTrainingTestContext_1.createTrainingTestContext)({
        workspaceId: options.workspaceId,
        applicationId: options.applicationId,
        url: options.url,
        apiClient: options.apiClient,
    });
    const browserConfig = {
        height: options.height ?? 900,
        width: options.width ?? 1280,
        headmode: options.headless ? execution_1.Headmode.Headless : execution_1.Headmode.Headful,
        browserName: 'chrome',
        browserType: browserTypes_1.BrowserType.Chrome,
        disableFocusEmulation: true,
        minimizeScrollIntoView: true,
        visitUrlTimeout: 5000,
        ignoreCertificateErrors: true,
    };
    const executor = new execution_1.BrowserStepExecutor(testContext, browserConfig, options.apiClient, false);
    logger.file('Launching browser...');
    const browser = await executor.initializeNewBrowser();
    if (!browser) {
        throw new Error('Failed to launch browser');
    }
    const executionContext = executor.getExecutionContext();
    const page = executionContext.pageFrameContext.getCurrentPage();
    if (!page) {
        throw new Error('Failed to get page');
    }
    logger.file(`Navigating to ${options.url}...`);
    await page.goto(options.url);
    return { browser, page, executor };
}
async function createTrainingContext(options, page, browser, stepExecutor, rl) {
    const flowCache = new FlowCache_1.FlowCache({
        apiClient: options.apiClient,
        workspaceId: options.workspaceId,
        branchName: constants_1.DefaultBranchName,
    });
    const snippetCache = new SnippetCache_1.SnippetCache({
        apiClient: options.apiClient,
        workspaceId: options.workspaceId,
    });
    const state = new CliTestGenerationState_1.CliTestGenerationState({
        testIntent: options.testDescription ?? '',
        url: options.url,
        workspaceId: options.workspaceId,
        applicationId: options.applicationId,
    });
    const { customTestIdAttributes, enterTextMode: applicationEnterTextMode } = await (0, applicationUtil_1.loadApplicationConfig)(options.apiClient, options.applicationId);
    const trainingContext = new CliTrainingContext_1.CliTrainingContext({
        page,
        browser,
        recordingEnabled: true,
        customTestIdAttributes,
    });
    await trainingContext.initialize();
    const stateProvider = new CliStateProvider_1.CliStateProvider(state, trainingContext, flowCache, undefined, snippetCache, applicationEnterTextMode);
    const eventHandler = new CliEventHandler_1.CliEventHandler({
        state,
        trainingContext,
        flowCache,
        stepExecutor,
        readlineInterface: rl,
        verbose: false,
    });
    return { state, trainingContext, stateProvider, eventHandler, flowCache };
}
function recordInitialSteps(state, options) {
    const viewportStep = new ViewportStep_1.default({
        width: options.width ?? 1280,
        height: options.height ?? 900,
    });
    state.addStep(viewportStep);
    const visitUrlStep = new VisitUrlStep_1.default(options.url);
    state.addStep(visitUrlStep);
}
function configureTrainingContextForRecording(trainingContext, recordingHandler) {
    trainingContext.setRecordingEnabled(true);
    trainingContext.setMessageHandler((message) => {
        const msg = message;
        if (recordingHandler.canHandle(msg)) {
            void recordingHandler.handleMessage(msg);
            return { acknowledged: true };
        }
        if (msg?.action === 'content_reloaded') {
            const frameElementInfo = msg.frameElementInfo;
            const frameId = msg.frameInfo?.frameId;
            if (frameId !== undefined && frameElementInfo?.frameSelector) {
                trainingContext.trackFrameInfo(frameId, frameElementInfo.frameSelector);
            }
            return { acknowledged: true };
        }
        return { acknowledged: true };
    });
}
async function promptPostTrainingAction() {
    const answer = await inquirer.prompt([
        {
            type: 'list',
            name: 'action',
            message: 'What do you want to do with the recorded test?',
            choices: Object.values(PostTrainingAction),
        },
    ]);
    return answer.action;
}
async function saveTest(options, rawSteps, humanizedSteps, logger, trainingSessionId) {
    logger.terminal(chalk.cyan('Saving test...'));
    logger.file('Saving test...');
    const result = await (0, saveGeneratedTest_1.saveGeneratedTest)({
        apiClient: options.apiClient,
        workspaceId: options.workspaceId,
        applicationId: options.applicationId,
        url: options.url,
        testName: options.testName,
        testDescription: options.testDescription ?? '',
        rawSteps,
        stepDescriptions: humanizedSteps,
        trainingSessionId,
    });
    if (result.success) {
        logger.terminal(chalk.green('✓ Test saved successfully!'));
        if (result.testId) {
            logger.terminal(chalk.gray('  Test ID:'), chalk.white(result.testId));
        }
        if (result.testUrl) {
            logger.terminal(chalk.gray('  View:'), chalk.cyan(result.testUrl));
        }
        logger.file('Test saved successfully:', result.testId);
    }
    else {
        logger.terminal(chalk.red('✗ Failed to save test:'), result.error);
        logger.file('Failed to save test:', result.error);
    }
    return result;
}
