"""Build and check the element registry (stage 040 op).

WHY:
    Discovery produces a flat list of ElementRecords. The registry op turns
    that into an ElementRegistry and enforces the one structural invariant that
    spans elements: compact_id uniqueness. Mirrors stage 030's registry split
    (discover finds, registry indexes), keeping each op small.

OBS (reconciliation seam):
    Uniqueness of compact_id is exactly what makes by_compact_id() a sound
    reconciliation key for a later stage to compare against
    data/locks/elements.lock.json. The registry guarantees the key is unique;
    the reconciliation itself stays out of s040.
"""

from accountable_record.errors import StageInputDuplicate
from accountable_record.stage_040_elements.model import (
    ElementRecord,
    ElementRegistry,
)


def build_registry(records: list[ElementRecord]) -> ElementRegistry:
    """Build an ElementRegistry from discovered records.

    Args:
        records: Discovered element records, in discovery order.

    Returns:
        An :class:`ElementRegistry` preserving discovery order.

    Raises:
        StageInputDuplicate: Two records share one compact_id. The error's
            location is the duplicated compact_id.
    """
    seen: set[str] = set()
    for record in records:
        if record.compact_id in seen:
            raise StageInputDuplicate(
                f"Element compact_id {record.compact_id!r} is declared more than once",
                location=record.compact_id,
            )
        seen.add(record.compact_id)

    return ElementRegistry(records=tuple(records))
