/**
 * ReleaseService — public API M17. Single source of truth for release listing,
 * detail, snapshot, diff, and (Phase 6) restore. All other surfaces
 * (REST `/api/releases/*`, MCP `release-tools`, UI sidebar) are thin
 * adapters.
 *
 * Spec reference: `modules/m17-snapshots-releases.md` (`m17api001`,
 * `m17dom001`, `m17dcre01`).
 */
import type Database from 'better-sqlite3';
import type { ChangedBy, RawDelta, Release, ReleaseDetail, SpecSnapshot } from '../../shared/entities.js';
import type { PluginHost } from '../core/plugin-host/types.js';
import type { RawEntityReader, RawEntityType } from '../domain/raw-entity-reader.js';
import type { VersionService } from './versions.js';
import type { PageVersionService } from './page-version.js';
import type { PageSerializer } from './page-serializer.js';
import type { TagsService } from './tags.js';
import type { PagesService } from './pages.js';
import type { PagesWatcher } from '../fs/watcher.js';
import type { RestoreResult } from '../serialization/types.js';
import { type BuildBundleResult } from './release-bundle.js';
export interface RestoreEntityInput {
    type: RawEntityType;
    slug: string;
    releaseId: number | string;
}
export interface RestorePageInput {
    path: string;
    releaseId: number | string;
}
export interface RestoreSpecInput {
    releaseId: number | string;
}
export interface RestoreEntityResult {
    type: RawEntityType;
    slug: string;
    op: RestoreResult['op'];
    warnings?: string[];
}
export interface RestorePageResult {
    path: string;
    op: 'created' | 'updated' | 'deleted' | 'noop';
    warnings?: string[];
}
export interface RestoreSpecResult {
    releaseId: number;
    entityResults: RestoreEntityResult[];
    pageResults: RestorePageResult[];
}
export declare class ReleaseService {
    private db;
    private host;
    private versions;
    private pageVersions;
    private pageSerializer;
    private rawReader;
    private tagsService;
    private pagesService;
    private watcher;
    private cwd;
    constructor(db: Database.Database, host: PluginHost, versions: VersionService, pageVersions: PageVersionService, pageSerializer: PageSerializer, rawReader: RawEntityReader, tagsService: TagsService, pagesService: PagesService, watcher?: PagesWatcher | null, cwd?: string);
    listReleases(): Release[];
    getRelease(idOrName: number | string): ReleaseDetail;
    /**
     * Count of captures still queued at HEAD — entity_version + page_version rows
     * with `release_id IS NULL`. Drives the M25 "You have N unreleased changes"
     * banner shown only on the latest (mutable) release card.
     */
    countUnreleased(): number;
    /**
     * Manual release creation (decyzja 9: zero auto-trigger). Validates
     * non-empty + UNIQUE name and non-empty description; in a single
     * transaction inserts spec_release and assigns all unreleased
     * entity_version + page_version rows.
     */
    createRelease(input: {
        name: string;
        description: string;
    }, actor: ChangedBy): ReleaseDetail;
    /**
     * Mutate the LATEST release only (decyzja 13: implicit last = mutable).
     * Older releases are frozen — `id != MAX(id)` ⇒ 409 RELEASE_FROZEN.
     * Optionally pulls all `release_id IS NULL` rows from entity_version /
     * page_version into this release (decyzja 14, no untie).
     */
    updateRelease(input: {
        idOrName: number | string;
        name?: string;
        description?: string;
        assignUnreleased?: boolean;
    }): ReleaseDetail;
    /**
     * Cumulative state at the end of a release: per (type, slug), the latest
     * entity_version row at-or-before `releaseId`. Each row carries
     * `op` + `data` (snapshot) + `serializer_version`.
     */
    getReleaseSnapshot(idOrName: number | string): SpecSnapshot;
    /**
     * Structured semantic diff between two releases. For each entity that
     * differs (or pages that differ), computes per-plugin `host.diff(...)`
     * (entities) or `pageSerializer.diff(...)` (pages). Falls back to
     * default deep-diff when plugin doesn't override `diff`.
     *
     * `fromIdOrName === null` ⇒ initial brief: synthetic empty `from` snapshot.
     * Wszystkie encje/strony w `to` widoczne jako `op: 'create'`. Output
     * `RawDelta.from` jest wtedy `null` (sygnal dla M21 / UI).
     */
    getReleaseDiff(fromIdOrName: number | string | null, toIdOrName: number | string): RawDelta;
    /**
     * Restore a single entity to its state at the target release. Append-only:
     * the restore generates a normal mutation through write-API, producing a
     * new entity_version row with `release_id = NULL`. Idempotent (decyzja 11):
     * re-running restore on already-matching state yields op='noop'.
     */
    restoreEntity(input: RestoreEntityInput, actor?: ChangedBy): RestoreEntityResult;
    /**
     * Restore a single page. Looks up the latest page_version snapshot at-or-
     * before the release and writes its content via PagesService. The watcher
     * suppresses the resulting chokidar event; the REST capture path then
     * records a fresh page_version row with `release_id = NULL` and
     * `changed_by = 'user'`.
     */
    restorePage(input: RestorePageInput, _actor?: ChangedBy): Promise<RestorePageResult>;
    /**
     * Restore the entire spec to a release. Topological sort: DTO first
     * (Endpoint references DTO via linked_dtos), then everything else, then
     * pages. Each step generates normal mutations → all visible in timeline.
     */
    restoreSpec(input: RestoreSpecInput, actor?: ChangedBy): Promise<RestoreSpecResult>;
    /**
     * Build a portable `tar.gz` of release N, deterministically reconstructed
     * from the versioning tables (`getReleaseSnapshot`) + sanitized `config.json`.
     * Does NOT read `pagesDir` on disk nor entity HEADs. The returned `tarGzPath`
     * points at a temp file the CALLER owns (M25 deletes after streaming to
     * remote); the internal working dir is cleaned up before returning.
     */
    buildBundleArchive(releaseId: number): Promise<BuildBundleResult>;
    /**
     * CONTRACT-ONLY in v1 — implementation deferred to M26 (Release Import,
     * `c4s import <bundle>`). The signature is public so the bidirectional bundle
     * contract is fixed now (write here, read in M26).
     *
     * M26 read-direction algorithm (do not implement here):
     *   1. Parse `manifest.json` FIRST (first tar entry). Missing → throw
     *      `BUNDLE_MANIFEST_MISSING`. `bundleSchemaVersion` > max supported →
     *      throw `BUNDLE_SCHEMA_UNSUPPORTED { foundVersion, maxSupportedVersion }`.
     *   2. Optionally verify SHA-256 (caller supplies expected hash, e.g. from an
     *      M25 push header). Mismatch → throw `BUNDLE_HASH_MISMATCH { expected, actual }`.
     *   3. Read `config.json` via a `bundleSchemaVersion`-aware parser; unknown
     *      fields → warn + ignore (forward-compat).
     *   4. Stream `pages/<path>.md`; reject `..` / absolute / null-byte paths →
     *      throw `BUNDLE_MALFORMED_ENTRY { path, reason }`.
     *   5. Stream `entities/<typePlural>.json`; plural absent from local
     *      `config.entities` → throw `BUNDLE_UNKNOWN_ENTITY_TYPE { type }`.
     *   6. Compose a `SpecSnapshot` (same shape as `getReleaseSnapshot`). M26 owns
     *      what to do with it (UPSERT restore / dry-run diff / read-only mount).
     * All errors are structured (code + payload), never bare strings.
     */
    restoreBundleArchive(stream: NodeJS.ReadableStream): Promise<SpecSnapshot>;
    private findReleaseRow;
    private toRelease;
    private computeCountBreakdown;
    /**
     * Find the latest entity_version row matching a slug at-or-before
     * `releaseId`. Slug lookup is index-by-data-slug since legacy rows may
     * predate slug presence in `data`. Returns null if no row found.
     */
    private latestEntityRowForSlug;
    /**
     * For each entity_id seen in the release range, return the latest version
     * row at-or-before `releaseId`. Rows where the underlying entity table has
     * since been DELETEd appear as op='delete' tombstones (data carries the
     * last snapshot before deletion).
     */
    private latestEntityRowsAtOrBefore;
    private latestPageRowsAtOrBefore;
    /**
     * Resolve a slug for an entity_version row. Snapshot data carries `slug`
     * directly (post-M17 rows). For pre-M17 legacy rows, fall back to the
     * underlying table — but the table may be deleted. Return null in that
     * case (caller skips the entry).
     */
    private resolveSlug;
}
