"""The shared view — pin P1 of the NEOf1 lock: which bytes both sides hash identically.

`matching.compare()` already names the fields both members must agree on for one event
(`COMPARED`). This module hashes EXACTLY that projection — an extraction, not a new list,
so the two definitions of agreement cannot drift apart: two records of one event satisfy
`shared_view_hash(a) == shared_view_hash(b)` precisely when `compare(a, b)` finds no
differing field.

Three rules, all inherited rather than invented:

  * The projection keeps a compared field only when the record carries it. A field
    present on one side and absent on the other yields different projections — the same
    rule `compare()` applies: an invoice with an amount and an invoice without one are
    not the same invoice.
  * Hashing is domain-separated SHA-256 over RFC 8785 canonical bytes, the one
    canonicalisation every other derivation in the grammar uses.
  * The agreement root is an RFC 6962 Merkle root — the same leaf/node discipline as
    `snapshot.py` — over the relation-period's `(event_key, shared_view_hash)` pairs in
    sorted order, so both sides build the identical tree without talking. Equal roots ⇒
    the entire period of that relation agrees; one comparison settles however many
    events stood behind it.

Tree separation: an agreement leaf's preimage is `0x00 ‖ event_key(32) ‖ view_hash(32)`
(65 B), a snapshot leaf's is `0x00 ‖ record_hash(32)` (33 B), an internal node's is
`0x01 ‖ left ‖ right` — length and prefix keep the three shapes from ever being confused.
"""

from __future__ import annotations

import hashlib

from .canonical import canonical_bytes
from .matching import COMPARED
from .snapshot import LEAF_PREFIX, merkle_root

DOMAIN_SHARED_VIEW = b"NEOEDGES:SHARED-VIEW:V1"


def shared_view(record: dict) -> dict:
    """The record's shared-view projection: exactly `matching`'s compared fields, and only
    those the record actually carries."""
    return {name: record[name] for name in COMPARED if name in record}


def shared_view_hash(record: dict) -> bytes:
    """32 raw bytes: SHA-256(domain ‖ canonical bytes of the projection).

    Raw bytes, not hex — this value exists to ride the wire (`EVENT_PAIR`, the agreement
    root), where hashes are carried as 32 bytes, never 64 characters.
    """
    return hashlib.sha256(DOMAIN_SHARED_VIEW + canonical_bytes(shared_view(record))).digest()


def agreement_leaf(event_key_hex: str, view_hash: bytes) -> bytes:
    """One leaf of the agreement tree: the RFC 6962 leaf prefix over the pair's 64 raw bytes."""
    return hashlib.sha256(LEAF_PREFIX + bytes.fromhex(event_key_hex) + view_hash).digest()


def agreement_root(pairs) -> bytes:
    """The 32 B root over `(event_key_hex, shared_view_hash)` pairs, sorted, as raw bytes.

    Sorting is by the pair's bytes, so insertion order — which each ERP dictates
    differently — cannot move the root. Both parties compute this independently over
    their own book; equal roots settle the period, unequal roots hand the dispute to
    Merkle bisection through `EVENT_PAIR` objects.

    The empty pair set has the defined empty root but is never emitted: a relation with
    no events in a period produces no attest at all — silence is free.
    """
    ordered = sorted((bytes.fromhex(key), view) for key, view in pairs)
    leaves = [hashlib.sha256(LEAF_PREFIX + key + view).digest() for key, view in ordered]
    return bytes.fromhex(merkle_root(leaves))
