"""Sublocation registry — read-only plumbing for decomposed locations.

REC-111 PR-3, updated by REC-134 Phase 7. A *decomposed* location ships a
``base/location.json`` registry that enumerates its allowed sublocations (each
with a derived establishing-ref relative to ``base/`` plus the sha256 of the
bible sublocation description) and an adjacency graph. A location WITHOUT a
``location.json`` is *undecomposed*: nothing is constrained and the loader
returns ``None`` (everything permitted).

Consumers:
  - Phase 3 ``world_state_pass`` reads the registry's ``sublocations`` map.
  - Phase 5 ``spatial_validator`` reads the registry + resolves refs via
    ``location_base_dir`` / ``sublocation_ref`` and validates them with
    ``validate_ref_file``.

Nothing WRITES the registry except ``tools/gen_sublocations.py``.
``tools/migrate_sublocations.py`` was retired on 2026-06-11 because it stamped
the old plate-hash meaning into the same ``source_sha256`` field.
"""

from __future__ import annotations

import json
from pathlib import Path


def location_base_dir(project_paths, location_id: str) -> Path:
    """The ``assets/loc/{location_id}/base/`` dir.

    Single source for the registry-ref join below; exported so the Phase 5
    validator never re-derives the path.
    """
    return project_paths.asset_look_dir("loc", location_id, "base")


def load_location_registry(project_paths, location_id: str) -> dict | None:
    """Parse ``assets/loc/{location_id}/base/location.json``.

    Returns the parsed dict, or ``None`` when the file is absent (absent =
    location not decomposed = everything permitted).
    """
    path = location_base_dir(project_paths, location_id) / "location.json"
    if not path.is_file():
        return None
    return json.loads(path.read_text())


def sublocation_ref(project_paths, location_id: str, name: str) -> Path | None:
    """Resolve a registry entry's relative ``ref`` against the location base dir.

    Returns ``None`` if the registry or the named entry is missing.
    """
    registry = load_location_registry(project_paths, location_id)
    if not registry:
        return None
    entry = (registry.get("sublocations") or {}).get(name)
    if not entry or not entry.get("ref"):
        return None
    return location_base_dir(project_paths, location_id) / entry["ref"]


def validate_ref_file(path: Path) -> str | None:
    """Return an error string if ``path`` is not a usable image ref, else None.

    Flags, in order: a MISSING file, a file smaller than 1KB, or bytes that do
    not decode as an image via PIL ``Image.open`` + ``verify()`` (the REC-125
    stub class — a text/placeholder masquerading as an image).
    """
    if not path.is_file():
        return f"missing ref file: {path}"
    size = path.stat().st_size
    if size < 1024:
        return f"ref file too small ({size}B < 1KB): {path}"
    try:
        from PIL import Image

        with Image.open(path) as img:
            img.verify()
    except Exception as exc:  # noqa: BLE001 — any decode failure means invalid ref
        return f"ref file not a decodable image: {path} ({exc})"
    return None
