"""Typed JSON and target primitives for the V1.5 live package."""

from __future__ import annotations
import hashlib
import json
from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
from typing import TypeAlias, cast

JsonValue: TypeAlias = (
    str | int | float | bool | None | list["JsonValue"] | dict[str, "JsonValue"]
)
ServiceReader: TypeAlias = Callable[[str], dict[str, str]]


@dataclass(frozen=True, slots=True)
class LiveTarget:
    profile_root: Path
    profiles_root: Path
    service_name: str
    unit_file: Path
    dropin_dir: Path


class UpgradeDenied(RuntimeError):
    """Stable fail-closed denial."""


def canonical(value: JsonValue) -> bytes:
    return json.dumps(
        value,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
        allow_nan=False,
    ).encode()


def sha256_bytes(payload: bytes) -> str:
    return hashlib.sha256(payload).hexdigest()


def sha256_file(path: Path) -> str:
    return sha256_bytes(path.read_bytes())


def load_object(path: Path) -> dict[str, JsonValue]:
    try:
        value = cast(JsonValue, json.loads(path.read_text(encoding="utf-8")))
    except (OSError, UnicodeError, json.JSONDecodeError) as exc:
        raise UpgradeDenied(f"invalid_json:{path.name}") from exc
    if not isinstance(value, dict):
        raise UpgradeDenied(f"invalid_object:{path.name}")
    return value


def object_at(value: JsonValue | None, label: str) -> dict[str, JsonValue]:
    if not isinstance(value, dict):
        raise UpgradeDenied(f"invalid_{label}")
    return value


def list_at(value: JsonValue | None, label: str) -> list[JsonValue]:
    if not isinstance(value, list):
        raise UpgradeDenied(f"invalid_{label}")
    return value


def rows_at(value: JsonValue | None, label: str) -> list[JsonValue]:
    if isinstance(value, dict):
        return list(value.values())
    return list_at(value, label)


def string_at(value: JsonValue | None, label: str) -> str:
    if not isinstance(value, str):
        raise UpgradeDenied(f"invalid_{label}")
    return value
