"""Combined Bifrost provider (issue #18 Deliverable 1): ONE ASGI app fronting BOTH
the memory.* and affect.* planes, so a single bound Worldtree session both remembers
AND shows live PAD.

Contract: docs/contracts/issues/18.contract.md (§ Deliverable 1)

Wraps `bifrost.consumer.build_combined_app` (bifrost >=0.10.0) over our real
SQLite-backed stores and mounts the SAME non-bifrost affect read route as the
standalone affect provider (the shared `add_affect_read_route` helper, INV-011). The
composite advertises both caps by store PRESENCE at the handshake; per-plane failure
isolation is bifrost's per-route call-time dispatch isolation (INV-013). It is
ADDITIVE — the standalone :8390/:8391 apps are unchanged (INV-014).
"""
from __future__ import annotations

from bifrost.consumer import ConsumerRegistration, build_combined_app
from bifrost.reference_server import JwtVerifier

from ratatoskr.provider.affect_store import RatatoskrAffectStore, add_affect_read_route
from ratatoskr.provider.memory_store import RatatoskrMemoryStore


def build_combined_provider_app(
    memory_store: RatatoskrMemoryStore,
    affect_store: RatatoskrAffectStore,
    heimdall_key: bytes,
    consumer_id: str = "ratatoskr",
):
    """Compose `build_combined_app` over BOTH stores + mount the shared affect read
    route. Returns a Starlette app exposing POST /bifrost/handshake +
    /bifrost/memory-call + /bifrost/affect-call + GET /affect/state/{agent_id}.

    Both stores are REQUIRED (INV-009): bifrost's build_combined_app raises if either
    is None. The affect cap depends on the affect store advertising affect_supported +
    emit + fetch (strong-or-absent, INV-010) — guarded here at build time so a
    misconfigured store fails fast rather than silently withholding the cap.
    """
    if getattr(affect_store, "affect_supported", False) is not True:  # PRE-001 / INV-010
        raise ValueError("affect_store must advertise affect_supported=True")
    if not (isinstance(heimdall_key, bytes) and heimdall_key):  # PRE-002
        raise ValueError("heimdall_key must be non-empty bytes")
    verifier = JwtVerifier(algorithm="HS256", key_bytes=heimdall_key)
    registration = ConsumerRegistration(consumer_id=consumer_id)
    # build_combined_app validates memory_store/affect_store presence (INV-009, raises
    # ValueError on None) and mounts handshake + memory-call + affect-call (no tool-call).
    app = build_combined_app(memory_store, affect_store, verifier, registration)
    add_affect_read_route(app, affect_store)  # INV-011: the SAME read route, same db
    return app
