import fsp from 'node:fs/promises';
import path from 'node:path';
import { ensureDir, exists, nowIso, packageRoot, readJson, rel, sha256, writeJsonAtomic } from './fsx.js';
import { addVisualAnchor, ingestImage, missionImageLedgerPath } from './wiki-image/image-voxel-ledger.js';
export function computerUseLiveEvidencePath(root = packageRoot(), opts = {}) {
    if (opts.missionId) {
        return path.join(root, '.sneakoscope', 'missions', opts.missionId, 'computer-use-live-evidence.json');
    }
    return path.join(root, '.sneakoscope', 'reports', 'computer-use-live-evidence.json');
}
export async function readComputerUseLiveEvidence(root = packageRoot(), opts = {}) {
    const candidates = [
        opts.path || null,
        opts.missionId ? computerUseLiveEvidencePath(root, { missionId: opts.missionId }) : null,
        computerUseLiveEvidencePath(root)
    ].filter(Boolean);
    for (const file of candidates) {
        const parsed = await readJson(file, null).catch(() => null);
        if (isComputerUseLiveEvidence(parsed))
            return { ok: true, path: file, evidence: parsed };
    }
    return { ok: false, path: null, evidence: null };
}
export async function writeComputerUseLiveEvidence(file, evidence) {
    await ensureDir(path.dirname(file));
    await writeJsonAtomic(file, evidence);
    return { ok: true, path: file, evidence };
}
export async function buildComputerUseLiveEvidence(opts) {
    const root = opts.root || packageRoot();
    const status = normalizeComputerUseStatus(opts.statusReport?.status);
    const realOptIn = opts.realOptIn === true;
    const route = opts.route || null;
    const missionId = opts.missionId || null;
    const warnings = [];
    const blockers = [];
    const capability = await detectCapability(opts);
    const platform = String(opts.statusReport?.platform || process.platform);
    let mode = realOptIn && status !== 'not_macos' ? 'live_capture_attempted' : 'probe_only';
    const evidence = {
        schema: 'sks.computer-use-live-evidence.v1',
        generated_at: nowIso(),
        route,
        mission_id: missionId,
        mode,
        status,
        platform,
        mock: false,
        capability,
        capture: {
            screenshot: {
                attempted: false,
                status: 'not_attempted',
                path: null,
                sha256: null,
                redacted: false,
                local_only: true
            },
            action: {
                attempted: false,
                status: 'not_attempted',
                actions: [],
                redacted: false,
                local_only: true
            }
        },
        image_voxel: {
            linked: false,
            ledger_path: missionId ? rel(root, missionImageLedgerPath(root, missionId)) : null,
            anchor_ids: [],
            reason: null
        },
        privacy: {
            shared_triwiki_publish_allowed: false,
            contains_screen_content: false,
            redaction_required: false
        },
        blockers,
        warnings
    };
    if (!realOptIn) {
        evidence.image_voxel.reason = 'probe_only_no_live_capture_attempted';
        return evidence;
    }
    if (status === 'not_macos') {
        blockers.push('not_macos');
        evidence.mode = 'probe_only';
        evidence.image_voxel.reason = 'not_macos';
        return evidence;
    }
    if (status !== 'available') {
        blockers.push(status);
        evidence.mode = 'live_capture_blocked';
        evidence.capture.screenshot.status = opts.captureScreenshot ? 'blocked' : 'not_attempted';
        evidence.capture.screenshot.attempted = opts.captureScreenshot === true;
        evidence.image_voxel.reason = status;
        return evidence;
    }
    if (opts.captureScreenshot) {
        await attemptScreenshotCapture(root, evidence, opts);
    }
    else {
        evidence.image_voxel.reason = 'capture_screenshot_not_requested';
    }
    if (opts.allowAction) {
        evidence.capture.action.attempted = true;
        evidence.capture.action.status = 'blocked';
        blockers.push('computer_use_action_adapter_missing');
        warnings.push('Computer Use smoke never runs click/type actions without an official non-destructive Codex App adapter.');
    }
    if (evidence.capture.screenshot.status === 'captured') {
        evidence.mode = 'live_capture_success';
        evidence.privacy.contains_screen_content = true;
    }
    else if (blockers.length) {
        evidence.mode = 'live_capture_blocked';
    }
    return evidence;
}
export function isComputerUseLiveEvidence(value) {
    if (!value || typeof value !== 'object')
        return false;
    const evidence = value;
    return evidence.schema === 'sks.computer-use-live-evidence.v1'
        && isComputerUseEvidenceMode(evidence.mode)
        && evidence.mock === false
        && typeof evidence.capture === 'object'
        && typeof evidence.privacy === 'object';
}
export function isComputerUseEvidenceMode(value) {
    return value === 'probe_only'
        || value === 'live_capture_attempted'
        || value === 'live_capture_success'
        || value === 'live_capture_blocked';
}
async function detectCapability(opts) {
    if (opts.capabilityAdapter) {
        const detected = await opts.capabilityAdapter.detect();
        return {
            codex_app_installed: Boolean(detected.codex_app_installed),
            capability_detected: Boolean(detected.capability_detected),
            external_capability_blocked: detected.external_capability_blocked === true,
            blocker: detected.blocker || null
        };
    }
    const status = normalizeComputerUseStatus(opts.statusReport?.status);
    const installed = Boolean(opts.statusReport?.app?.app?.installed);
    const available = status === 'available';
    return {
        codex_app_installed: installed,
        capability_detected: available,
        external_capability_blocked: status !== 'available',
        blocker: available ? null : status
    };
}
async function attemptScreenshotCapture(root, evidence, opts) {
    evidence.capture.screenshot.attempted = true;
    const outputPath = screenshotArtifactPath(root, opts);
    const adapter = opts.screenshotAdapter || null;
    if (!adapter) {
        evidence.capture.screenshot.status = 'blocked';
        evidence.blockers.push('codex_app_capability_missing');
        evidence.warnings.push('Official Codex App Computer Use screenshot adapter is not exposed to this CLI process.');
        evidence.image_voxel.reason = 'computer_use_capture_adapter_missing';
        return;
    }
    try {
        const result = await adapter.captureScreenshot({
            root,
            route: opts.route || null,
            missionId: opts.missionId || null,
            outputPath
        });
        if (!result.ok) {
            evidence.capture.screenshot.status = 'blocked';
            evidence.blockers.push(redactCaptureMessage(result.blocker || 'external_capability_blocked'));
            if (result.warning)
                evidence.warnings.push(redactCaptureMessage(result.warning));
            evidence.image_voxel.reason = result.blocker || 'screenshot_capture_blocked';
            return;
        }
        const capturedPath = await materializeScreenshot(result, outputPath);
        const data = await fsp.readFile(capturedPath);
        evidence.capture.screenshot.status = result.localOnly === false ? 'captured' : 'captured';
        evidence.capture.screenshot.path = rel(root, capturedPath);
        evidence.capture.screenshot.sha256 = sha256(data);
        evidence.capture.screenshot.redacted = result.redacted === true;
        evidence.capture.screenshot.local_only = result.localOnly !== false;
        evidence.privacy.redaction_required = result.redacted === true;
        await linkScreenshotToImageVoxel(root, evidence, capturedPath, opts);
    }
    catch (err) {
        evidence.capture.screenshot.status = 'failed';
        evidence.blockers.push('screenshot_capture_failed');
        evidence.image_voxel.reason = redactCaptureMessage(err instanceof Error ? err.message : String(err));
    }
}
async function materializeScreenshot(result, outputPath) {
    if (result.path && await exists(result.path))
        return path.resolve(result.path);
    if (result.data === undefined || result.data === null)
        throw new Error('screenshot_capture_returned_no_path_or_data');
    await ensureDir(path.dirname(outputPath));
    const bytes = typeof result.data === 'string' ? Buffer.from(result.data, 'base64') : Buffer.from(result.data);
    await fsp.writeFile(outputPath, bytes);
    return outputPath;
}
async function linkScreenshotToImageVoxel(root, evidence, screenshotPath, opts) {
    if (!opts.missionId) {
        evidence.image_voxel.reason = 'mission_id_missing';
        return;
    }
    if (!evidence.capture.screenshot.sha256) {
        evidence.image_voxel.reason = 'screenshot_sha256_missing';
        return;
    }
    const relative = rel(root, screenshotPath);
    if (relative.startsWith('..')) {
        evidence.image_voxel.reason = 'screenshot_path_outside_project';
        return;
    }
    try {
        const imageId = evidence.capture.screenshot.sha256;
        const ingested = await ingestImage(root, relative, {
            missionId: opts.missionId,
            source: 'computer_use_live_screenshot',
            id: imageId,
            capturedAt: evidence.generated_at
        });
        const image = ingested.image;
        const anchorId = `computer-use-${imageId.slice(0, 16)}-screen`;
        const anchor = await addVisualAnchor(root, {
            id: anchorId,
            missionId: opts.missionId,
            imageId,
            bbox: [0, 0, image.width || 1, image.height || 1],
            label: 'Computer Use live screenshot',
            source: 'computer_use_live_screenshot',
            evidencePath: relative,
            route: opts.route || '$Computer-Use',
            trustScore: 0.86
        });
        evidence.image_voxel.linked = Boolean(anchor.ok);
        evidence.image_voxel.ledger_path = rel(root, missionImageLedgerPath(root, opts.missionId));
        evidence.image_voxel.anchor_ids = anchor.ok ? [anchorId] : [];
        evidence.image_voxel.reason = anchor.ok ? null : (anchor.validation?.issues || ['image_voxel_anchor_failed']).join(',');
    }
    catch (err) {
        evidence.image_voxel.reason = redactCaptureMessage(err instanceof Error ? err.message : String(err));
    }
}
function screenshotArtifactPath(root, opts) {
    if (opts.missionId) {
        return path.join(root, '.sneakoscope', 'missions', opts.missionId, 'computer-use-live-screenshot.png');
    }
    return path.join(root, '.sneakoscope', 'reports', 'computer-use-live-screenshot.png');
}
function normalizeComputerUseStatus(value) {
    const allowed = [
        'available',
        'codex_app_missing',
        'macos_permission_missing',
        'codex_app_capability_missing',
        'external_capability_blocked',
        'not_macos',
        'unknown'
    ];
    return allowed.includes(value) ? value : 'unknown';
}
function redactCaptureMessage(value) {
    return String(value || 'unknown')
        .replace(/sk-[A-Za-z0-9_-]+/g, '[redacted]')
        .replace(/CODEX_LB_API_KEY=([^\s]+)/g, 'CODEX_LB_API_KEY=[redacted]')
        .slice(0, 500);
}
//# sourceMappingURL=computer-use-live-evidence.js.map