"""The SINGLE SOURCE of the game's vocabulary + color language + court configs.

Transcribed VERBATIM from docs/review-patterns-ref.md §1 (which itself
transcribes the review app's src/lib/types.ts + src/lib/status-styles.ts and
the final CHECK constraints), plus the §2.1/§2.2 status<->review coupling map
and the plan-v1.md §2 court configurations.

Nothing anywhere re-types these values: the DB migration CHECKs (when added),
the service-layer automations, the seed loader, and the frontend (via
GET /api/meta) all consume THIS module (frontend.md: enums from one authority).

Casing note (review-patterns-ref §3.1): PAGE_STATUSES and SIGNOFF_OPTIONS are
stored all-lowercase — display casing comes from a CSS `capitalize` utility on
the consuming component, never from the stored string. NOTE_CATEGORIES and
ISSUE_STATUSES are Title Case already and get no `capitalize` class. The
`capitalize` flag in meta_payload() carries this rule to the frontend.
"""
from __future__ import annotations

# ---------------------------------------------------------------------------
# 1.1 Page Status — balls.status on pages/features courts (UI column "Status")
# ---------------------------------------------------------------------------

PAGE_STATUSES = (
    "open",
    "creating",
    "finalizing",
    "revision pending",
    "revising",
    "drafted",
    "completed",
)
DEFAULT_PAGE_STATUS = "open"

STATUS_STYLES = {
    "open": "bg-muted text-muted-foreground border-border",
    "creating": "bg-orange-100 text-orange-800 border-orange-200 dark:bg-orange-950 dark:text-orange-200 dark:border-orange-900",
    "finalizing": "bg-yellow-100 text-yellow-800 border-yellow-200 dark:bg-yellow-950 dark:text-yellow-200 dark:border-yellow-900",
    # grey-yellow / khaki (raw hex triad — Tailwind has no built-in khaki)
    "revision pending": "bg-[#ebe7cd] text-[#6d6837] border-[#d8d2a6] dark:bg-[#3a3720] dark:text-[#cbc488] dark:border-[#55502d]",
    # chartreuse
    "revising": "bg-lime-100 text-lime-800 border-lime-200 dark:bg-lime-950 dark:text-lime-200 dark:border-lime-900",
    "drafted": "bg-green-100 text-green-800 border-green-200 dark:bg-green-950 dark:text-green-200 dark:border-green-900",
    "completed": "bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-950 dark:text-blue-200 dark:border-blue-900",
}

STATUS_DOT = {
    "open": "bg-muted-foreground/50",
    "creating": "bg-orange-500",
    "finalizing": "bg-yellow-500",
    "revision pending": "bg-[#b8ad5e]",
    "revising": "bg-lime-500",
    "drafted": "bg-green-500",
    "completed": "bg-blue-500",
}

# ---------------------------------------------------------------------------
# 1.2 Review — balls.review_state (review app DB column `signoff`, UI "Review")
#
# Color mirroring keeper: in the revision sub-loop, khaki always means
# "waiting on the other column", lime always means "the actively-happening
# state" — status khaki (revision pending) pairs with review lime (revision
# needed); status lime (revising) pairs with review khaki (revision not
# ready). The khaki hex triad is shared IDENTICALLY between the two vocabs,
# deliberately (the two closest-related concepts share a color).
# ---------------------------------------------------------------------------

SIGNOFF_OPTIONS = (
    "not ready",
    "ready for review",
    "revision not ready",
    "revision needed",
    "approved",
)
DEFAULT_SIGNOFF = "not ready"

SIGNOFF_STYLES = {
    "not ready": "bg-muted text-muted-foreground border-border",
    "ready for review": "bg-orange-100 text-orange-800 border-orange-200 dark:bg-orange-950 dark:text-orange-200 dark:border-orange-900",
    # yellow-grey / khaki (same hex triad as status "revision pending")
    "revision not ready": "bg-[#ebe7cd] text-[#6d6837] border-[#d8d2a6] dark:bg-[#3a3720] dark:text-[#cbc488] dark:border-[#55502d]",
    "revision needed": "bg-lime-100 text-lime-800 border-lime-200 dark:bg-lime-950 dark:text-lime-200 dark:border-lime-900",
    "approved": "bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-950 dark:text-blue-200 dark:border-blue-900",
}

SIGNOFF_DOT = {
    "not ready": "bg-muted-foreground/50",
    "ready for review": "bg-orange-500",
    "revision not ready": "bg-[#b8ad5e]",
    "revision needed": "bg-lime-500",
    "approved": "bg-blue-500",
}

