#!/usr/bin/env python
"""Content-addressed candidate caches for one-shot hybrid regeneration.

The full-sequence candidate cache deliberately treats the complete edit as one
planning contract.  Selected-shot regeneration has a narrower contract: the
base edit freezes every other shot and supplies the selected shot's sequence
context.  This module keeps those artifacts in a separate root so a one-shot
entry can never be mistaken for a complete candidate-pool bundle.

Payload encoding and integrity checks are delegated to
``hybrid_candidate_cache``.  Its NPZ/joblib split already preserves numeric
camera arrays efficiently and validates every member before trusted local
deserialization.
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Any, Mapping

import hybrid_candidate_cache as candidate_cache


SINGLE_SHOT_CACHE_CONTRACT_VERSION = "hybrid-single-shot-candidate-v1"


@dataclass(frozen=True)
class SingleShotCacheLoadResult:
    """Validated result for one requested shot cache lookup."""

    status: str
    cache_key: str
    path: Path
    shot: candidate_cache.CandidatePoolShot | None = None
    error: str | None = None

    @property
    def hit(self) -> bool:
        return self.status == "hit" and self.shot is not None


def build_single_shot_cache_identity(
    *,
    input_fingerprint: Any,
    preparation_fingerprint: Any,
    base_context: Any,
    planner_directive: Any,
    controls: Any,
    stage_versions: Mapping[str, Any],
) -> dict[str, Any]:
    """Build a Seed-independent identity for one selected-shot pool."""

    return candidate_cache.build_candidate_cache_identity(
        input_fingerprint=input_fingerprint,
        preparation_fingerprint=preparation_fingerprint,
        directives={
            "contractVersion": SINGLE_SHOT_CACHE_CONTRACT_VERSION,
            "baseContext": base_context,
            "plannerDirective": planner_directive,
        },
        controls=controls,
        stage_versions=stage_versions,
    )


def try_load_single_shot_cache(
    cache_root: Path,
    identity: Mapping[str, Any],
    *,
    shot_index: int,
    start_frame: int,
    end_frame_exclusive: int,
) -> SingleShotCacheLoadResult:
    """Load one exact shot bundle and reject full or mismatched bundles."""

    loaded = candidate_cache.try_load_cache_bundle(
        cache_root,
        identity,
        trust_pickle=True,
    )
    if not loaded.hit:
        return SingleShotCacheLoadResult(
            status=loaded.status,
            cache_key=loaded.cache_key,
            path=loaded.path,
            error=loaded.error,
        )
    try:
        if loaded.loaded is None:
            raise candidate_cache.CandidateCacheIntegrityError(
                "Single-shot cache hit has no loaded payload."
            )
        shots = loaded.loaded.shots
        metadata = loaded.loaded.bundle_metadata
        if len(shots) != 1:
            raise candidate_cache.CandidateCacheIntegrityError(
                "Single-shot cache must contain exactly one shot."
            )
        shot = shots[0]
        expected_span = (int(start_frame), int(end_frame_exclusive))
        if (
            int(shot.shot_index) != int(shot_index)
            or (
                int(shot.start_frame),
                int(shot.end_frame_exclusive),
            )
            != expected_span
        ):
            raise candidate_cache.CandidateCacheIntegrityError(
                "Single-shot cache index or frame span does not match."
            )
        if (
            not isinstance(metadata, dict)
            or metadata.get("contractVersion")
            != SINGLE_SHOT_CACHE_CONTRACT_VERSION
            or metadata.get("shotIndex") != int(shot_index)
            or metadata.get("startFrame") != expected_span[0]
            or metadata.get("endFrameExclusive") != expected_span[1]
        ):
            raise candidate_cache.CandidateCacheIntegrityError(
                "Single-shot cache runtime contract is invalid."
            )
        return SingleShotCacheLoadResult(
            status="hit",
            cache_key=loaded.cache_key,
            path=loaded.path,
            shot=shot,
        )
    except (
        candidate_cache.CandidateCacheError,
        TypeError,
        ValueError,
    ) as error:
        return SingleShotCacheLoadResult(
            status="invalid",
            cache_key=loaded.cache_key,
            path=loaded.path,
            error=f"{type(error).__name__}: {error}",
        )


def publish_single_shot_cache(
    cache_root: Path,
    identity: Mapping[str, Any],
    shot: candidate_cache.CandidatePoolShot,
    *,
    summary: Mapping[str, Any] | None = None,
) -> candidate_cache.CandidateCacheBundleInfo:
    """Atomically publish one immutable selected-shot candidate pool."""

    return candidate_cache.publish_cache_bundle(
        cache_root,
        identity,
        [shot],
        bundle_metadata={
            "contractVersion": SINGLE_SHOT_CACHE_CONTRACT_VERSION,
            "shotIndex": int(shot.shot_index),
            "startFrame": int(shot.start_frame),
            "endFrameExclusive": int(shot.end_frame_exclusive),
        },
        summary=dict(summary or {}),
    )


def quarantine_single_shot_cache(
    path: Path,
    cache_key: str,
) -> Path | None:
    """Quarantine an invalid local entry before an automatic rebuild."""

    if not Path(path).exists():
        return None
    return candidate_cache.quarantine_cache_bundle(path, cache_key)
