"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.applySpecSourceTransition = applySpecSourceTransition;
exports.parseSpecRegistrySourceHistory = parseSpecRegistrySourceHistory;
exports.serializeSpecRegistrySourceHistory = serializeSpecRegistrySourceHistory;
const source_1 = require("./source");
const HISTORY_ENTRY_KEYS = new Set(["source", "reason", "transitioned_at", "provenance"]);
const PROVENANCE = Object.freeze({
    scope: "repository-local",
    actor: "day-shift-cli",
    command: "spec source add"
});
function applySpecSourceTransition(input) {
    const entryId = requireText(input.entryId, "Spec source transition entry id");
    const nextRef = requireText(input.nextRef, "Spec source transition ref");
    const reason = requireText(input.reason, "Spec source transition reason");
    const targetIndex = input.currentDocument.entries.findIndex((entry) => entry.id === entryId);
    if (targetIndex === -1) {
        throw new RangeError(`Spec index document is missing entry '${entryId}'.`);
    }
    const currentEntry = input.currentDocument.entries[targetIndex];
    if (currentEntry.source.ref === nextRef) {
        return {
            document: input.currentDocument,
            entry: currentEntry,
            changed: false,
            transitionTimestamp: null
        };
    }
    const transitionTimestamp = normalizeTimestamp((input.now ?? (() => new Date().toISOString()))());
    const history = [...(currentEntry.optional.source_history ?? [])];
    const previousTransition = history.at(-1)?.transitionedAt;
    if (previousTransition !== undefined && transitionTimestamp < previousTransition) {
        throw new TypeError("Spec source transition timestamps must be chronological.");
    }
    const historyEntry = Object.freeze({
        source: cloneSource(currentEntry.source),
        reason,
        transitionedAt: transitionTimestamp,
        provenance: PROVENANCE,
        additionalMetadata: Object.freeze({})
    });
    const nextEntry = {
        ...currentEntry,
        source: {
            ...currentEntry.source,
            ref: nextRef,
            additionalMetadata: { ...currentEntry.source.additionalMetadata }
        },
        optional: {
            ...currentEntry.optional,
            source_history: Object.freeze([...history, historyEntry]),
            updated_at: transitionTimestamp
        },
        additionalMetadata: { ...currentEntry.additionalMetadata }
    };
    const nextEntries = input.currentDocument.entries.map((entry, index) => index === targetIndex ? nextEntry : entry);
    return {
        document: {
            ...input.currentDocument,
            notes: [...input.currentDocument.notes],
            entries: nextEntries,
            additionalMetadata: { ...input.currentDocument.additionalMetadata }
        },
        entry: nextEntry,
        changed: true,
        transitionTimestamp
    };
}
function parseSpecRegistrySourceHistory(input) {
    if (!Array.isArray(input)) {
        throw new TypeError("Spec index entry field 'source_history' must be an array.");
    }
    let previousTimestamp = null;
    return Object.freeze(input.map((value, index) => {
        const record = readRecord(value, `Spec source history entry ${index + 1}`);
        const transitionedAt = normalizeTimestamp(record.transitioned_at);
        if (previousTimestamp !== null && transitionedAt < previousTimestamp) {
            throw new TypeError("Spec source history entries must be chronological.");
        }
        previousTimestamp = transitionedAt;
        const additionalMetadata = {};
        for (const [key, item] of Object.entries(record)) {
            if (!HISTORY_ENTRY_KEYS.has(key))
                additionalMetadata[key] = item;
        }
        return Object.freeze({
            source: (0, source_1.parseSpecRegistrySourceReference)(record.source),
            reason: requireText(record.reason, `Spec source history entry ${index + 1} reason`),
            transitionedAt,
            provenance: parseProvenance(record.provenance),
            additionalMetadata: Object.freeze(additionalMetadata)
        });
    }));
}
function serializeSpecRegistrySourceHistory(history) {
    return history.map((entry) => {
        const serialized = {
            source: (0, source_1.serializeSpecRegistrySourceReference)(entry.source),
            reason: requireText(entry.reason, "Spec source history reason"),
            transitioned_at: normalizeTimestamp(entry.transitionedAt),
            provenance: {
                scope: entry.provenance.scope,
                actor: entry.provenance.actor,
                command: entry.provenance.command
            }
        };
        for (const [key, value] of Object.entries(entry.additionalMetadata)) {
            if (!(key in serialized))
                serialized[key] = value;
        }
        return serialized;
    });
}
function parseProvenance(input) {
    const record = readRecord(input, "Spec source transition provenance");
    if (record.scope !== PROVENANCE.scope || record.actor !== PROVENANCE.actor || record.command !== PROVENANCE.command) {
        throw new TypeError("Spec source transition provenance must use the repository-local Day Shift CLI contract.");
    }
    return PROVENANCE;
}
function cloneSource(source) {
    return {
        ...source,
        additionalMetadata: { ...source.additionalMetadata }
    };
}
function normalizeTimestamp(value) {
    if (typeof value !== "string" || value.trim() === "") {
        throw new TypeError("Spec source transition timestamp must be a non-empty ISO-8601 string.");
    }
    const parsed = new Date(value);
    if (!Number.isFinite(parsed.valueOf())) {
        throw new TypeError("Spec source transition timestamp must be a valid ISO-8601 string.");
    }
    return parsed.toISOString();
}
function requireText(value, label) {
    if (typeof value !== "string" || value.trim() === "") {
        throw new TypeError(`${label} must be a non-empty string.`);
    }
    return value.trim();
}
function readRecord(input, label) {
    if (typeof input !== "object" || input === null || Array.isArray(input)) {
        throw new TypeError(`${label} must be an object.`);
    }
    return input;
}