# ---------------------------------------------------------------------------
# 1.3 Category — balls.category (shared vocab; Title Case already, the single
# most-reused vocab in the review app: notes, issues, and the bell all render
# the identical pill from these two maps)
# ---------------------------------------------------------------------------

NOTE_CATEGORIES = (
    "Revision",
    "Bug",
    "Question",
    "Live Changelog",
    "Informational",
)
DEFAULT_NOTE_CATEGORY = "Revision"

CATEGORY_STYLES = {
    "Revision": "bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-950 dark:text-blue-200 dark:border-blue-900",
    "Bug": "bg-red-100 text-red-800 border-red-200 dark:bg-red-950 dark:text-red-200 dark:border-red-900",
    "Question": "bg-amber-100 text-amber-800 border-amber-200 dark:bg-amber-950 dark:text-amber-200 dark:border-amber-900",
    "Live Changelog": "bg-violet-100 text-violet-800 border-violet-200 dark:bg-violet-950 dark:text-violet-200 dark:border-violet-900",
    "Informational": "bg-teal-100 text-teal-800 border-teal-200 dark:bg-teal-950 dark:text-teal-200 dark:border-teal-900",
}

CATEGORY_DOT = {
    "Revision": "bg-blue-500",
    "Bug": "bg-red-500",
    "Question": "bg-amber-500",
    "Live Changelog": "bg-violet-500",
    "Informational": "bg-teal-500",
}

# ---------------------------------------------------------------------------
# 1.4 Issue Status — balls.status on issues/warmup courts
#
# "Address Later" dark-mode outlier kept DELIBERATELY (review-patterns-ref
# §1.4): every other vocab's dark variant is -950 bg / -200 text / -900
# border; Address Later alone is slate-800/300/700 (slate has no good
# muted-but-visible -950 step). Reproduced verbatim, consciously.
# ---------------------------------------------------------------------------

ISSUE_STATUSES = (
    "Open",
    "Addressing",
    "Ready for Review",
    "Revision Required",
    "Address Later",
    "FYI Only",
    "Resolved",
)
DEFAULT_ISSUE_STATUS = "Open"

ISSUE_STATUS_STYLES = {
    "Open": "bg-muted text-muted-foreground border-border",
    "Addressing": "bg-orange-100 text-orange-800 border-orange-200 dark:bg-orange-950 dark:text-orange-200 dark:border-orange-900",
    "Ready for Review": "bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-950 dark:text-blue-200 dark:border-blue-900",
    "Revision Required": "bg-red-100 text-red-800 border-red-200 dark:bg-red-950 dark:text-red-200 dark:border-red-900",
    "Address Later": "bg-slate-100 text-slate-700 border-slate-200 dark:bg-slate-800 dark:text-slate-300 dark:border-slate-700",
    "FYI Only": "bg-violet-100 text-violet-800 border-violet-200 dark:bg-violet-950 dark:text-violet-200 dark:border-violet-900",
    "Resolved": "bg-green-100 text-green-800 border-green-200 dark:bg-green-950 dark:text-green-200 dark:border-green-900",
}

ISSUE_STATUS_DOT = {
    "Open": "bg-muted-foreground/50",
    "Addressing": "bg-orange-500",
    "Ready for Review": "bg-blue-500",
    "Revision Required": "bg-red-500",
    "Address Later": "bg-slate-400",
    "FYI Only": "bg-violet-500",
    "Resolved": "bg-green-500",
}

# Defensive fallbacks for an unknown/legacy value (review-patterns-ref §1.7),
# and the bell's one-off bespoke "Resolved" badge pill.
FALLBACK_STYLE = "bg-muted text-muted-foreground border-border"
FALLBACK_DOT = "bg-muted-foreground/50"
RESOLVED_BADGE_CLASS = (
    "inline-flex items-center rounded-full border border-green-200 bg-green-100 "
    "px-1.5 py-0 text-[10px] font-medium leading-4 text-green-800 "
    "dark:border-green-900 dark:bg-green-950 dark:text-green-200"
)

# ---------------------------------------------------------------------------
# 2. The status<->review FIELD coupling (review-patterns-ref §2.1 + §2.2,
# the "full bidirectional map for the rebuild's service-layer function").
#
# INTENTIONALLY NOT EXHAUSTIVE: status drives review for the "author is
# making forward progress" states; review drives status for the "reviewer
# rendered a verdict" states. "revision pending" and "completed" have NO
# status-side rule — they are only ever reached from the review side
# (nobody manually sets a page to completed; an "approved" review does).
# ---------------------------------------------------------------------------

