# ============================================================
# src/accountable_record/transitions/s070_resolution/s080_generated_to_lock.py
# ============================================================
"""Build s070 resolution lock payloads from s080 generated artifacts.

WHY:
    This module is a cross-stage producer. It translates generated artifact
    records from s080 into the elements-lock payload verified by s070.

    The command layer decides when to write the lock file. Stage 070 owns the
    lock vocabulary and verification rules. Stage 080 owns generated artifact
    collection. This module owns the transition from generated records to the
    resolution-lock payload, including digest computation over the generated
    artifact content.

OBS:
    This module may import both s070 and s080 because transition modules are
    explicitly allowed to span the stage boundary they realize.

OBS:
    Verify-side reconciliation remains in the validating stage for now.
    This module is only the write-side producer for the generated-to-resolution
    lock transition.

OBS:
    GeneratedElementRecord is intentionally passive and does not carry digest.
    The lock producer reads the generated/exported element artifact and computes
    the canonical-generated-json digest when producing the lock.

OBS (provisional package semantics):
    Package resolution is not yet modeled here. Until package locks and package
    inclusion are implemented, package records are empty, element version is the
    provisional project version, and included_by is empty. These are current
    lock-shape fields, not final package-resolution semantics.
"""

from datetime import UTC, datetime
import json
from pathlib import Path

from accountable_record.boundary.json import is_table
from accountable_record.stage_070_resolution.base import constants
from accountable_record.stage_070_resolution.model import ResolutionPolicy
from accountable_record.stage_080_generated.base.scalars import canonical_json_digest
from accountable_record.stage_080_generated.model import GeneratedElementRecord

__all__ = [
    "DEFAULT_ELEMENT_VERSION",
    "DEFAULT_RESOLUTION_POLICY",
    "build_element_lock_payload",
]

DEFAULT_ELEMENT_VERSION = "0.1.0"

# Generated records are derived from stage 060 export records, whose rel_dir is
# the directory containing the exported element JSON artifact.
GENERATED_ELEMENT_FILENAME = "element.json"

DEFAULT_RESOLUTION_POLICY = ResolutionPolicy(
    one_major_version_per_identity_per_graph=True,
    range_conflicts_are_errors=True,
)


def build_element_lock_payload(
    generated_records: tuple[GeneratedElementRecord, ...],
    *,
    repo_root: Path,
) -> dict[str, object]:
    """Build an elements-lock payload from generated artifact records.

    Args:
        generated_records: Generated element artifact records collected by s080.
        repo_root: Target repository root containing the generated/export tree.

    Returns:
        JSON-serializable payload for data/locks/elements.lock.json.
    """
    return {
        constants.LOCK_KEY_SCHEMA: constants.ELEMENT_LOCK_SCHEMA_TAG,
        constants.LOCK_KEY_RESOLVED_AT: _resolved_at_utc(),
        constants.LOCK_KEY_RESOLUTION_POLICY: _resolution_policy_payload(
            DEFAULT_RESOLUTION_POLICY
        ),
        constants.LOCK_KEY_PACKAGES: [],
        constants.LOCK_KEY_ELEMENTS: [
            _element_lock_payload(repo_root=repo_root, record=record)
            for record in generated_records
        ],
    }


def _element_lock_payload(
    *,
    repo_root: Path,
    record: GeneratedElementRecord,
) -> dict[str, object]:
    """Return one element-lock payload entry."""
    generated_json = _read_generated_element_json(repo_root=repo_root, record=record)

    return {
        constants.ELEMENT_KEY_COMPACT_ID: record.compact_id,
        constants.ELEMENT_KEY_VERSION: DEFAULT_ELEMENT_VERSION,
        constants.ELEMENT_KEY_INCLUDED_BY: [],
        constants.ELEMENT_KEY_DIGEST_ALGORITHM: constants.DIGEST_ALGORITHM_SHA256,
        constants.ELEMENT_KEY_DIGEST_TARGET: (
            constants.DIGEST_TARGET_CANONICAL_GENERATED_JSON
        ),
        constants.ELEMENT_KEY_DIGEST: canonical_json_digest(generated_json),
    }


def _read_generated_element_json(
    *,
    repo_root: Path,
    record: GeneratedElementRecord,
) -> dict[str, object]:
    """Read the generated/exported element JSON object for digesting."""
    path = repo_root / record.rel_dir / GENERATED_ELEMENT_FILENAME

    with path.open("r", encoding="utf-8") as handle:
        parsed = json.load(handle)

    if not is_table(parsed):
        msg = f"{path} must contain a JSON object"
        raise ValueError(msg)

    return parsed


def _resolution_policy_payload(policy: ResolutionPolicy) -> dict[str, bool]:
    """Return the JSON payload for a resolution policy."""
    return {
        constants.POLICY_ONE_MAJOR_VERSION_PER_IDENTITY_PER_GRAPH: (
            policy.one_major_version_per_identity_per_graph
        ),
        constants.POLICY_RANGE_CONFLICTS_ARE_ERRORS: (
            policy.range_conflicts_are_errors
        ),
    }


def _resolved_at_utc() -> str:
    """Return the lock resolution timestamp in UTC ISO-8601 form."""
    return datetime.now(UTC).replace(microsecond=0).isoformat()
