/**
 * PageVersionService — append-only versioning of markdown pages (M17 Phase 4).
 * Parallel to VersionService but for filesystem pages. Each capture produces
 * one row in `page_version` with `release_id = NULL` (assigned by
 * `releaseService.createRelease`).
 */
export class PageVersionService {
    db;
    serializer;
    constructor(db, serializer) {
        this.db = db;
        this.serializer = serializer;
    }
    /**
     * Capture a new version of a page. For `op = 'delete'`, the caller must
     * provide the last-known content (since the file is gone) — used to build
     * a tombstone snapshot.
     *
     * M21 (m02multidir): caller can pass an alternative `serializer` so this
     * single shared `PageVersionService` can capture both `pagesDir` and
     * `briefsDir` files. Each `PageSerializer` is bound to a specific
     * `PagesService` (= rootDir) at construction time; the path-keyed
     * `page_version` table itself is rootDir-agnostic.
     */
    async recordVersion(relPath, op, changedBy, fallbackContent, serializer, kind = 'page', changeSummary) {
        const ser = serializer ?? this.serializer;
        const data = op === 'delete' && fallbackContent !== undefined
            ? ser.snapshotFromContent(relPath, fallbackContent)
            : op === 'delete'
                ? this.synthesizeDeleteFromLastVersion(relPath)
                : await ser.snapshot(relPath);
        const next = this.nextVersionNumber(relPath, kind);
        const summary = typeof changeSummary === 'string' && changeSummary.length > 0
            ? changeSummary
            : null;
        const info = this.db
            .prepare(`INSERT INTO page_version (path, version, data, serializer_version, op, changed_by, kind, change_summary)
         VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
            .run(relPath, next, JSON.stringify(data), ser.version, op, changedBy, kind, summary);
        const row = this.db
            .prepare(`SELECT * FROM page_version WHERE id = ?`)
            .get(info.lastInsertRowid);
        return this.toListItem(row);
    }
    listVersions(relPath) {
        const rows = this.db
            .prepare(`SELECT * FROM page_version WHERE path = ? ORDER BY version DESC`)
            .all(relPath);
        return rows.map((r) => this.toListItem(r));
    }
    getVersion(relPath, version) {
        const row = this.db
            .prepare(`SELECT * FROM page_version WHERE path = ? AND version = ?`)
            .get(relPath, version);
        return row ? this.toDetail(row) : null;
    }
    /**
     * M17: latest captured version of a page at-or-before a given release.
     * Mirrors VersionService.getLatestVersionForEntity.
     */
    getLatestForPath(relPath, releaseId, kind) {
        // M23: optional `kind` filter — distinguishes page/brief/patch timelines
        // for the same path. Omitted ⇒ legacy behaviour (latest regardless of kind).
        const kindClause = kind ? ' AND kind = ?' : '';
        let row;
        if (releaseId === undefined) {
            row = this.db
                .prepare(`SELECT * FROM page_version WHERE path = ?${kindClause}
            ORDER BY version DESC LIMIT 1`)
                .get(...(kind ? [relPath, kind] : [relPath]));
        }
        else if (releaseId === null) {
            row = this.db
                .prepare(`SELECT * FROM page_version WHERE path = ? AND release_id IS NULL${kindClause}
            ORDER BY version DESC LIMIT 1`)
                .get(...(kind ? [relPath, kind] : [relPath]));
        }
        else {
            row = this.db
                .prepare(`SELECT * FROM page_version
            WHERE path = ? AND release_id IS NOT NULL AND release_id <= ?${kindClause}
            ORDER BY version DESC LIMIT 1`)
                .get(...(kind ? [relPath, releaseId, kind] : [relPath, releaseId]));
        }
        return row ? this.toDetail(row) : null;
    }
    /** True when this path has any captured version. Used by initial-sync hook. */
    hasAny(relPath) {
        const row = this.db
            .prepare(`SELECT 1 FROM page_version WHERE path = ? LIMIT 1`)
            .get(relPath);
        return !!row;
    }
    /** Distinct paths captured at-or-before a given release (or all if undefined). */
    listPathsForRelease(releaseId) {
        const rows = this.db
            .prepare(`SELECT DISTINCT path FROM page_version WHERE release_id = ? ORDER BY path`)
            .all(releaseId);
        return rows.map((r) => r.path);
    }
    /**
     * Count page captures with `release_id IS NULL` (waiting to be picked up by
     * next release). Excludes briefs — briefs nigdy nie wpadają do release'u
     * (M21 spec, sekcja m21db0001).
     */
    countUnreleased() {
        const row = this.db
            .prepare(`SELECT COUNT(*) AS n FROM page_version WHERE release_id IS NULL AND kind = 'page'`)
            .get();
        return row.n;
    }
    /**
     * Atomic: assign all unreleased page_version rows (kind='page') to a release.
     * Returns the count of rows updated. Briefy (kind='brief') NIGDY nie wpadają
     * do release'u — mają własną oś czasu, niezależną od release timeline.
     */
    assignToRelease(releaseId) {
        const info = this.db
            .prepare(`UPDATE page_version SET release_id = ? WHERE release_id IS NULL AND kind = 'page'`)
            .run(releaseId);
        return Number(info.changes);
    }
    /**
     * M23: numer wersji jest sekwencyjny per `(path, kind)`, nie per `path` —
     * page/brief/patch o tej samej ścieżce (teoretycznie) mają niezależne osie
     * czasu. W praktyce ścieżki nie kolidują, bo każdy `kind` ma własny katalog.
     */
    nextVersionNumber(relPath, kind) {
        const row = this.db
            .prepare(`SELECT MAX(version) AS v FROM page_version WHERE path = ? AND kind = ?`)
            .get(relPath, kind);
        return (row.v ?? 0) + 1;
    }
    synthesizeDeleteFromLastVersion(relPath) {
        const last = this.getLatestForPath(relPath);
        if (last)
            return last.data;
        // No prior version — we don't have content. Tombstone with empty content.
        return {
            path: relPath,
            content: '',
            frontmatter: {},
            anchors: [],
            xml_refs: [],
        };
    }
    toListItem(row) {
        return {
            id: row.id,
            path: row.path,
            version: row.version,
            op: row.op,
            changedBy: row.changed_by,
            releaseId: row.release_id,
            serializerVersion: row.serializer_version,
            createdAt: row.created_at,
            changeSummary: row.change_summary,
        };
    }
    toDetail(row) {
        return {
            ...this.toListItem(row),
            data: JSON.parse(row.data),
        };
    }
}
//# sourceMappingURL=page-version.js.map