"""Deterministic client-operation resolution for generated applications.

The platform port catalog owns the transport and the generated SDK owns the
browser-facing names. Codegen may consume this surface, but it must never
invent a client helper or extend a platform adapter to make one appear.
"""

from __future__ import annotations

from typing import Any


# Requirement ids are intentionally allowed to use the product vocabulary used
# by App Blocks while resolving to the exact catalog port and SDK export.
CLIENT_OPERATION_REGISTRY: dict[str, dict[str, str]] = {
    "auth.me": {"port_id": "auth.me", "export": "me"},
    "auth.profile": {"port_id": "auth.profile", "export": "updateProfile"},
    "auth.sign_in": {"port_id": "auth.signin", "export": "signIn"},
    "auth.signin": {"port_id": "auth.signin", "export": "signIn"},
    "auth.sign_up": {"port_id": "auth.signup", "export": "signUp"},
    "auth.signup": {"port_id": "auth.signup", "export": "signUp"},
    "auth.sign_out": {"port_id": "auth.signout", "export": "signOut"},
    "auth.signout": {"port_id": "auth.signout", "export": "signOut"},
    "auth.magic_link": {
        "port_id": "auth.magic_link_request",
        "export": "requestMagicLink",
    },
    "auth.password_sign_in": {"port_id": "auth.signin", "export": "signIn"},
    # These remain explicit operations even while the current platform-port
    # catalog has not admitted them.  A password contract therefore fails
    # closed in preflight instead of making the generated app invent a reset
    # flow around a Cloud endpoint.
    "auth.password_reset_request": {
        "port_id": "auth.password_reset_request",
        "export": "requestPasswordReset",
    },
    "auth.password_reset_complete": {
        "port_id": "auth.password_reset",
        "export": "confirmPasswordReset",
    },
    "booking.resources.list": {
        "port_id": "bookings.list_resources",
        "export": "listBookingResources",
    },
    "booking.resources.upsert": {
        "port_id": "bookings.upsert_resource",
        "export": "upsertBookingResource",
    },
    "booking.availability": {
        "port_id": "bookings.availability",
        "export": "getBookingAvailability",
    },
    "booking.reserve": {"port_id": "bookings.reserve", "export": "reserveBooking"},
    "booking.get": {"port_id": "bookings.get", "export": "getBooking"},
    "booking.cancel": {"port_id": "bookings.cancel", "export": "cancelBooking"},
}

PRIMITIVE_TO_OPERATION = {
    "booking.reserve": "booking.reserve",
    "booking.cancel": "booking.cancel",
    "booking.availability": "booking.availability",
}


def required_client_operations(plan: dict[str, Any]) -> list[str]:
    """Derive client operations from an authoritative GenerationPlan only."""

    composition = plan.get("composition") or {}
    values = {
        str(value)
        for key in ("primitive_ids", "app_block_ids", "cloud_capability_ids")
        for value in (composition.get(key) or [])
        if value
    }
    operations = {PRIMITIVE_TO_OPERATION[value] for value in values if value in PRIMITIVE_TO_OPERATION}
    for value in values:
        if value in CLIENT_OPERATION_REGISTRY:
            operations.add(value)
    # Authentication is a client surface requirement whenever the plan says
    # authentication is required. The exact sign-in mode remains resolver-owned.
    if (plan.get("runtime") or {}).get("authentication_required"):
        operations.update({"auth.me", "auth.sign_in"})
        auth_contract = (plan.get("runtime") or {}).get("auth_contract") or {}
        methods = {
            method
            for audience in auth_contract.get("audiences") or []
            for method in audience.get("methods") or []
        }
        if "magic_link" in methods:
            operations.add("auth.magic_link")
        if "password" in methods:
            operations.update(
                {
                    "auth.password_sign_in",
                    "auth.password_reset_request",
                    "auth.password_reset_complete",
                }
            )
    return sorted(operations)


def resolve_client_surface(
    port_context: dict[str, Any], required_operations: list[str]
) -> dict[str, dict[str, Any]]:
    """Resolve required operations to admitted ports and exact SDK exports."""

    admitted = {
        str(port.get("port_id")): port
        for port in (port_context.get("ports") or [])
        if isinstance(port, dict) and port.get("port_id")
    }
    surface: dict[str, dict[str, Any]] = {}
    for operation in required_operations:
        spec = CLIENT_OPERATION_REGISTRY.get(operation)
        if not spec:
            surface[operation] = {
                "available": False,
                "reason": "no_canonical_operation_mapping",
            }
            continue
        port = admitted.get(spec["port_id"])
        surface[operation] = {
            "available": bool(port),
            "port_id": spec["port_id"],
            "export": spec["export"],
            "call": f"p2hs.{spec['export']}(...)" if port else None,
            "bff": (port or {}).get("bff") if port else None,
            "reason": None if port else "port_not_admitted",
        }
    return surface


def unavailable_client_operations(surface: dict[str, dict[str, Any]]) -> list[str]:
    return sorted(key for key, value in surface.items() if not value.get("available"))


__all__ = [
    "CLIENT_OPERATION_REGISTRY",
    "required_client_operations",
    "resolve_client_surface",
    "unavailable_client_operations",
]
