"""Canonical accessor for take identity.

Per data-contracts.md §2b: PassStore + ExecutionStore + StepRunner write
`take_number` (1-based, monotonic). The CP-7 in-memory Take dataclass uses
`take_index` (0-based). Some legacy records on disk may have ONLY
`take_index`. This module normalizes reads.

Canonical: `take_number` (1-based). Use `read_take_number(take_dict)` to
fetch from any take dict regardless of which key is present.
"""

from __future__ import annotations

from typing import Any


class TakeNumberMissingError(KeyError):
    """Raised when a take dict has no recoverable take number."""
    pass


def read_take_number(take_dict: dict[str, Any]) -> int:
    """Return the 1-based take number from a take dict.

    Resolution order:
      1. `take_number` (canonical write-side key).
      2. `take_index` + 1 (legacy / in-memory CP-7 dataclass round-trip).
      3. raise TakeNumberMissingError.
    """
    if "take_number" in take_dict:
        n = take_dict["take_number"]
        if not isinstance(n, int):
            raise TakeNumberMissingError(
                f"take_number present but non-int: {n!r} ({type(n).__name__})"
            )
        if n < 1:
            raise TakeNumberMissingError(
                f"take_number must be >= 1 (1-based); got {n}"
            )
        return n
    if "take_index" in take_dict:
        i = take_dict["take_index"]
        if not isinstance(i, int):
            raise TakeNumberMissingError(
                f"take_index present but non-int: {i!r}"
            )
        if i < 0:
            raise TakeNumberMissingError(
                f"take_index must be >= 0 (0-based); got {i}"
            )
        return i + 1
    raise TakeNumberMissingError(
        f"take dict has neither 'take_number' nor 'take_index': "
        f"keys={sorted(take_dict.keys())!r}"
    )


def read_take_index(take_dict: dict[str, Any]) -> int:
    """Return the 0-based take index. Inverse of read_take_number()."""
    if "take_index" in take_dict:
        i = take_dict["take_index"]
        if not isinstance(i, int) or i < 0:
            raise TakeNumberMissingError(
                f"take_index must be int >= 0; got {i!r}"
            )
        return i
    if "take_number" in take_dict:
        return read_take_number(take_dict) - 1
    raise TakeNumberMissingError(
        f"take dict has neither key; keys={sorted(take_dict.keys())!r}"
    )


__all__ = [
    "read_take_number",
    "read_take_index",
    "TakeNumberMissingError",
]
