// Governed Figma snapshot-build orchestration (Epic #750, Issues #758, #760, #759).
//
// The snapshot route was a thin happy-path that bypassed the governance machinery the epic's children
// built and unit-tested: the encrypted PAT vault (#758), the read-only-scope consent gate + audit
// ledger + operational metrics (#760). This module wires them into ONE governed build so every
// production snapshot is consent-gated, audited (success AND coded failure), metric-bearing, and
// resolves the PAT with the correct vault > config > env precedence — without changing any connector,
// builder, or store behaviour. Figma is contacted ONLY inside the bounded build (the boundary holds).
//
// Token handling stays server-side: the vault token (when present) flows into the connector's
// X-Figma-Token header only; it never reaches a return value, log, audit entry, metric, or snapshot.
// The vault degrades gracefully — an unconfigured vault yields `undefined` and the build falls back to
// the config/env token, so the existing env-auth path keeps working unchanged.
import { join } from "node:path";
import { appendFigmaConnectorAudit, assertReadOnlyConsent, buildFigmaSnapshot, createDefaultFigmaHttpPort, createDefaultFigmaRenderPort, createFigmaConnector, createFigmaTokenStore, deriveFigmaScopeRef, FigmaConnectorError, observeFigmaSnapshot, parseFigmaTarget, recordReadOnlyConsent, resolveFigmaToken, resolveFigmaVaultKey, } from "./figma/index.js";
import { QualityIntelligenceFigma } from "@oscharko-dev/keiko-quality-intelligence";
const FIGMA_VAULT_SUBDIR = "figma";
const FIGMA_TOKEN_VAULT_FILE = "figma-token.vault";
const tokenVaultDir = (evidenceDir) => join(evidenceDir, FIGMA_VAULT_SUBDIR);
const tokenVaultPath = (evidenceDir) => join(tokenVaultDir(evidenceDir), FIGMA_TOKEN_VAULT_FILE);
/**
 * Read the encrypted-at-rest vault PAT (#758), or `undefined` when no vault token is stored or the
 * vault key cannot be resolved. Highest precedence in {@link resolveFigmaToken}. Never throws — a
 * missing/unreadable vault degrades to the config/env token rather than failing the build.
 */
export const readFigmaVaultToken = (deps) => {
    try {
        const { key } = resolveFigmaVaultKey(deps.env, tokenVaultDir(deps.evidenceDir), deps.keychainAccess);
        return createFigmaTokenStore({ key, storePath: tokenVaultPath(deps.evidenceDir) }).read();
    }
    catch {
        return undefined;
    }
};
/** Build the encrypted token store for operator rotation/revocation (#758). */
export const figmaTokenStoreFor = (deps) => {
    const { key } = resolveFigmaVaultKey(deps.env, tokenVaultDir(deps.evidenceDir), deps.keychainAccess);
    return createFigmaTokenStore({ key, storePath: tokenVaultPath(deps.evidenceDir) });
};
// Count deterministic a11y findings (#812) on the cleaned IR — the metric the #760 audit surfaces.
// Coverage-notice items are excluded: they record what could NOT be checked, not a finding.
const a11yFindingsCount = (ir) => {
    let count = 0;
    for (const items of QualityIntelligenceFigma.deriveA11yTestItemsByScreen(ir.screens).values()) {
        for (const item of items)
            if (item.category === "a11y")
                count += 1;
    }
    return count;
};
// Record (when acknowledged) and assert read-only-scope consent BEFORE any token materialisation or
// egress — an unconsented scope never reaches Figma (#760). Throws FIGMA_CONSENT_REQUIRED if missing.
const gateConsent = (deps, scopeRef) => {
    if (deps.acknowledgeReadOnly === true) {
        recordReadOnlyConsent({
            scopeRef,
            evidenceDir: deps.evidenceDir,
            acknowledgedBy: "operator",
            now: deps.now,
        });
    }
    assertReadOnlyConsent(scopeRef, deps.evidenceDir);
};
// Deep scoped fetch with audit-on-failure: a fetch failure is recorded as a coded audit entry before
// it propagates, so the audit ledger captures the action even when the egress never produced a build.
const auditedDeepFetch = async (connector, boardLink, deps, scopeRef, action) => {
    try {
        return await connector.fetchScopedNodesDeep(boardLink, { fetchedAt: deps.now });
    }
    catch (error) {
        appendFigmaConnectorAudit({
            scopeRef,
            evidenceDir: deps.evidenceDir,
            action,
            outcome: "error",
            errorCode: error instanceof FigmaConnectorError ? error.code : "FIGMA_INTERNAL",
            now: deps.now,
        });
        throw error;
    }
};
/**
 * Run the governed snapshot-build for `boardLink`: resolve the vault>config>env PAT, gate on recorded
 * read-only consent BEFORE any Figma egress, perform the deep scoped-pagination fetch + render, and
 * audit + compute metrics. Throws a coded {@link FigmaConnectorError} on any failure (the route maps
 * it to a status) — a fetch failure is audited before it propagates, a render failure inside the
 * observed wrapper likewise. `isResnapshot` only changes the audited action label.
 */
export const governedSnapshotBuild = async (boardLink, deps, isResnapshot = false) => {
    const target = parseFigmaTarget(boardLink);
    if (target === null)
        throw new FigmaConnectorError("FIGMA_MALFORMED_URL");
    const scopeRef = deriveFigmaScopeRef(target.fileKey, target.nodeId);
    const httpPort = deps.httpPort ?? createDefaultFigmaHttpPort(deps.egress);
    const renderPort = deps.renderPort ?? createDefaultFigmaRenderPort(deps.egress);
    const vaultToken = readFigmaVaultToken(deps);
    gateConsent(deps, scopeRef);
    // Render-token (vault > config > env). Throws FIGMA_TOKEN_MISSING when nothing is configured.
    const token = resolveFigmaToken({ vaultToken, envToken: deps.env.FIGMA_ACCESS_TOKEN });
    const connector = createFigmaConnector({
        http: httpPort,
        env: deps.env,
        ...(vaultToken !== undefined ? { vaultToken } : {}),
        config: { pagination: deps.pagination ?? {} },
    });
    const action = isResnapshot ? "resnapshot" : "snapshot";
    const scoped = await auditedDeepFetch(connector, boardLink, deps, scopeRef, action);
    const ir = QualityIntelligenceFigma.cleanScopedNodesToScreenIr(scoped.nodes);
    const observed = await observeFigmaSnapshot({
        ctx: { evidenceDir: deps.evidenceDir, now: deps.now },
        provenance: scoped.provenance,
        ir,
        // The snapshot-build itself is fully deterministic (no model) — the model-augmentation share is
        // the QI test-gen concern (#754). Here it is 0/0 → a 0 model-augmented share (100% deterministic).
        augmentation: { deterministic: 0, modelAugmented: 0 },
        extras: { a11yFindings: a11yFindingsCount(ir) },
        isResnapshot,
        run: () => buildFigmaSnapshot({
            ir,
            provenance: scoped.provenance,
            token,
            imagesPort: httpPort,
            renderPort,
        }),
    });
    return {
        provenance: scoped.provenance,
        coverage: scoped.coverage,
        snapshot: observed.snapshot,
        ir,
        metrics: observed.metrics,
        scopeRef,
    };
};
