"""Deterministic prompt bundle compiler.

Compiles a validated prompt bundle into a canonical artifact with a
SHA-256 digest that is stable across reorderings, whitespace variations,
and timestamps.

Migrated from abi-promptware/compiler.py + schema.py.
"""

from __future__ import annotations

import hashlib
import json
import re
from datetime import UTC, datetime
from pathlib import Path
from typing import Any

import jsonschema

# ---------------------------------------------------------------------------
# Schema loading
# ---------------------------------------------------------------------------

_BUNDLE_SCHEMA_PATH = Path(__file__).resolve().parent / "schemas" / "prompt_bundle.schema.json"


def load_bundle_schema() -> dict:
    """Load and return the prompt bundle JSON Schema."""
    return json.loads(_BUNDLE_SCHEMA_PATH.read_text(encoding="utf-8"))


def validate_bundle(bundle: dict) -> None:
    """Validate a prompt bundle against the schema.

    Raises jsonschema.ValidationError on invalid input.
    """
    schema = load_bundle_schema()
    jsonschema.validate(instance=bundle, schema=schema)


# ---------------------------------------------------------------------------
# Compiler
# ---------------------------------------------------------------------------

_VARIABLE_RE = re.compile(r"\{\{(\w+)\}\}")


def _normalize_whitespace(text: str) -> str:
    """Collapse runs of whitespace to single spaces, strip edges."""
    return re.sub(r"\s+", " ", text).strip()


def _extract_template_variables(template: str) -> set[str]:
    """Return all {{var}} references found in a template string."""
    return set(_VARIABLE_RE.findall(template))


def _validate_variable_usage(prompts: list[dict[str, Any]]) -> None:
    """Reject undeclared or unused variables."""
    for prompt in prompts:
        declared = set(prompt["variables"])
        used = _extract_template_variables(prompt["template"])

        undeclared = used - declared
        if undeclared:
            raise ValueError(
                f"Prompt '{prompt['prompt_id']}' references undeclared "
                f"variable(s): {sorted(undeclared)}"
            )

        unused = declared - used
        if unused:
            raise ValueError(
                f"Prompt '{prompt['prompt_id']}' declares unused "
                f"variable(s): {sorted(unused)}"
            )


def compile_bundle(
    bundle: dict[str, Any],
    *,
    deterministic_ts: str | None = None,
) -> dict[str, Any]:
    """Compile a validated prompt bundle into a canonical artifact.

    Steps:
    1. Validate against schema
    2. Validate variable usage (undeclared/unused)
    3. Sort prompts by prompt_id
    4. Normalize whitespace in each template
    5. Sort variables alphabetically within each prompt
    6. Produce compiled artifact with SHA-256 of canonical JSON
    """
    validate_bundle(bundle)
    _validate_variable_usage(bundle["prompts"])

    sorted_prompts = sorted(bundle["prompts"], key=lambda p: p["prompt_id"])

    compiled_prompts: list[dict[str, Any]] = []
    all_variables: set[str] = set()

    for prompt in sorted_prompts:
        normalized_template = _normalize_whitespace(prompt["template"])
        sorted_vars = sorted(prompt["variables"])
        all_variables.update(sorted_vars)

        compiled_prompts.append({
            "prompt_id": prompt["prompt_id"],
            "role": prompt["role"],
            "template": normalized_template,
            "variables": sorted_vars,
            "constraints": prompt["constraints"],
        })

    hash_payload = {
        "bundle_id": bundle["bundle_id"],
        "bundle_version": bundle["bundle_version"],
        "compiled_prompts": compiled_prompts,
    }
    canonical_json = json.dumps(hash_payload, sort_keys=True, separators=(",", ":"))
    sha256 = hashlib.sha256(canonical_json.encode("utf-8")).hexdigest()

    compiled_at = deterministic_ts or datetime.now(UTC).isoformat()

    return {
        "schema_version": "compiled-prompt-bundle/1.0",
        "bundle_id": bundle["bundle_id"],
        "bundle_version": bundle["bundle_version"],
        "compiled_prompts": compiled_prompts,
        "sha256": sha256,
        "compiled_at": compiled_at,
        "variables_declared": sorted(all_variables),
    }