STATUS_TO_REVIEW = {
    "open": "not ready",
    "creating": "not ready",
    "finalizing": "not ready",
    "drafted": "ready for review",
    "revising": "revision not ready",
    # "revision pending": no rule — reached via review "revision needed"
    # "completed":        no rule — reached via review "approved"
}

REVIEW_TO_STATUS = {
    "revision needed": "revision pending",
    "approved": "completed",
    # "not ready" / "ready for review" / "revision not ready": no rule
}

STATUS_REVIEW_COUPLING = {
    "status_to_review": STATUS_TO_REVIEW,
    "review_to_status": REVIEW_TO_STATUS,
}

# ---------------------------------------------------------------------------
# 3. Court configurations (plan-v1.md §2) — declarative: a court row defines
# its columns and ball types; ALL courts share the machinery. Adding a court
# type is data, not code. The four v1 configs:
#
# serve_sheet field kinds are a closed set the frontend renders from:
#   text | markdown | url | tags | category | players
# ball_chip_driver: "coupling" (chip driven by the status<->review coupling)
#   or "action_to" (the chip IS the assignee control).
# `automations` is the court's automation map. status_to_review /
#   review_to_status are LIVE from M1 (pure field coupling, applied in
#   services/board.py). The touch/movement automations (auto_spike_on_ready,
#   rally_memory, resolved_clears_action_to, informational_sets_status) are
#   declared here as data but fire only when M2 lands touches.
# ---------------------------------------------------------------------------

_ISSUE_SERVE_SHEET = [
    {"field": "title", "label": "Title", "kind": "text", "required": True},
    {"field": "body", "label": "Body", "kind": "markdown", "required": False},
    {
        "field": "category",
        "label": "Category",
        "kind": "category",
        "required": False,
        "remember_last_used": True,
    },
    {
        "field": "action_to",
        "label": "Recipients",
        "kind": "players",
        "required": False,
        "remember_last_used": True,
    },
    {"field": "tags", "label": "Tags", "kind": "tags", "required": False},
]

_ISSUE_AUTOMATIONS = {
    # All four are M2 (touches/ball movement); declared as data now.
    "auto_spike_on_ready": True,  # "Ready for Review" hands back to author
    "rally_memory": True,  # prev_action_to snapshot + restore
    "resolved_clears_action_to": True,  # point over, ball dead
    "informational_sets_status": "FYI Only",  # one-directional nudge
}

COURT_CONFIGS = {
    "pages": {
        "key": "pages",
        "name": "Pages",
        "ball_types": ["page"],
        "serve_sheet": [
            {"field": "title", "label": "Page name", "kind": "text", "required": True},
            {"field": "url_staging", "label": "Staging URL", "kind": "url", "required": False},
            {"field": "url_live", "label": "Live URL", "kind": "url", "required": False},
            {"field": "tags", "label": "Tags", "kind": "tags", "required": False},
        ],
        "columns": ["title", "links", "tags", "status", "review_state", "ball_chip"],
        "status_vocab": "page_status",
        "review_vocab": "review_state",
        "ball_chip_driver": "coupling",
        "aging_days": 3,
        "defaults": {"status": DEFAULT_PAGE_STATUS, "review_state": DEFAULT_SIGNOFF},
        "automations": {
            "status_to_review": STATUS_TO_REVIEW,
            "review_to_status": REVIEW_TO_STATUS,
        },
    },
    "features": {
        "key": "features",
        "name": "Features",
        "ball_types": ["feature"],
        "serve_sheet": [
            {"field": "body", "label": "Description", "kind": "markdown", "required": True},
        ],
        # "description" renders the ball's body (title holds the derived
        # first-line snippet for compact contexts: bell, sheet header).
        "columns": ["description", "status", "review_state", "ball_chip"],
        "status_vocab": "page_status",
        "review_vocab": "review_state",
        "ball_chip_driver": "coupling",
        "aging_days": 3,
        "defaults": {"status": DEFAULT_PAGE_STATUS, "review_state": DEFAULT_SIGNOFF},
        # The review app never wired the coupling for features (it lived
        # only in review-table.tsx, scoped to pages); the rebuild gives
        # features the IDENTICAL coupling deliberately (ref §2.2 note).
        "automations": {
            "status_to_review": STATUS_TO_REVIEW,
            "review_to_status": REVIEW_TO_STATUS,
        },
    },
    "issues": {
        "key": "issues",
        "name": "Issues",
        "ball_types": ["issue"],
        "serve_sheet": _ISSUE_SERVE_SHEET,
        # The ball chip IS the assignee control on this court (action_to).
        "columns": ["title", "category", "action_to", "status", "tags"],
        "status_vocab": "issue_status",
        "review_vocab": None,
        "ball_chip_driver": "action_to",
        "aging_days": 3,
        "defaults": {"status": DEFAULT_ISSUE_STATUS, "category": DEFAULT_NOTE_CATEGORY},
        "automations": dict(_ISSUE_AUTOMATIONS),
    },
    "warmup": {
        "key": "warmup",
        "name": "Warm-up",
        # Structurally = Issues with these ball types (plan §2: the pre-game
        # space — access requests, credentials, questions, requirements).
        "ball_types": ["Request", "Question", "Requirement"],
        "serve_sheet": _ISSUE_SERVE_SHEET,
        "columns": ["title", "category", "action_to", "status", "tags"],
        "status_vocab": "issue_status",
        "review_vocab": None,
        "ball_chip_driver": "action_to",
        "aging_days": 3,
        "defaults": {"status": DEFAULT_ISSUE_STATUS, "category": DEFAULT_NOTE_CATEGORY},
        "automations": dict(_ISSUE_AUTOMATIONS),
    },
}

