/**
 * Pack Versioning + Rollback (W1.4 of F-NEW-PRODUCT-AGENT-VISIBILITY-CLUSTER-2026-05-12).
 *
 * Lets a customer publish a new version of a company pack, run it in
 * shadow-mode (record what it WOULD have decided without gating real
 * traffic), then promote to active. One-click rollback to the prior active
 * version. Every transition is an audit event.
 *
 * Storage substrate: in-process Map per-pack. A persistence adapter is a
 * follow-up slice (would wire to Prisma; the API surface is identical).
 *
 * Customer flow:
 *   1. Author v1 of company pack -> publishVersion -> setActive(v1)
 *   2. Author v2 with a new rule -> publishVersion -> setShadow(v2)
 *   3. Observe shadow decisions for N hours -> setActive(v2) (auto-archives v1)
 *   4. Something breaks -> rollback(packId, v1) -> v1 active again, v2 archived
 *
 * @connexum/ai-governance  F-NEW-PRODUCT-PACK-VERSIONING-ROLLBACK
 */
const VERSION_PATTERN = /^v?\d+(\.\d+){0,2}(-[A-Za-z0-9.-]+)?$/;
function validateVersion(v) {
    if (!VERSION_PATTERN.test(v)) {
        throw new Error(`Invalid version '${v}'. Expected semver-ish (e.g. '1', '1.0', '1.2.3', '2.0.0-rc1').`);
    }
}
/**
 * In-process store of pack versions. Tracks one active version + zero-or-one
 * shadow version per packId, plus an arbitrary number of archived versions.
 */
export class PackVersionStore {
    versions = new Map();
    active = new Map();
    shadow = new Map();
    eventSink;
    constructor(eventSink = () => undefined) {
        this.eventSink = eventSink;
    }
    publishVersion(pack, publishedBy, changelog) {
        if (!pack.version) {
            throw new Error('publishVersion: pack must have a non-empty version field');
        }
        validateVersion(pack.version);
        if (!publishedBy || publishedBy.trim() === '') {
            throw new Error('publishVersion: publishedBy is required (audit)');
        }
        const list = this.versions.get(pack.id) ?? [];
        if (list.some((r) => r.version === pack.version)) {
            throw new Error(`publishVersion: pack '${pack.id}' already has version '${pack.version}'. ` +
                'Versions are immutable once published.');
        }
        const now = new Date().toISOString();
        const record = {
            packId: pack.id,
            version: pack.version,
            pack,
            state: 'draft',
            publishedAt: now,
            publishedBy,
            changelog,
            stateChangedAt: now,
            stateChangedBy: publishedBy,
        };
        list.push(record);
        this.versions.set(pack.id, list);
        this.eventSink({
            kind: 'VERSION_PUBLISHED',
            packId: pack.id,
            version: pack.version,
            actor: publishedBy,
            timestamp: now,
            detail: changelog,
        });
        return record;
    }
    setActive(packId, version, actor) {
        if (!actor || actor.trim() === '') {
            throw new Error('setActive: actor is required (audit)');
        }
        const record = this.requireRecord(packId, version);
        const previousActive = this.active.get(packId);
        const now = new Date().toISOString();
        // Archive previous active.
        if (previousActive && previousActive !== version) {
            const prev = this.requireRecord(packId, previousActive);
            prev.state = 'archived';
            prev.stateChangedAt = now;
            prev.stateChangedBy = actor;
            this.eventSink({
                kind: 'VERSION_ARCHIVED',
                packId,
                version: previousActive,
                actor,
                timestamp: now,
            });
        }
        // Clear shadow if same version is being promoted.
        if (this.shadow.get(packId) === version)
            this.shadow.delete(packId);
        record.state = 'active';
        record.stateChangedAt = now;
        record.stateChangedBy = actor;
        this.active.set(packId, version);
        this.eventSink({
            kind: 'VERSION_ACTIVATED',
            packId,
            version,
            previousVersion: previousActive,
            actor,
            timestamp: now,
        });
        return record;
    }
    setShadow(packId, version, actor) {
        if (!actor || actor.trim() === '') {
            throw new Error('setShadow: actor is required (audit)');
        }
        const record = this.requireRecord(packId, version);
        if (this.active.get(packId) === version) {
            throw new Error(`setShadow: version '${version}' of pack '${packId}' is currently active. ` +
                'A version cannot be both active and shadow.');
        }
        const now = new Date().toISOString();
        record.state = 'shadow';
        record.stateChangedAt = now;
        record.stateChangedBy = actor;
        this.shadow.set(packId, version);
        this.eventSink({
            kind: 'VERSION_SHADOW_ENABLED',
            packId,
            version,
            actor,
            timestamp: now,
        });
        return record;
    }
    /**
     * One-click rollback: promote `toVersion` to active and archive the
     * current active version. Emits PACK_ROLLED_BACK plus the usual
     * activation/archival events.
     */
    rollback(packId, toVersion, actor) {
        if (!actor || actor.trim() === '') {
            throw new Error('rollback: actor is required (audit)');
        }
        const previousActive = this.active.get(packId);
        if (previousActive === toVersion) {
            throw new Error(`rollback: pack '${packId}' is already on version '${toVersion}'. Nothing to do.`);
        }
        const record = this.setActive(packId, toVersion, actor);
        this.eventSink({
            kind: 'PACK_ROLLED_BACK',
            packId,
            version: toVersion,
            previousVersion: previousActive,
            actor,
            timestamp: new Date().toISOString(),
        });
        return record;
    }
    getActiveVersion(packId) {
        const v = this.active.get(packId);
        return v ? this.findRecord(packId, v) : undefined;
    }
    getShadowVersion(packId) {
        const v = this.shadow.get(packId);
        return v ? this.findRecord(packId, v) : undefined;
    }
    getVersion(packId, version) {
        return this.findRecord(packId, version);
    }
    listVersions(packId) {
        return [...(this.versions.get(packId) ?? [])];
    }
    findRecord(packId, version) {
        const list = this.versions.get(packId);
        if (!list)
            return undefined;
        return list.find((r) => r.version === version);
    }
    requireRecord(packId, version) {
        const record = this.findRecord(packId, version);
        if (!record) {
            throw new Error(`pack '${packId}' has no published version '${version}'`);
        }
        return record;
    }
}
//# sourceMappingURL=pack-versioning.js.map