import { randomUUID } from 'node:crypto';
import dayjs from 'dayjs';
import { BrowserEvidenceService } from '../evidence/index.js';
import { BrowserPolicyService } from '../policy/index.js';
import { BrowserSnapshotService } from '../snapshots/index.js';
/**
 * Owns one browser run: driver lifecycle, policy checks, snapshots, and evidence.
 */
export class BrowserSessionService {
    config;
    driverFactory;
    driver;
    latestSnapshot;
    evidence;
    policy;
    constructor(config, driverFactory) {
        this.config = config;
        this.driverFactory = driverFactory;
        this.evidence = new BrowserEvidenceService(config.evidenceDir);
        this.policy = new BrowserPolicyService(config.policy);
    }
    async open(input) {
        const actionId = this.actionId('open');
        const timestamp = this.now();
        const decision = this.policy.evaluateNavigation(input.url);
        if (decision.status !== 'allowed') {
            await this.record({
                id: actionId,
                timestamp,
                action: 'open',
                status: decision.status,
                reason: decision.reason,
                detail: { url: input.url },
            });
            return {
                status: decision.status,
                actionId,
                timestamp,
                reason: decision.reason,
            };
        }
        const driver = await this.getDriver();
        const finalUrl = await driver.open(input.url);
        const finalDecision = this.policy.evaluateNavigation(finalUrl);
        await this.record({
            id: actionId,
            timestamp,
            action: 'open',
            status: finalDecision.status === 'allowed' ? 'completed' : finalDecision.status,
            url: finalUrl,
            reason: finalDecision.reason,
            detail: { requestedUrl: input.url, finalUrl },
        });
        return {
            status: finalDecision.status,
            actionId,
            timestamp,
            url: finalUrl,
            reason: finalDecision.reason,
            data: { finalUrl },
        };
    }
    async snapshot() {
        const actionId = this.actionId('snapshot');
        const timestamp = this.now();
        const driver = await this.getDriver();
        const snapshot = BrowserSnapshotService.create(await driver.snapshot({ maxElements: this.policy.maxElementsPerSnapshot() }));
        this.latestSnapshot = snapshot;
        const snapshotPath = await this.evidence.recordSnapshot(snapshot);
        await this.record({
            id: actionId,
            timestamp,
            action: 'snapshot',
            status: 'completed',
            url: snapshot.url,
            detail: { snapshotId: snapshot.id, snapshotPath, elementCount: snapshot.elements.length },
        });
        return {
            status: 'allowed',
            actionId,
            timestamp,
            url: snapshot.url,
            data: snapshot,
        };
    }
    async click(input) {
        const actionId = this.actionId('click');
        const timestamp = this.now();
        const element = this.latestSnapshot?.elements.find((candidate) => candidate.ref === input.ref);
        if (!element) {
            const reason = `Unknown browser snapshot ref: ${input.ref}`;
            await this.record({
                id: actionId,
                timestamp,
                action: 'click',
                status: 'blocked',
                reason,
                detail: { ref: input.ref },
            });
            return {
                status: 'blocked',
                actionId,
                timestamp,
                reason,
            };
        }
        const decision = this.policy.evaluateClick(element);
        if (decision.status !== 'allowed') {
            return await this.completePolicyDeniedClick({
                actionId,
                timestamp,
                ref: input.ref,
                element,
                decision,
                url: this.currentUrl(),
            });
        }
        let blockedNavigation;
        let finalUrl;
        try {
            finalUrl = await (await this.getDriver()).click(input.ref, {
                canNavigateTo: (url) => {
                    const navigationDecision = this.policy.evaluateNavigation(url);
                    if (navigationDecision.status === 'allowed') {
                        return true;
                    }
                    blockedNavigation = { url, decision: navigationDecision };
                    return false;
                },
            });
        }
        catch (error) {
            this.latestSnapshot = undefined;
            if (blockedNavigation) {
                return await this.completePolicyDeniedClick({
                    actionId,
                    timestamp,
                    ref: input.ref,
                    element,
                    decision: blockedNavigation.decision,
                    url: blockedNavigation.url,
                });
            }
            throw error;
        }
        this.latestSnapshot = undefined;
        if (blockedNavigation) {
            return await this.completePolicyDeniedClick({
                actionId,
                timestamp,
                ref: input.ref,
                element,
                decision: blockedNavigation.decision,
                url: blockedNavigation.url,
            });
        }
        const finalDecision = this.policy.evaluateNavigation(finalUrl);
        await this.record({
            id: actionId,
            timestamp,
            action: 'click',
            status: finalDecision.status === 'allowed' ? 'completed' : finalDecision.status,
            url: finalUrl,
            reason: finalDecision.reason,
            detail: { ref: input.ref, target: this.evidenceTarget(element), finalUrl },
        });
        return {
            status: finalDecision.status,
            actionId,
            timestamp,
            url: finalUrl,
            reason: finalDecision.reason,
            data: { finalUrl },
        };
    }
    async completePolicyDeniedClick(args) {
        await this.record({
            id: args.actionId,
            timestamp: args.timestamp,
            action: 'click',
            status: args.decision.status,
            reason: args.decision.reason,
            url: args.url,
            detail: { ref: args.ref, target: this.evidenceTarget(args.element) },
        });
        return {
            status: args.decision.status,
            actionId: args.actionId,
            timestamp: args.timestamp,
            url: args.url,
            reason: args.decision.reason,
        };
    }
    async screenshot(input = {}) {
        const actionId = this.actionId('screenshot');
        const timestamp = this.now();
        const path = this.evidence.screenshotPath(actionId, input.name);
        await this.evidence.initialize();
        await (await this.getDriver()).screenshot(path);
        await this.record({
            id: actionId,
            timestamp,
            action: 'screenshot',
            status: 'completed',
            url: this.currentUrl(),
            detail: { path },
        });
        return {
            status: 'allowed',
            actionId,
            timestamp,
            url: this.currentUrl(),
            data: { path },
        };
    }
    async close() {
        const actionId = this.actionId('close');
        const timestamp = this.now();
        await this.driver?.close();
        this.driver = undefined;
        await this.record({
            id: actionId,
            timestamp,
            action: 'close',
            status: 'completed',
        });
        return {
            status: 'allowed',
            actionId,
            timestamp,
        };
    }
    async getDriver() {
        if (!this.driver) {
            this.driver = await this.driverFactory.launch({ profile: this.config.profile });
        }
        return this.driver;
    }
    currentUrl() {
        return this.driver?.currentUrl();
    }
    async record(event) {
        await this.evidence.recordEvent(event);
    }
    actionId(action) {
        return `browser_${action}_${randomUUID()}`;
    }
    evidenceTarget(element) {
        return {
            ref: element.ref,
            role: element.role,
            name: element.name,
            text: element.text,
            href: element.href,
            rawHref: element.rawHref,
            tagName: element.tagName,
        };
    }
    now() {
        return dayjs().toISOString();
    }
}
//# sourceMappingURL=service.js.map