"""Bundled Claude provider catalog."""
from typing import Any, Mapping
from okstra_ctl.domain.provider import (
    LeadLaunchSpec,
    ModelSpec,
    ProviderSpec,
    ServedModelAttestation,
)
from okstra_ctl.domain.role import ALL_ROLE_TOKENS
from okstra_ctl.domain.worker_exec import (
    ExecCommand,
    PolicySupport,
    WorkerExecRequest,
)
from okstra_ctl.domain.worker_presentation import JsonEvents
from okstra_ctl.domain.worker_stream import content_block_events


CLAUDE = {
    "fable": ModelSpec(
        "fable", "fable", "fable", version_kind="channel", channel_family="fable"
    ),
    "fable-5": ModelSpec(
        "fable-5", "fable-5", "claude-fable-5", aliases=("claude-fable-5",),
        channel_family="fable",
    ),
    "opus": ModelSpec(
        "opus", "opus", "opus", version_kind="channel", channel_family="opus"
    ),
    "opus-5": ModelSpec(
        "opus-5", "opus-5", "claude-opus-5", aliases=("claude-opus-5",),
        channel_family="opus",
    ),
    "opus-4-8": ModelSpec(
        "opus-4-8", "opus-4-8", "claude-opus-4-8", aliases=("claude-opus-4-8",),
        channel_family="opus",
    ),
    "opus-4-7": ModelSpec(
        "opus-4-7", "opus-4-7", "claude-opus-4-7", aliases=("claude-opus-4-7",),
        channel_family="opus",
    ),
    "opus-4-6": ModelSpec(
        "opus-4-6", "opus-4-6", "claude-opus-4-6", aliases=("claude-opus-4-6",),
        channel_family="opus",
    ),
    "sonnet": ModelSpec(
        "sonnet", "sonnet", "sonnet", version_kind="channel", channel_family="sonnet"
    ),
    "sonnet-5": ModelSpec(
        "sonnet-5", "sonnet-5", "claude-sonnet-5", aliases=("claude-sonnet-5",),
        channel_family="sonnet",
    ),
    "sonnet-4-6": ModelSpec(
        "sonnet-4-6", "sonnet-4-6", "claude-sonnet-4-6", aliases=("claude-sonnet-4-6",),
        channel_family="sonnet",
    ),
    "haiku": ModelSpec(
        "haiku", "haiku", "haiku", version_kind="channel", channel_family="haiku"
    ),
    "haiku-4-5": ModelSpec(
        "haiku-4-5", "haiku-4-5", "claude-haiku-4-5", aliases=("claude-haiku-4-5",),
        channel_family="haiku",
    ),
    "haiku-4-5-20251001": ModelSpec(
        "haiku-4-5-20251001", "haiku-4-5", "claude-haiku-4-5-20251001",
        channel_family="haiku",
    ),
}


def normalise_served_model(raw_model: str | None) -> ServedModelAttestation:
    if not raw_model or not raw_model.strip():
        return ServedModelAttestation.unknown()
    model_id = raw_model.strip().lower().removeprefix("claude-")
    spec = CLAUDE.get(model_id)
    level = (
        "channel"
        if spec is not None and spec.version_kind == "channel"
        else "exact"
    )
    return ServedModelAttestation(
        raw_model, f"claude/{model_id}", level, "provider-output"
    )


def observe_served_model(event: Mapping[str, Any]) -> str | None:
    if event.get("type") == "system" and event.get("subtype") == "init":
        model = event.get("model")
        return model if isinstance(model, str) and model.strip() else None
    message = event.get("message")
    if isinstance(message, Mapping):
        model = message.get("model")
        return model if isinstance(model, str) and model.strip() else None
    return None


# Opening the approval gate for a non-interactive run. Without it this CLI
# auto-denies any tool call outside the seeded allowlist and the worker silently
# routes around the denial, which reads as a thinner analysis rather than as a
# failure. The value is taken from the CLI's own help text; it has not been
# observed end to end, which is why policy_support claims no write boundary.
_APPROVAL_ARGS = ("--permission-mode", "bypassPermissions")


class ClaudeExecution:
    """claude CLI invocation.

    Working directory is the project root even when a stage worktree exists:
    this CLI attaches the worktree with `--add-dir` and runs from the root,
    unlike grok/kimi which run inside the worktree.
    """

    def build_command(self, request: WorkerExecRequest) -> ExecCommand:
        argv = ["claude", "-p", "--model", request.model]
        # The dispatcher's id, not the CLI's own: it is what team-state records,
        # so token collection reads this worker's jsonl instead of guessing at
        # `agentName` — which a pane-dispatched CLI never writes.
        if request.session_id:
            argv += ["--session-id", request.session_id]
        for directory in request.policy.write_scope:
            if directory != request.project_root:
                argv += ["--add-dir", str(directory)]
        if request.policy.auto_approve:
            argv += list(_APPROVAL_ARGS)
        # `--verbose` is mandatory, not cosmetic: Claude Code rejects `--print`
        # combined with `--output-format=stream-json` without it and exits 1 in
        # under a second.
        argv += ["--output-format=stream-json", "--verbose"]
        return ExecCommand(
            argv=tuple(argv),
            stdin_text=request.prompt_text,
            cwd=request.project_root,
            presentation=JsonEvents(
                normalise=content_block_events,
                observe=observe_served_model,
            ),
        )

    def policy_support(self) -> PolicySupport:
        return PolicySupport(
            can_auto_approve=True,
            can_bound_write_scope=False,
            note=(
                "the approval flag is taken from help text and has not been "
                "observed end to end; no flag is known to bound this provider's "
                "writes, so none is claimed"
            ),
        )


def create_provider() -> ProviderSpec:
    return ProviderSpec(
        provider="claude",
        display_label="Claude",
        models=CLAUDE,
        default_models={
            "lead": "opus",
            "analyser": "opus",
            "critic": "opus",
            "designer": "opus",
            "planner": "opus",
            "executor": "opus",
            "verifier": "opus",
            "report-writer": "sonnet",
            "translator": "sonnet",
        },
        wrapper="okstra-claude-exec.sh",
        supported_roles=ALL_ROLE_TOKENS,
        execution_capabilities=frozenset(
            {
                "lead-session",
                "worker-artifact-io",
                "source-readonly",
                "extended-artifact-authoring",
                "project-mutation",
            }
        ),
        lead_launch=LeadLaunchSpec(
            executable="claude",
            model_flag="--model",
            start_session_id_flag="--session-id",
            resume_session_id_flag="--resume",
        ),
        exec_strategy=ClaudeExecution(),
        served_model_normalizer=normalise_served_model,
    )