# The vocab registry meta_payload() serves; status_vocab/review_vocab keys in
# COURT_CONFIGS index into this. `capitalize`: whether the stored strings are
# all-lowercase and need the CSS `capitalize` utility (ref §3.1 casing table).
VOCABULARIES = {
    "page_status": {
        "values": list(PAGE_STATUSES),
        "styles": STATUS_STYLES,
        "dots": STATUS_DOT,
        "default": DEFAULT_PAGE_STATUS,
        "capitalize": True,
    },
    "review_state": {
        "values": list(SIGNOFF_OPTIONS),
        "styles": SIGNOFF_STYLES,
        "dots": SIGNOFF_DOT,
        "default": DEFAULT_SIGNOFF,
        "capitalize": True,
    },
    "category": {
        "values": list(NOTE_CATEGORIES),
        "styles": CATEGORY_STYLES,
        "dots": CATEGORY_DOT,
        "default": DEFAULT_NOTE_CATEGORY,
        "capitalize": False,
    },
    "issue_status": {
        "values": list(ISSUE_STATUSES),
        "styles": ISSUE_STATUS_STYLES,
        "dots": ISSUE_STATUS_DOT,
        "default": DEFAULT_ISSUE_STATUS,
        "capitalize": False,
    },
}


def court_config(config_key: str) -> dict | None:
    """The authoritative config for a court's config_key (None if unknown).
    Services read THIS, not the court row's stored config snapshot."""
    return COURT_CONFIGS.get(config_key)


def status_values_for(config_key: str) -> tuple[str, ...]:
    """The valid status vocabulary for a court type (empty if unknown)."""
    cfg = COURT_CONFIGS.get(config_key)
    if not cfg:
        return ()
    return tuple(VOCABULARIES[cfg["status_vocab"]]["values"])


# "Done" per vocabulary — the synthetic "In play"/open StatChip and the
# show-resolved toggle key off these, so the frontend never hardcodes a
# terminal value (M1 frontend friction item #1).
DONE_STATUSES = {
    "page_status": ["completed"],
    "issue_status": ["Resolved"],
}
# Not actionable: announcements by category, and these statuses.
NON_ACTIONABLE = {
    "categories": ["Informational"],
    "statuses": ["FYI Only", "Resolved", "completed"],
}


def meta_payload() -> dict:
    """The one authority, serialized for GET /api/meta (and reused by the
    seed loader when stamping court rows). Everything the frontend needs to
    render vocab pills/dots and court layouts without re-typing a value."""
    return {
        "vocab": VOCABULARIES,
        "fallback": {"style": FALLBACK_STYLE, "dot": FALLBACK_DOT},
        "resolved_badge_class": RESOLVED_BADGE_CLASS,
        "coupling": STATUS_REVIEW_COUPLING,
        "court_configs": COURT_CONFIGS,
        "done_statuses": DONE_STATUSES,
        "non_actionable": NON_ACTIONABLE,
    }
