"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestObserver = exports.FlowObserver = exports.AfterObserver = exports.DeferredObserver = exports.Observer = exports.STEPLESS_ACTION_STEP_SOURCE_INDEX = exports.createCaptureApplicationStateExecutionIndex = exports.createTempFile = exports.isDeferredObservableAction = exports.isObservable = exports.ObserverPhase = exports.ObservationType = void 0;
exports.captureApplicationState = captureApplicationState;
exports.runtimeRecoverySummarySessionCreated = runtimeRecoverySummarySessionCreated;
exports.runtimeRecoverySummary = runtimeRecoverySummary;
exports.runtimeRecoverySummaryUnreachable = runtimeRecoverySummaryUnreachable;
const execution_1 = require("../execution");
const messaging_1 = require("../core/messaging/messaging");
const loggingProvider_1 = require("../providers/logging/loggingProvider");
const promises_1 = require("fs/promises");
const ObservationDispatcher_1 = require("./ObservationDispatcher");
Object.defineProperty(exports, "ObservationType", { enumerable: true, get: function () { return ObservationDispatcher_1.ObservationType; } });
Object.defineProperty(exports, "ObserverPhase", { enumerable: true, get: function () { return ObservationDispatcher_1.ObserverPhase; } });
Object.defineProperty(exports, "isObservable", { enumerable: true, get: function () { return ObservationDispatcher_1.isObservable; } });
Object.defineProperty(exports, "isDeferredObservableAction", { enumerable: true, get: function () { return ObservationDispatcher_1.isDeferredObservableAction; } });
Object.defineProperty(exports, "createTempFile", { enumerable: true, get: function () { return ObservationDispatcher_1.createTempFile; } });
Object.defineProperty(exports, "createCaptureApplicationStateExecutionIndex", { enumerable: true, get: function () { return ObservationDispatcher_1.createCaptureApplicationStateExecutionIndex; } });
Object.defineProperty(exports, "STEPLESS_ACTION_STEP_SOURCE_INDEX", { enumerable: true, get: function () { return ObservationDispatcher_1.STEPLESS_ACTION_STEP_SOURCE_INDEX; } });
const syntheticActionCodes_1 = require("./syntheticActionCodes");
const agentNames_1 = require("./agentNames");
const mablApi_1 = require("../mablApi");
class Observer {
    constructor(observationType, observerPhase, dispatcher, skipFirstObservation = true) {
        this.observationType = observationType;
        this.observerPhase = observerPhase;
        this.dispatcher = dispatcher;
        this.skipFirstObservation = skipFirstObservation;
        this.messagesToSend = [];
        this.isCloudRun = false;
        this.skipFirstObservation = skipFirstObservation;
    }
    async observe(options, observeFunction) {
        if (!(0, execution_1.isBrowserTestExecutionContext)(options.executionContext) &&
            !(0, execution_1.isMobileExecutionContext)(options.executionContext)) {
            loggingProvider_1.logger.error('Observers are only supported for browser and mobile test execution contexts');
            return;
        }
        const executionContext = options.executionContext;
        const flowSourceIndex = options.executionIndex.flowSourceIndex;
        const stepSourceIndex = options.executionIndex.stepSourceIndex;
        if (flowSourceIndex === undefined || stepSourceIndex === undefined) {
            const errorMessage = `Either flowSourceIndex ${flowSourceIndex} or stepSourceIndex ${stepSourceIndex} is undefined for ${this.observationType}`;
            const error = new Error(errorMessage);
            loggingProvider_1.logger.error(`${error}`);
            return;
        }
        const isFlowObservation = this.observerPhase === ObservationDispatcher_1.ObserverPhase.FLOW;
        const isExecutionIndexObservable = (0, ObservationDispatcher_1.isObservable)(options.executionIndex);
        loggingProvider_1.logger.debug(`${this.observationType}: observerPhase=${this.observerPhase}, isFlowObservation=${isFlowObservation}, isExecutionIndexObservable=${isExecutionIndexObservable}, executionIndex=${JSON.stringify(options.executionIndex, undefined, 0)}, lastObservationExecutionIndex=${JSON.stringify(this.lastObservationExecutionIndex, undefined, 0)}`, { isFlowObservation, isExecutionIndexObservable });
        if (!isExecutionIndexObservable && !isFlowObservation) {
            return;
        }
        else if (this.skipFirstObservation &&
            this.lastObservationExecutionIndex === undefined &&
            this.observerPhase === ObservationDispatcher_1.ObserverPhase.DEFERRED &&
            !(0, ObservationDispatcher_1.isDeferredObservableAction)(options.executionIndex.actionName) &&
            !isFlowObservation) {
            loggingProvider_1.logger.warn('Skipping first observation...');
            this.lastObservationExecutionIndex = options.executionIndex;
            return;
        }
        const startTimeMilliseconds = Date.now();
        const payload = {
            success: false,
            startTimeMilliseconds,
            endTimeMilliseconds: startTimeMilliseconds,
            lastObservationExecutionIndex: this.lastObservationExecutionIndex,
        };
        let result;
        try {
            result = await observeFunction({
                executionContext,
                executionIndex: options.executionIndex,
                properties: options.properties,
                action: options.action,
            });
            if (result.filesByObservationType) {
                payload.fileNameByObservationType = Object.entries(result.filesByObservationType).reduce((acc, curr) => {
                    acc[curr[0]] = curr[1].uri;
                    return acc;
                }, {});
            }
            payload.keepArtifactFiles = result.keepArtifactFiles;
            payload.success = true;
        }
        catch (e) {
            const error = e;
            loggingProvider_1.logger.error(`Observer.observe failure: ${error}`);
            this.dispatcher.onObservationError(error);
        }
        finally {
            this.isCloudRun = executionContext.testContext.isCloudRun ?? false;
            payload.endTimeMilliseconds = Date.now();
            payload.action = options.action;
            payload.observationProperties = result?.properties ?? {};
            payload.times = result?.times ?? [
                {
                    observationName: this.observationType,
                    waitTimeMs: payload.endTimeMilliseconds - payload.startTimeMilliseconds,
                },
            ];
            const previousStepRunId = this.lastObservationExecutionIndex?.stepRunId;
            this.messagesToSend.push(() => {
                this.dispatcher.onObservationComplete(this.observationType, options.executionIndex, payload, executionContext instanceof execution_1.BrowserTestExecutionContext
                    ? executionContext.getCurrentUrl()
                    : undefined);
                this.lastObservationExecutionIndex = options.executionIndex;
                const stepRunId = this.observerPhase === ObservationDispatcher_1.ObserverPhase.DEFERRED
                    ? previousStepRunId ?? options.executionIndex.stepRunId
                    : options.executionIndex.stepRunId;
                if (stepRunId) {
                    executionContext.addObservation(stepRunId, {
                        observationType: this.observationType,
                        success: payload.success,
                        startTimeMilliseconds: payload.startTimeMilliseconds,
                        endTimeMilliseconds: payload.endTimeMilliseconds,
                        filesByObservationType: result?.filesByObservationType,
                        properties: payload.observationProperties,
                        times: payload.times,
                    });
                }
            });
            if (this.isCloudRun) {
                if (result?.times) {
                    result.times.forEach((time) => {
                        loggingProvider_1.logger.info(`Took ${time.waitTimeMs}ms to observe ${time.observationName}`);
                    });
                }
                else {
                    loggingProvider_1.logger.info(`Took ${payload.endTimeMilliseconds - payload.startTimeMilliseconds}ms to observe ${this.observationType} calculated`);
                }
            }
        }
    }
    sendObservationMessage() {
        if (this.messagesToSend.length > 0) {
            const lastMessage = this.messagesToSend.pop();
            this.messagesToSend = [];
            if (lastMessage) {
                lastMessage();
            }
        }
        else if (this.isCloudRun) {
            loggingProvider_1.logger.info(`No messages to send for ${this.observationType}`);
        }
    }
    async writeTempFile(filename, data) {
        let tmpFile;
        try {
            tmpFile = await (0, ObservationDispatcher_1.createTempFile)(filename);
            loggingProvider_1.logger.debug(`Writing ${this.observationType} observation artifact to temp file [${tmpFile}]`);
            await (0, promises_1.writeFile)(tmpFile, data);
            return tmpFile;
        }
        catch (error) {
            loggingProvider_1.logger.error(`Failed to write temp file [${tmpFile}] to container file system`);
            throw error;
        }
    }
    observeDeferred(_options) {
        return Promise.resolve();
    }
    observeAfter(_options) {
        return Promise.resolve();
    }
    observeFlow(_options) {
        return Promise.resolve();
    }
    observeTest(_options) {
        return Promise.resolve();
    }
    onPageCreated(_page, _pageId) {
        return Promise.resolve();
    }
    onPageDestroyed(_page, _pageId) {
        return Promise.resolve();
    }
}
exports.Observer = Observer;
class DeferredObserver extends Observer {
    constructor(observationType, dispatcher) {
        super(observationType, ObservationDispatcher_1.ObserverPhase.DEFERRED, dispatcher);
        this.observationType = observationType;
    }
    observeDeferred(options) {
        return this.observe(options, this.observeDeferredFunction);
    }
}
exports.DeferredObserver = DeferredObserver;
class AfterObserver extends Observer {
    constructor(observationType, dispatcher) {
        super(observationType, ObservationDispatcher_1.ObserverPhase.AFTER, dispatcher);
        this.observationType = observationType;
    }
    observeAfter(options) {
        return this.observe(options, this.observeAfterFunction);
    }
}
exports.AfterObserver = AfterObserver;
class FlowObserver extends Observer {
    constructor(observationType, dispatcher) {
        super(observationType, ObservationDispatcher_1.ObserverPhase.FLOW, dispatcher);
        this.observationType = observationType;
    }
    observeFlow(options) {
        if (!options.executionIndex.stepSourceIndex) {
            options.executionIndex.stepSourceIndex =
                ObservationDispatcher_1.STEPLESS_ACTION_STEP_SOURCE_INDEX;
        }
        return this.observe(options, this.observeFlowFunction);
    }
}
exports.FlowObserver = FlowObserver;
class TestObserver extends Observer {
    constructor(observationType, dispatcher) {
        super(observationType, ObservationDispatcher_1.ObserverPhase.TEST, dispatcher);
        this.observationType = observationType;
    }
    observeTest(options) {
        return this.observe(options, this.observeTestFunction);
    }
}
exports.TestObserver = TestObserver;
async function captureApplicationState(observers, executionContext) {
    const executionIndex = (0, ObservationDispatcher_1.createCaptureApplicationStateExecutionIndex)(executionContext);
    const options = {
        executionContext,
        executionIndex,
        action: { actionCode: syntheticActionCodes_1.CAPTURE_APPLICATION_STATE_ACTION_CODE },
    };
    options.properties = {
        variables: new execution_1.ObserverController(executionContext, observers, 'Observer timeout').stringifyVariablesSummary(),
    };
    await Promise.allSettled([
        ...observers.map(async (observer) => new Promise(async (resolve, reject) => {
            try {
                await observer.observeDeferred(options);
                resolve({});
            }
            catch (e) {
                reject(e);
            }
            finally {
                observer.sendObservationMessage();
            }
        })),
        ...observers.map(async (observer) => new Promise(async (resolve, reject) => {
            try {
                await observer.observeAfter(options);
                resolve({});
            }
            catch (e) {
                reject(e);
            }
            finally {
                observer.sendObservationMessage();
            }
        })),
        ...observers.map(async (observer) => new Promise(async (resolve, reject) => {
            try {
                await observer.observeTest(options);
                resolve({});
            }
            catch (e) {
                reject(e);
            }
            finally {
                observer.sendObservationMessage();
            }
        })),
    ]);
    executionContext.log.info('Capture state of application after test', {
        executionPhase: messaging_1.ExecutionPhase.AFTER_ACTION,
        executionIndex,
        action: { actionCode: syntheticActionCodes_1.CAPTURE_APPLICATION_STATE_ACTION_CODE },
    });
}
const RUNTIME_RECOVERY_SUMMARY_REASONING_LOG_MAX_CHARS = 500;
function runtimeRecoverySummarySessionCreated(executionContext, agentSessionId) {
    executionContext.log.info(`${agentNames_1.RUNTIME_RECOVERY_SUMMARY_AGENT_NAME}: Session created: ${agentSessionId}`, {
        executionPhase: messaging_1.ExecutionPhase.AFTER_ACTION,
        executionIndex: (0, ObservationDispatcher_1.createRuntimeRecoverySummaryExecutionIndex)(executionContext),
        action: { actionCode: syntheticActionCodes_1.RUNTIME_RECOVERY_SUMMARY_ACTION_CODE },
        properties: {
            agent_session_id: agentSessionId,
        },
    });
}
const RUNTIME_RECOVERY_SUMMARY_UNREACHABLE_TEXT = `${agentNames_1.RUNTIME_RECOVERY_SUMMARY_AGENT_NAME} unreachable — verdict not available.`;
function runtimeRecoverySummary(executionContext, outcome) {
    const executionIndex = (0, ObservationDispatcher_1.createRuntimeRecoverySummaryExecutionIndex)(executionContext);
    const message = outcome.verdict === mablApi_1.RuntimeRecoverySummaryVerdict.VerdictEnum.Fail
        ? agentNames_1.TEST_FAILED_AFTER_REVIEW
        : agentNames_1.TEST_PASSED_AFTER_REVIEW;
    let truncatedReasoning = outcome.reasoning;
    if (outcome.reasoning.length > RUNTIME_RECOVERY_SUMMARY_REASONING_LOG_MAX_CHARS) {
        const cut = outcome.reasoning.lastIndexOf(' ', RUNTIME_RECOVERY_SUMMARY_REASONING_LOG_MAX_CHARS);
        const breakAt = cut > 0 ? cut : RUNTIME_RECOVERY_SUMMARY_REASONING_LOG_MAX_CHARS;
        truncatedReasoning = `${outcome.reasoning.slice(0, breakAt)}…`;
    }
    const logProperties = {
        executionPhase: messaging_1.ExecutionPhase.AFTER_ACTION,
        executionIndex,
        action: { actionCode: syntheticActionCodes_1.RUNTIME_RECOVERY_SUMMARY_ACTION_CODE },
        properties: {
            agent_session_id: outcome.agentSessionId,
            verdict: outcome.verdict,
            advisory: String(outcome.advisory),
            recovery_count: String(outcome.recoveryCount),
            reasoning_summary: truncatedReasoning,
            summary: outcome.summary,
            ...(outcome.insight
                ? {
                    insight_category: outcome.insight.category,
                    insight_text: outcome.insight.text,
                }
                : {}),
        },
    };
    executionContext.log.info(message, logProperties);
}
function runtimeRecoverySummaryUnreachable(executionContext, agentSessionId, reason) {
    const message = reason
        ? `${RUNTIME_RECOVERY_SUMMARY_UNREACHABLE_TEXT} ${reason}`
        : RUNTIME_RECOVERY_SUMMARY_UNREACHABLE_TEXT;
    executionContext.log.info(message, {
        executionPhase: messaging_1.ExecutionPhase.AFTER_ACTION,
        executionIndex: (0, ObservationDispatcher_1.createRuntimeRecoverySummaryExecutionIndex)(executionContext),
        action: { actionCode: syntheticActionCodes_1.RUNTIME_RECOVERY_SUMMARY_ACTION_CODE },
        properties: {
            ...(agentSessionId !== undefined
                ? { agent_session_id: agentSessionId }
                : {}),
            reasoning_summary: message,
        },
    });
}
