"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScreenshotObserver = exports.BOUNDING_BOX = void 0;
const Observer_1 = require("./Observer");
const loggingProvider_1 = require("../providers/logging/loggingProvider");
const mablApi_1 = require("../mablApi");
const SCREENSHOT_OBSERVATION_ARTIFACT_NAME = 'screenshot.png';
const FULL_SCREENSHOT_OBSERVATION_ARTIFACT_NAME = 'full_screenshot.png';
const BBOX_OBSERVATION_ARTIFACT_NAME = 'bbox_capture.json';
exports.BOUNDING_BOX = 'boundingBoxes';
class ScreenshotObserver extends Observer_1.DeferredObserver {
    constructor(bboxCaptureEnabled, minBoundingBoxArea, dispatcher, isCloudRun = false) {
        super(Observer_1.ObservationType.SCREENSHOT, dispatcher);
        this.bboxCaptureEnabled = bboxCaptureEnabled;
        this.minBoundingBoxArea = minBoundingBoxArea;
        this.observeDeferredFunction = async (observerContext) => {
            const { executionContext } = observerContext;
            const action = observerContext.action;
            const observationResults = [];
            const properties = action?.properties;
            const boundingBox = properties && 'boundingBox' in properties && properties.boundingBox
                ? JSON.parse(properties.boundingBox)
                : undefined;
            const isElementScope = properties && 'observationScope' in properties
                ? properties.observationScope === 'element'
                : false;
            const shouldCropImage = isElementScope && boundingBox;
            let takeScreenshot = true;
            if (isElementScope && !shouldCropImage) {
                executionContext.log.debug('Element screenshot was requested but no bounding box was provided');
            }
            else if (isElementScope &&
                (boundingBox?.width === 0 || boundingBox?.height === 0)) {
                executionContext.log.info('Unable to take element screenshot. Element is not visible');
                takeScreenshot = false;
            }
            if (takeScreenshot) {
                const screenshotBoundingBox = shouldCropImage ? boundingBox : undefined;
                const [screenshotOutcome, bboxCaptureResult] = await Promise.allSettled([
                    this.takeScreenshot(observerContext, screenshotBoundingBox),
                    this.getBoundingBox(observerContext),
                ]);
                let fullImageBuffer;
                if (screenshotOutcome.status === 'fulfilled') {
                    observationResults.push(...screenshotOutcome.value.results);
                    fullImageBuffer = screenshotOutcome.value.fullImage;
                    if (bboxCaptureResult.status === 'fulfilled' &&
                        bboxCaptureResult.value) {
                        observationResults.push(bboxCaptureResult.value);
                    }
                }
                let filesByObservationType = {};
                let times = [];
                if (observationResults.length) {
                    filesByObservationType = observationResults.reduce((acc, result) => {
                        acc[result.observationName] = { uri: result.uri };
                        return acc;
                    }, {});
                    times = observationResults.map((result) => ({
                        observationName: result.observationName,
                        waitTimeMs: result.waitTimeMs,
                    }));
                    const elementBboxProperties = await this.computeElementBboxProperties(boundingBox, fullImageBuffer, observerContext);
                    const mode = executionContext.testContext.testRecoveryAgentMode;
                    const keepArtifactFiles = mode !== mablApi_1.TestRecoveryAgentModeEnum.Off;
                    return {
                        filesByObservationType,
                        times,
                        properties: elementBboxProperties,
                        keepArtifactFiles,
                    };
                }
            }
            return {};
        };
        this.isCloudRun = isCloudRun;
    }
    async getBoundingBox(observerContext) {
        const startTimeMsBBox = Date.now();
        const bboxResult = await this.captureBoundingBox(observerContext);
        if (!bboxResult) {
            return;
        }
        const bboxTempFile = await this.writeTempFile(BBOX_OBSERVATION_ARTIFACT_NAME, bboxResult);
        if (!bboxTempFile) {
            loggingProvider_1.logger.warn('BoundingBox observation artifact file not available!');
            return;
        }
        return {
            observationName: exports.BOUNDING_BOX,
            uri: bboxTempFile,
            waitTimeMs: Date.now() - startTimeMsBBox,
        };
    }
    async takeScreenshot(observerContext, screenshotBoundingBox) {
        const startPageScreenshot = Date.now();
        const fullImage = await this.captureScreenshot(observerContext);
        if (!fullImage) {
            loggingProvider_1.logger.warn('Screenshot was not captured');
            return { results: [] };
        }
        const results = [];
        const fullScreenshotTmpFile = await this.writeTempFile(FULL_SCREENSHOT_OBSERVATION_ARTIFACT_NAME, fullImage);
        if (fullScreenshotTmpFile) {
            results.push({
                observationName: Observer_1.ObservationType.FULL_SCREENSHOT,
                uri: fullScreenshotTmpFile,
                waitTimeMs: Date.now() - startPageScreenshot,
            });
        }
        let screenshotUri = fullScreenshotTmpFile;
        if (screenshotBoundingBox && this.isCloudRun) {
            try {
                const croppedImage = await this.cropImage(fullImage, screenshotBoundingBox, observerContext);
                const croppedTmpFile = await this.writeTempFile(SCREENSHOT_OBSERVATION_ARTIFACT_NAME, croppedImage);
                if (croppedTmpFile) {
                    screenshotUri = croppedTmpFile;
                }
            }
            catch (error) {
                loggingProvider_1.logger.warn(`Failed to crop screenshot: ${error}`);
            }
        }
        if (screenshotUri) {
            const screenshotTimeMs = Date.now() - startPageScreenshot;
            loggingProvider_1.logger.debug(`Screenshot [${screenshotUri}] took [${screenshotTimeMs}ms]`);
            results.push({
                observationName: Observer_1.ObservationType.SCREENSHOT,
                uri: screenshotUri,
                waitTimeMs: screenshotTimeMs,
            });
        }
        return { results, fullImage };
    }
    async computeElementBboxProperties(boundingBox, fullImageBuffer, observerContext) {
        if (!boundingBox || !fullImageBuffer || fullImageBuffer.length < 24) {
            return undefined;
        }
        try {
            const scaleFactor = await this.getScaleFactor(observerContext);
            const absX = boundingBox.x * scaleFactor;
            const absY = boundingBox.y * scaleFactor;
            const absWidth = boundingBox.width * scaleFactor;
            const absHeight = boundingBox.height * scaleFactor;
            const imageWidth = fullImageBuffer.readUInt32BE(16);
            const imageHeight = fullImageBuffer.readUInt32BE(20);
            return {
                element_bbox_x: absX,
                element_bbox_y: absY,
                element_bbox_width: absWidth,
                element_bbox_height: absHeight,
                element_bbox_x_pct: imageWidth > 0 ? absX / imageWidth : 0,
                element_bbox_y_pct: imageHeight > 0 ? absY / imageHeight : 0,
                element_bbox_width_pct: imageWidth > 0 ? absWidth / imageWidth : 0,
                element_bbox_height_pct: imageHeight > 0 ? absHeight / imageHeight : 0,
            };
        }
        catch (error) {
            loggingProvider_1.logger.warn(`Failed to compute element bounding box properties: ${error}`);
            return undefined;
        }
    }
    async cropImage(fullImage, boundingBox, context) {
        const scaleFactor = await this.getScaleFactor(context);
        let imagescript;
        try {
            imagescript = require('imagescript');
        }
        catch (error) {
            loggingProvider_1.logger.warn(`imagescript unavailable; returning full screenshot instead of cropping: ${error}`);
        }
        if (!imagescript) {
            return fullImage;
        }
        const image = await imagescript.Image.decode(fullImage);
        const imageWidth = image.width;
        const imageHeight = image.height;
        let { x, y, width, height } = boundingBox;
        x *= scaleFactor;
        y *= scaleFactor;
        width *= scaleFactor;
        height *= scaleFactor;
        if (x < 0 || y < 0 || x + width > imageWidth || y + height > imageHeight) {
            context.executionContext.log.debug(`Screen does not fully contain element, taking screenshot of page instead.  Element at x: ${x}, y: ${y}, width: ${width}, height: ${height}`);
            return fullImage;
        }
        try {
            image.crop(Math.floor(x), Math.floor(y), Math.floor(width), Math.floor(height));
            return Buffer.from(await image.encode());
        }
        catch (error) {
            loggingProvider_1.logger.warn(`Error while cropping screenshot: ${error}`);
            context.executionContext.log.info('Unable to crop screenshot, page view may not fully contain the element, providing full page screenshot');
            return fullImage;
        }
    }
}
exports.ScreenshotObserver = ScreenshotObserver;
