import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { getMidsceneRunSubDir } from "@midscene/shared/common";
import { MIDSCENE_REPORT_QUIET, globalConfigManager } from "@midscene/shared/env";
import { ifInBrowser, logMsg, uuid } from "@midscene/shared/utils";
import { generateDumpScriptTag, generateImageScriptTag, getBaseUrlFixScript } from "./dump/html-utils.mjs";
import { ScreenshotStore } from "./dump/screenshot-store.mjs";
import { ReportActionDump } from "./types.mjs";
import { appendFileSync, getReportTpl } from "./utils.mjs";
function _define_property(obj, key, value) {
    if (key in obj) Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
    });
    else obj[key] = value;
    return obj;
}
const nullReportGenerator = {
    onExecutionUpdate: ()=>{},
    flush: async ()=>{},
    finalize: async ()=>void 0,
    getReportPath: ()=>void 0
};
function assertReportGenerationOptions(opts) {
    if (false === opts.generateReport && true === opts.persistExecutionDump) throw new Error('persistExecutionDump cannot be true when generateReport is false');
}
class ReportGenerator {
    static create(reportFileName, opts) {
        assertReportGenerationOptions(opts);
        if (false === opts.generateReport) return nullReportGenerator;
        if (ifInBrowser) return nullReportGenerator;
        validateReportFileName(reportFileName);
        const reportRootDir = getMidsceneRunSubDir('report');
        const outputDir = join(reportRootDir, reportFileName);
        const reportPath = 'html-and-external-assets' === opts.outputFormat ? join(outputDir, 'index.html') : join(reportRootDir, ensureHtmlFileName(reportFileName));
        return new ReportGenerator({
            reportPath,
            screenshotMode: 'html-and-external-assets' === opts.outputFormat ? 'directory' : 'inline',
            persistExecutionDump: opts.persistExecutionDump,
            autoPrint: opts.autoPrintReportMsg
        });
    }
    onExecutionUpdate(execution, reportMeta, attributes) {
        this.lastExecution = execution;
        this.lastReportMeta = reportMeta;
        this.mergeReportAttributes(attributes);
        this.writeQueue = this.writeQueue.then(()=>{
            if (this.destroyed) return;
            this.doWriteExecution(execution, reportMeta);
        });
    }
    async flush() {
        await this.writeQueue;
    }
    async finalize() {
        if (this.lastExecution && this.lastReportMeta) this.onExecutionUpdate(this.lastExecution, this.lastReportMeta);
        await this.flush();
        this.destroyed = true;
        if (!this.initialized) return;
        this.printReportPath('finalized');
        return this.reportPath;
    }
    getReportPath() {
        return this.reportPath;
    }
    printReportPath(verb) {
        if (!this.autoPrint || !this.reportPath) return;
        if (globalConfigManager.getEnvConfigInBoolean(MIDSCENE_REPORT_QUIET)) return;
        'directory' === this.screenshotMode ? logMsg(`Midscene - report ${verb}: npx serve ${dirname(this.reportPath)}`) : logMsg(`Midscene - report ${verb}: ${this.reportPath}`);
    }
    doWriteExecution(execution, reportMeta) {
        const singleDump = this.wrapAsReportDump(execution, reportMeta);
        if ('inline' === this.screenshotMode) this.writeInlineExecution(execution, singleDump);
        else this.writeDirectoryExecution(execution, singleDump);
        if (this.shouldPersistExecutionDump) this.persistExecutionDumpToFile(execution, singleDump);
        if (!this.firstWriteDone) {
            this.firstWriteDone = true;
            this.printReportPath('generated');
        }
    }
    mergeReportAttributes(attributes) {
        if (!attributes) return;
        for (const [key, value] of Object.entries(attributes))if (null != value) {
            if ('data-group-id' !== key) this.reportAttributes[key] = String(value);
        }
    }
    hydrateStateFromExistingReport() {
        if (!existsSync(this.reportPath)) return;
        this.initialized = true;
        if (!this.shouldPersistExecutionDump) return;
        const reportDir = dirname(this.reportPath);
        const existingExecutionIndices = readdirSync(reportDir).map((name)=>/^(\d+)\.execution\.json$/.exec(name)?.[1]).filter((index)=>Boolean(index)).map((index)=>Number.parseInt(index, 10)).filter((index)=>Number.isFinite(index));
        if (existingExecutionIndices.length > 0) this.executionLogIndex = Math.max(...existingExecutionIndices);
    }
    getDumpScriptAttributes() {
        return {
            'data-group-id': this.reportStreamId,
            ...this.reportAttributes
        };
    }
    wrapAsReportDump(execution, reportMeta) {
        return new ReportActionDump({
            sdkVersion: reportMeta.sdkVersion,
            groupName: reportMeta.groupName,
            groupDescription: reportMeta.groupDescription,
            modelBriefs: reportMeta.modelBriefs,
            deviceType: reportMeta.deviceType,
            executions: [
                execution
            ]
        });
    }
    writeInlineExecution(execution, singleDump) {
        const dir = dirname(this.reportPath);
        if (!existsSync(dir)) mkdirSync(dir, {
            recursive: true
        });
        if (!this.initialized) {
            writeFileSync(this.reportPath, getReportTpl());
            this.initialized = true;
        }
        for (const screenshot of execution.collectScreenshots())this.screenshotStore.persist(screenshot);
        const serialized = singleDump.serialize();
        appendFileSync(this.reportPath, `\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`);
    }
    writeDirectoryExecution(execution, singleDump) {
        const dir = dirname(this.reportPath);
        if (!existsSync(dir)) mkdirSync(dir, {
            recursive: true
        });
        for (const screenshot of execution.collectScreenshots())this.screenshotStore.persist(screenshot);
        const serialized = singleDump.serialize();
        if (!this.initialized) {
            writeFileSync(this.reportPath, `${getReportTpl()}${getBaseUrlFixScript()}`);
            this.initialized = true;
        }
        appendFileSync(this.reportPath, `\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`);
    }
    getExecutionLogKey(execution) {
        if (!execution.id) throw new Error('ReportGenerator: execution.id is required for persisting execution dumps');
        return `id:${execution.id}`;
    }
    persistExecutionDumpToFile(execution, singleDump) {
        const dir = dirname(this.reportPath);
        if (!existsSync(dir)) mkdirSync(dir, {
            recursive: true
        });
        const executionLogKey = this.getExecutionLogKey(execution);
        let fileIndex = this.executionLogFileIndexByExecutionKey.get(executionLogKey);
        if (!fileIndex) {
            this.executionLogIndex += 1;
            fileIndex = this.executionLogIndex;
            this.executionLogFileIndexByExecutionKey.set(executionLogKey, fileIndex);
        }
        const fileName = `${fileIndex}.execution.json`;
        const filePath = join(dirname(this.reportPath), fileName);
        writeFileSync(filePath, singleDump.serialize(2), 'utf-8');
    }
    constructor(options){
        _define_property(this, "reportPath", void 0);
        _define_property(this, "screenshotMode", void 0);
        _define_property(this, "shouldPersistExecutionDump", void 0);
        _define_property(this, "autoPrint", void 0);
        _define_property(this, "firstWriteDone", false);
        _define_property(this, "executionLogIndex", 0);
        _define_property(this, "executionLogFileIndexByExecutionKey", new Map());
        _define_property(this, "reportStreamId", void 0);
        _define_property(this, "screenshotStore", void 0);
        _define_property(this, "initialized", false);
        _define_property(this, "lastExecution", void 0);
        _define_property(this, "lastReportMeta", void 0);
        _define_property(this, "reportAttributes", {});
        _define_property(this, "writeQueue", Promise.resolve());
        _define_property(this, "destroyed", false);
        this.reportPath = options.reportPath;
        this.screenshotMode = options.screenshotMode;
        this.shouldPersistExecutionDump = options.persistExecutionDump ?? false;
        this.autoPrint = options.autoPrint ?? true;
        this.reportStreamId = uuid();
        this.screenshotStore = new ScreenshotStore({
            mode: 'inline' === this.screenshotMode ? 'inline' : 'directory',
            reportPath: this.reportPath,
            screenshotsDir: join(dirname(this.reportPath), 'screenshots'),
            writeInlineImage: (id, base64)=>{
                appendFileSync(this.reportPath, `\n${generateImageScriptTag(id, base64)}`);
            },
            alsoWriteFileCopy: this.shouldPersistExecutionDump
        });
        this.hydrateStateFromExistingReport();
        this.printReportPath('will be generated at');
    }
}
function ensureHtmlFileName(reportFileName) {
    return reportFileName.endsWith('.html') ? reportFileName : `${reportFileName}.html`;
}
function validateReportFileName(reportFileName) {
    if (!reportFileName?.trim()) throw new Error('reportFileName must be a non-empty string');
    if (/[\\/]/.test(reportFileName)) throw new Error('reportFileName must not contain path separators (`/` or `\\\\`)');
    if (/[:*?"<>|]/.test(reportFileName)) throw new Error('reportFileName contains illegal filename characters: : * ? " < > |');
}
export { ReportGenerator, assertReportGenerationOptions, nullReportGenerator };

//# sourceMappingURL=report-generator.mjs.map