from src.domain.starter_library.errors import UnknownStarterContractError
from src.domain.starter_library.schemas import (
    A11Y_BASELINE,
    A11Y_FOUNDATION,
    DATA_STATES,
    STARTER_CATALOG_SCHEMA_VERSION,
    STARTER_CONTRACT_VERSION,
    StarterAdmission,
    StarterContract,
    StarterLayer,
    StarterRuntimeDependency,
    StarterState,
)

CODEGEN_RULES = {
    "prefer_existing": True,
    "do_not_rebuild_standard_ui": True,
    "configure_before_fork": True,
    "custom_only_when_project_specific": True,
    "do_not_modify_shared_library_source": True,
    "extension_order": ["use", "configure", "compose", "wrap", "extend", "custom"],
    "standardize_mechanics_customize_experience": True,
}

# Product, capability, or shell ids that must never become Starter contracts.
FORBIDDEN_STARTER_IDS = frozenset(
    {
        "pipeline.board",
        "task.board",
        "calendar.schedule",
        "notification.inbox",
        "dashboard.analytics",
        "catalog.grid",
        "catalog.detail",
        "inventory.list",
        "membership.list",
        "order.list",
        "invoice.list",
        "auth.login",
        "billing.checkout",
        "customer.list",
        "customer.form",
        "saas.shell",
        "crm.shell",
        "booking.shell",
        "search",
        "reporting",
        "chart_definition",
        "calculator_definition",
    }
)


def _c(
    contract_id: str,
    layer: StarterLayer,
    description: str,
    *,
    admission: StarterAdmission = "admitted",
    use_when: tuple[str, ...] = (),
    avoid_when: tuple[str, ...] = (),
    supports: tuple[str, ...] = (),
    requires: tuple[str, ...] = (),
    compatible_with: tuple[str, ...] = (),
    states: tuple[StarterState, ...] = (),
    accessibility: tuple[str, ...] | None = None,
    fallbacks: dict[str, str] | None = None,
    runtime_dependencies: tuple[tuple[str, bool], ...] = (),
) -> StarterContract:
    if accessibility is None:
        accessibility = A11Y_FOUNDATION if layer == "foundation" else A11Y_BASELINE
    return StarterContract(
        id=contract_id,
        layer=layer,
        description=description,
        admission=admission,
        use_when=list(use_when),
        avoid_when=list(avoid_when),
        supports=list(supports),
        requires=list(requires),
        compatible_with=list(compatible_with),
        states=list(states),
        accessibility=list(accessibility),
        fallbacks=dict(fallbacks or {}),
        runtime_dependencies=[
            StarterRuntimeDependency(binding=binding, configuration_required=required)
            for binding, required in runtime_dependencies
        ],
    )


_ADMITTED = [
    _c(
        "collection.index",
        "composite",
        "Routable collection landing surface over existing pages or Dataset items.",
        use_when=("business page groups, blogs, resources, directories",),
        avoid_when=("creating a new content or taxonomy model",),
        supports=("route", "title", "description", "children", "items"),
        requires=("layout.page_header", "state.empty"),
        compatible_with=("dataset",),
        states=("idle", "loading", "success", "empty", "error"),
    ),
    _c(
        "collection.grid",
        "composite",
        "Responsive cards for a Dataset-backed collection.",
        use_when=("blog, resource, case study, or service cards",),
        avoid_when=("inventing a vertical-specific listing block",),
        supports=("cards", "image", "title", "excerpt", "link"),
        requires=("collection.index",),
        compatible_with=("dataset",),
        states=("idle", "loading", "success", "empty", "error"),
    ),
    _c(
        "collection.list",
        "composite",
        "Compact linked list for a Dataset-backed collection.",
        use_when=("jobs, webinars, guides, or directory entries",),
        avoid_when=("a staff record list with create/edit mechanics",),
        supports=("items", "title", "metadata", "link"),
        requires=("collection.index",),
        compatible_with=("dataset",),
        states=("idle", "loading", "success", "empty", "error"),
    ),
    _c(
        "collection.nav",
        "composite",
        "Links for child and sibling collections without owning site navigation.",
        use_when=("blog categories, resource groups, and page hierarchies",),
        avoid_when=("replacing the site's global navigation model",),
        supports=("children", "siblings", "breadcrumbs", "active_route"),
        compatible_with=("dataset",),
        states=("idle",),
    ),
    _c(
        "collection.pagination",
        "composite",
        "Pagination or load-more controls for collection membership.",
        use_when=("large blogs, directories, and resource libraries",),
        avoid_when=("loading an unbounded collection into the browser",),
        supports=("page", "cursor", "load_more"),
        requires=("nav.pagination",),
        compatible_with=("records.query", "dataset"),
        states=("idle", "loading"),
    ),
    _c(
        "token.color",
        "foundation",
        "Semantic color tokens for surfaces, text, borders, and status.",
        use_when=("setting or overriding project color values",),
        avoid_when=("inventing a per-project color architecture", "hard-coding hex in components"),
        supports=(
            "background",
            "surface",
            "text",
            "primary",
            "danger",
            "warning",
            "success",
            "border",
        ),
    ),
    _c(
        "token.spacing",
        "foundation",
        "Spacing scale for layout gaps, padding, and stacking.",
        use_when=("spacing layout or component padding",),
        avoid_when=("one-off pixel spacing scattered through generated views",),
        supports=("scale",),
    ),
    _c(
        "token.typography",
        "foundation",
        "Typographic roles: display, headings, body, label, caption, code.",
        use_when=("text hierarchy", "labeling form and page content"),
        avoid_when=("arbitrary font-size utilities on every view",),
        supports=(
            "display",
            "h1",
            "h2",
            "h3",
            "h4",
            "body",
            "label",
            "caption",
            "overline",
            "code",
        ),
    ),
    _c(
        "token.radius",
        "foundation",
        "Corner radius tokens.",
        use_when=("rounding controls, cards, and dialogs",),
        avoid_when=("mixing unrelated radius values in one project",),
        supports=("sm", "md", "lg"),
    ),
    _c(
        "token.shadow",
        "foundation",
        "Elevation / shadow tokens.",
        use_when=("overlays, cards, and raised surfaces",),
        avoid_when=("decorative shadows that fight the token model",),
        supports=("sm", "md", "lg"),
    ),
    _c(
        "ui.button",
        "primitive",
        "Action button with variants, sizes, icons, and loading.",
        use_when=("primary, secondary, destructive, or link actions",),
        avoid_when=("a genuinely custom interaction that is not a button",),
        supports=(
            "primary",
            "secondary",
            "outline",
            "ghost",
            "destructive",
            "link",
            "loading",
            "icon",
            "full_width",
        ),
        states=("idle", "loading", "success"),
        fallbacks={"disabled": "preserve disabled semantics; do not fake with pointer-events"},
    ),
    _c(
        "ui.input",
        "primitive",
        "Single-line text input with label, description, and validation.",
        use_when=("short text, email, phone, search field chrome",),
        avoid_when=(
            "long-form notes — use ui.textarea",
            "choice among known options — use ui.select",
        ),
        supports=(
            "label",
            "placeholder",
            "description",
            "error",
            "prefix",
            "suffix",
            "icon",
            "disabled",
            "readonly",
            "required",
        ),
        requires=("form.field",),
        states=("idle", "error"),
        fallbacks={"error": "announce validation error with the field"},
    ),
    _c(
        "ui.textarea",
        "primitive",
        "Multiline text input sharing the Input validation architecture.",
        use_when=("notes", "descriptions", "comments", "prompts"),
        avoid_when=("single-line values — use ui.input",),
        supports=(
            "label",
            "placeholder",
            "description",
            "error",
            "disabled",
            "readonly",
            "required",
        ),
        requires=("form.field",),
        states=("idle", "error"),
    ),
    _c(
        "ui.select",
        "primitive",
        "Single-choice select with placeholder and validation.",
        use_when=("exactly one choice from a known or searchable list",),
        avoid_when=(
            "independent binary options — use ui.checkbox",
            "several visible exclusive choices — use ui.radio",
        ),
        supports=("single", "placeholder", "validation", "searchable"),
        requires=("form.field",),
        states=("idle", "loading", "empty", "error"),
        fallbacks={"empty": "show empty options copy", "error": "keep prior value; surface error"},
    ),
    _c(
        "ui.checkbox",
        "primitive",
        "Independent binary option.",
        use_when=("opt-in flags that do not exclude other options",),
        avoid_when=(
            "exactly one of several visible choices — use ui.radio",
            "settings toggle — prefer ui.switch",
        ),
        supports=("label", "disabled", "indeterminate"),
        states=("idle",),
    ),
    _c(
        "ui.radio",
        "primitive",
        "Exactly one of several visible choices.",
        use_when=("small exclusive choice sets", "Discovery option groups"),
        avoid_when=("many options — use ui.select", "independent flags — use ui.checkbox"),
        supports=("group", "label", "disabled"),
        states=("idle",),
    ),
    _c(
        "ui.switch",
        "primitive",
        "Immediate on/off setting control.",
        use_when=("application settings and reversible feature toggles",),
        avoid_when=("irreversible or high-consequence actions — use confirmation + ui.button",),
        supports=("label", "disabled"),
        states=("idle",),
    ),
    _c(
        "ui.dialog",
        "primitive",
        "Modal dialog for focused confirmation, form, or destructive tasks.",
        use_when=("a task that must complete or cancel before returning to the page",),
        avoid_when=(
            "lightweight contextual actions — use ui.dropdown or ui.tooltip",
            "record editing in admin — prefer ui.drawer",
        ),
        supports=(
            "confirmation",
            "form",
            "information",
            "destructive",
            "focus_trap",
            "escape",
            "backdrop",
        ),
        accessibility=A11Y_BASELINE + ("dialog_focus_trap", "escape_closes", "error_announcement"),
        states=("idle",),
    ),
    _c(
        "ui.drawer",
        "primitive",
        "Side overlay for record edit, detail, filters, or secondary workflows.",
        use_when=("admin record edit or detail without leaving the list", "filter sheets"),
        avoid_when=("a full-page create/edit is the product's primary task",),
        supports=("record_edit", "record_detail", "filters", "mobile"),
        accessibility=A11Y_BASELINE + ("dialog_focus_trap", "escape_closes"),
        states=("idle", "loading", "error"),
    ),
    _c(
        "ui.tabs",
        "primitive",
        "In-page section switcher.",
        use_when=("related views of the same record or settings group",),
        avoid_when=("primary app navigation — use layout.admin",),
        supports=("keyboard", "controlled"),
        states=("idle",),
    ),
    _c(
        "ui.badge",
        "primitive",
        "Compact label. Prefer status.badge for lifecycle state.",
        use_when=("counts, roles, or non-lifecycle tags",),
        avoid_when=(
            "record lifecycle — use status.badge",
            "arbitrary per-row colors",
        ),
        supports=("neutral", "info"),
        states=("idle",),
    ),
    _c(
        "ui.tooltip",
        "primitive",
        "Secondary explanation on hover or focus.",
        use_when=("short extra context that is not required to act",),
        avoid_when=("required instructions — put those in visible copy",),
        supports=("hover", "focus"),
        states=("idle",),
    ),
    _c(
        "ui.dropdown",
        "primitive",
        "Contextual action menu (view, edit, duplicate, archive, delete).",
        use_when=("row actions or overflow menus",),
        avoid_when=("a single primary action — use ui.button",),
        supports=("view", "edit", "duplicate", "archive", "delete"),
        states=("idle",),
    ),
    _c(
        "ui.avatar",
        "primitive",
        "Person or org image with initials fallback.",
        use_when=("user, member, or assignee identity",),
        avoid_when=("generic icons — use ui.icon once admitted",),
        supports=("image", "initials", "group", "status_indicator"),
        fallbacks={"missing_image": "initials"},
        states=("idle",),
    ),
    _c(
        "ui.skeleton",
        "primitive",
        "Preferred initial loading placeholder for major page sections.",
        use_when=("first paint of lists, details, and dashboards",),
        avoid_when=("tiny inline waits — ui.spinner may be enough",),
        supports=("block", "line", "table_row"),
        states=("loading",),
    ),
    _c(
        "ui.spinner",
        "primitive",
        "Compact indeterminate loading indicator.",
        use_when=("button loading or small inline waits",),
        avoid_when=("major page sections — prefer ui.skeleton",),
        supports=("indeterminate",),
        states=("loading",),
    ),
    _c(
        "ui.alert",
        "primitive",
        "Persistent inline feedback: info, success, warning, error.",
        use_when=("page-level messages the user must still see",),
        avoid_when=("transient success — use ui.toast", "section failure — use state.error"),
        supports=("info", "success", "warning", "error"),
        states=("success", "error"),
    ),
    _c(
        "ui.toast",
        "primitive",
        "Temporary confirmation. Not for errors that require action.",
        use_when=("short success confirmations",),
        avoid_when=("errors that need a next step", "configuration_required"),
        supports=("success", "info"),
        states=("success",),
    ),
    _c(
        "form.field",
        "composite",
        "Label, control, help, required marker, and error for one field.",
        use_when=("any labeled input in an application form",),
        avoid_when=("bare decorative inputs with no validation or label",),
        supports=("label", "help", "error", "required"),
        requires=("ui.input",),
        states=("idle", "error"),
        fallbacks={"error": "announce associated error"},
    ),
    _c(
        "form.section",
        "composite",
        "Grouped fields with title, explanation, and responsive grid.",
        use_when=("structured create/edit forms with more than one topic",),
        avoid_when=("a single field — form.field is enough",),
        supports=("title", "explanation", "divider", "responsive_grid"),
        requires=("form.field",),
        states=("idle",),
    ),
    _c(
        "form.standard",
        "composite",
        "Standard form: fields, validation, submit, cancel. Not a form engine.",
        use_when=("public or authenticated forms that submit through platform adapters",),
        avoid_when=("inventing a project-local form framework",),
        supports=("validation", "submit", "cancel"),
        requires=("form.field", "form.section"),
        compatible_with=("forms.public_submission",),
        states=("idle", "loading", "success", "error", "unauthorized", "configuration_required"),
        fallbacks={
            "error": "state.error",
            "configuration_required": "state.configuration_required",
        },
    ),
    _c(
        "record.table",
        "composite",
        "Sortable, filterable tabular record display.",
        use_when=("multiple structured records on desktop operational UI",),
        avoid_when=("fewer than a handful of visual cards", "primarily visual content"),
        supports=(
            "columns",
            "sorting",
            "filters",
            "selection",
            "bulk_actions",
            "row_actions",
            "pagination",
            "loading",
            "empty",
            "column_visibility",
            "sticky_header",
            "responsive_fallback",
        ),
        requires=("state.empty", "state.loading", "ui.dropdown"),
        compatible_with=("records.query",),
        states=("idle", "loading", "success", "empty", "error", "unauthorized"),
        fallbacks={
            "empty": "state.empty",
            "error": "state.error",
            "unauthorized": "permission copy, not a raw 403",
        },
    ),
    _c(
        "record.detail",
        "composite",
        "Record detail layout: status, properties, related, activity, actions.",
        use_when=("inspecting one record",),
        avoid_when=("editing is the only task — prefer record.edit or ui.drawer",),
        supports=("status", "properties", "related", "activity", "actions"),
        requires=("state.empty", "state.error"),
        compatible_with=("records.query",),
        states=("idle", "loading", "success", "empty", "error", "unauthorized"),
    ),
    _c(
        "record.create",
        "composite",
        "Authenticated create: form.standard + adapter.records.createRecord.",
        use_when=("staff or member creates a record the entity schema allows",),
        avoid_when=("public unauthenticated capture — use form.standard + the Forms App Block"),
        supports=("visible_field_planning", "hidden_single_option_selects", "system_keys_excluded"),
        requires=("form.standard", "adapter.records"),
        compatible_with=("records.query",),
        states=("idle", "loading", "success", "error", "unauthorized"),
        fallbacks={"unauthorized": "do not render a writable form"},
    ),
    _c(
        "record.edit",
        "composite",
        "Authenticated edit: form.standard + adapter.records.patchRecord.",
        use_when=("updating fields the entity schema marks editable",),
        avoid_when=("protected or system_managed fields — those are not slots"),
        supports=("visible_field_planning", "unsaved_changes"),
        requires=("form.standard", "adapter.records"),
        compatible_with=("records.query", "workflow.transition"),
        states=("idle", "loading", "success", "error", "unauthorized"),
    ),
    _c(
        "file.upload",
        "composite",
        "Provider-neutral application-file upload → file_id.",
        use_when=("attaching documents or images to a subject record",),
        avoid_when=(
            "raw object storage — use adapter.storage",
            "embedding vendor SDKs in the browser",
        ),
        supports=(
            "single",
            "multiple",
            "drag_drop",
            "image",
            "document",
            "type_restriction",
            "size_restriction",
            "progress",
            "retry",
            "preview",
            "remove",
        ),
        requires=("adapter.files", "state.error"),
        compatible_with=("files.attachments",),
        states=(
            "idle",
            "loading",
            "success",
            "empty",
            "error",
            "unauthorized",
            "configuration_required",
        ),
        fallbacks={
            "error": "failed upload with retry",
            "configuration_required": "state.configuration_required",
        },
    ),
    _c(
        "file.attachments",
        "composite",
        "Attachment list/remove for a subject record.",
        use_when=("showing files already bound to a record",),
        avoid_when=("a standalone DAM or folder product — that is an App Block"),
        supports=("list", "remove", "download"),
        requires=("file.upload", "adapter.files"),
        compatible_with=("files.attachments",),
        states=("idle", "loading", "success", "empty", "error", "unauthorized"),
    ),
    _c(
        "record.summary",
        "composite",
        "Thin operational summary of an existing list. Not Reporting.",
        use_when=("counts or highlights of the current listRecords page",),
        avoid_when=("analytics", "time-series", "metric-definition systems"),
        supports=("current_value", "list_scoped"),
        compatible_with=("records.query",),
        states=("idle", "loading", "success", "empty", "error"),
    ),
    _c(
        "record.calendar",
        "composite",
        "Temporal grouping of an existing list. Not calendar.sync and not a scheduling engine.",
        use_when=("month/week/day/agenda views of records that already have dates",),
        avoid_when=(
            "booking.availability engines",
            "calendar.sync",
            "inventing appointments as data.entity",
        ),
        supports=("month", "week", "day", "agenda"),
        compatible_with=("booking.availability", "records.query"),
        states=("idle", "loading", "success", "empty", "error"),
    ),
    _c(
        "state.empty",
        "composite",
        "Empty state with icon, title, description, and actions.",
        use_when=("a list or section has zero records",),
        avoid_when=("load failure — use state.error",),
        supports=("icon", "title", "description", "primary_action", "secondary_action"),
        states=("empty",),
    ),
    _c(
        "state.error",
        "composite",
        "Safe error state. Never render raw backend errors.",
        use_when=(
            "failed load",
            "permission denied",
            "integration unavailable",
            "temporary outage",
        ),
        avoid_when=("showing exception strings or stack traces to end users",),
        supports=(
            "failed_load",
            "permission_denied",
            "integration_unavailable",
            "temporarily_unavailable",
        ),
        states=("error", "unauthorized"),
        fallbacks={"unauthorized": "permission denied copy"},
    ),
    _c(
        "state.success",
        "composite",
        "Completed-task confirmation region.",
        use_when=("post-submit or post-booking confirmation screens",),
        avoid_when=("transient toasts — use ui.toast",),
        supports=("title", "next_action"),
        states=("success",),
    ),
    _c(
        "state.loading",
        "composite",
        "Section-level loading. Prefer skeletons for major regions.",
        use_when=("waiting on the first payload for a region",),
        avoid_when=("replacing ui.skeleton for large page chrome"),
        requires=("ui.skeleton", "ui.spinner"),
        states=("loading",),
    ),
    _c(
        "state.configuration_required",
        "composite",
        "Runtime setup required. The feature exists; configuration activates it.",
        use_when=("a provider binding is missing or degraded",),
        avoid_when=("treating missing Stripe/CRM as a failed build", "hiding the feature"),
        supports=("provider_name", "admin_action", "customer_unavailable_copy"),
        compatible_with=("payments.subscription", "email.send", "crm.contact.upsert"),
        states=("configuration_required",),
        fallbacks={
            "admin": "Connect {provider} to activate.",
            "customer": "This capability is not available yet.",
        },
        runtime_dependencies=(("provider_binding", True),),
    ),
    _c(
        "layout.page",
        "composite",
        "Public page frame (header, main, footer). Marketing/landing chrome.",
        use_when=("public marketing, landing, or unauthenticated content pages",),
        avoid_when=("authenticated app chrome — use layout.admin",),
        supports=("header", "main", "footer"),
        states=("idle",),
    ),
    _c(
        "layout.admin",
        "composite",
        "Visual authenticated app shell: sidebar, topbar, breadcrumbs, user menu. "
        "Not block:admin.shell.",
        use_when=("staff or signed-in application chrome",),
        avoid_when=("replacing the protected admin.shell App Block", "public marketing pages"),
        supports=(
            "sidebar",
            "topbar",
            "breadcrumbs",
            "page_content",
            "user_menu",
            "mobile_nav",
            "collapsed",
        ),
        requires=("adapter.auth",),
        states=("idle", "unauthorized"),
    ),
    _c(
        "layout.settings",
        "composite",
        "Settings page frame. Sections are optional; do not force all of them.",
        use_when=("account, workspace, notification, or integration settings",),
        avoid_when=("forcing billing/team/security sections that the project does not have",),
        supports=("nav", "section", "responsive"),
        states=("idle", "unauthorized", "configuration_required"),
    ),
    _c(
        "layout.page_header",
        "composite",
        "Title, description, breadcrumbs, primary and secondary actions.",
        use_when=("record lists, details, settings, and dashboards",),
        avoid_when=("inventing a new page heading per entity",),
        supports=("title", "description", "breadcrumbs", "primary_action", "secondary_actions"),
        states=("idle",),
    ),
    _c(
        "filter.bar",
        "composite",
        "Search, status, owner, date range, category, saved filters, clear.",
        use_when=("operational record lists",),
        avoid_when=("rebuilding filter chrome per CRM or case app",),
        supports=("search", "status", "owner", "date_range", "category", "saved_filters", "clear"),
        requires=("search.box", "ui.select"),
        compatible_with=("records.query",),
        states=("idle",),
    ),
    _c(
        "search.box",
        "composite",
        "Debounced search chrome. Not a Search capability.",
        use_when=("filtering an existing records.query list",),
        avoid_when=("a platform Search product", "a private search engine"),
        supports=("debounce",),
        requires=("ui.input",),
        compatible_with=("records.query",),
        states=("idle", "loading", "empty"),
    ),
    _c(
        "nav.pagination",
        "composite",
        "Page, cursor, or load-more controls matching the backend primitive.",
        use_when=("lists that page through records.query",),
        avoid_when=("loading the entire collection into the client",),
        supports=("page", "cursor", "load_more"),
        compatible_with=("records.query",),
        states=("idle", "loading"),
    ),
    _c(
        "status.badge",
        "composite",
        "Lifecycle state → semantic treatment. Distinct from ui.badge.",
        use_when=("workflow or record status display",),
        avoid_when=("Codegen picking colors per entity",),
        supports=("pending", "active", "paid", "draft", "rejected"),
        compatible_with=("workflow.transition",),
        states=("idle",),
    ),
    _c(
        "record.list",
        "composite",
        "Page-level record list: header, filters, table, pagination, create.",
        use_when=("staff lists of structured records",),
        avoid_when=(
            "rebuilding list chrome per entity",
            "replacing an App Block record.table slot",
        ),
        supports=("header", "search", "filters", "table", "cards", "pagination", "create"),
        requires=(
            "layout.page_header",
            "filter.bar",
            "record.table",
            "nav.pagination",
            "state.empty",
        ),
        compatible_with=("records.query", "record.card"),
        states=("idle", "loading", "success", "empty", "error", "unauthorized"),
        fallbacks={
            "empty": "state.empty",
            "error": "state.error",
            "unauthorized": "permission copy, not a raw 403",
        },
    ),
    _c(
        "record.card",
        "composite",
        "Mobile-friendly record representation; table remains the desktop host.",
        use_when=("narrow viewports or visual record summaries",),
        avoid_when=("replacing record.table on dense desktop ops UIs",),
        supports=("title", "subtitle", "status", "assignee"),
        requires=("status.badge",),
        compatible_with=("records.query",),
        states=("idle",),
    ),
    _c(
        "chart.bar",
        "composite",
        "Bar chart of a bounded Dataset. Not Reporting and not a chart builder.",
        use_when=("comparing categorical or time-bucket values from an explicit Dataset",),
        avoid_when=(
            "saved report_definition dashboards — that is reporting.dashboard",
            "scatter, combo, maps, or a chart_definition Cloud record",
        ),
        supports=("categories", "values", "empty", "loading"),
        requires=("state.empty", "state.loading"),
        compatible_with=("dataset", "metric.card", "visualization.chart"),
        states=("idle", "loading", "success", "empty", "error"),
        fallbacks={"empty": "state.empty"},
    ),
    _c(
        "chart.line",
        "composite",
        "Line chart of a bounded Dataset series. Not a time-series warehouse.",
        use_when=("showing a trend from an explicit Dataset series",),
        avoid_when=("BI analytics", "inventing a private charting backend"),
        supports=("x", "y", "empty", "loading"),
        requires=("state.empty", "state.loading"),
        compatible_with=("dataset", "visualization.chart"),
        states=("idle", "loading", "success", "empty", "error"),
        fallbacks={"empty": "state.empty"},
    ),
    _c(
        "chart.pie",
        "composite",
        "Pie chart of a bounded Dataset. Parts of one whole.",
        use_when=("showing composition of a small explicit Dataset",),
        avoid_when=("many categories better shown as chart.bar", "a chart builder"),
        supports=("categories", "values", "empty"),
        requires=("state.empty",),
        compatible_with=("dataset", "visualization.chart"),
        states=("idle", "loading", "success", "empty", "error"),
        fallbacks={"empty": "state.empty"},
    ),
    _c(
        "chart.area",
        "composite",
        "Area chart of a bounded Dataset series.",
        use_when=("emphasizing magnitude of a Dataset series over time",),
        avoid_when=("scatter or stacked combo charts — not admitted in v1"),
        supports=("x", "y", "empty", "loading"),
        requires=("state.empty", "state.loading"),
        compatible_with=("dataset", "visualization.chart"),
        states=("idle", "loading", "success", "empty", "error"),
        fallbacks={"empty": "state.empty"},
    ),
    _c(
        "metric.card",
        "composite",
        "Present a scalar Dataset or calculator output. Not metric.count.",
        use_when=("displaying one figure from a Dataset or Calculation IR output",),
        avoid_when=(
            "defining a saved aggregate — that is reporting metric.count/metric.sum",
            "replacing visualization.chart",
            "using proposed stat.card",
        ),
        supports=("value", "label", "empty", "loading"),
        requires=("state.empty",),
        compatible_with=("dataset", "calculation.calculator", "reporting.dashboard"),
        states=("idle", "loading", "success", "empty", "error"),
        fallbacks={"empty": "state.empty"},
    ),
    _c(
        "calculator.formula",
        "composite",
        "Typed inputs plus local Calculation IR evaluation. Not a formula builder.",
        use_when=("presenting a Feature Pack or Codegen authored calculator",),
        avoid_when=(
            "staff runtime formula authoring",
            "authoritative invoice/tax/commission math until calculation.evaluate",
            "eval() or arbitrary JavaScript",
        ),
        supports=("typed_inputs", "local_evaluate", "scalar_output", "dataset_output"),
        requires=("state.empty", "state.error", "metric.card"),
        compatible_with=("dataset", "calculation.calculator", "form.standard"),
        states=("idle", "loading", "success", "empty", "error"),
        fallbacks={"error": "state.error"},
    ),
    _c(
        "adapter.auth",
        "adapter",
        "Platform auth client. No local user database.",
        use_when=("sign-in, session, and role-aware routing",),
        avoid_when=("a second users table", "hard-coded admin passwords"),
        compatible_with=("auth.sign_in", "auth.profile"),
        states=("idle", "loading", "success", "error", "unauthorized"),
    ),
    _c(
        "adapter.records",
        "adapter",
        "Platform records client: createRecord / patchRecord / listRecords / getRecord.",
        use_when=("persisting or listing Cloud records",),
        avoid_when=("a project-local ORM or shadow database",),
        compatible_with=("records.query", "workflow.transition"),
        states=("idle", "loading", "success", "empty", "error", "unauthorized"),
    ),
    _c(
        "adapter.files",
        "adapter",
        "Application-file client (file_id upload/download/delete/attach).",
        use_when=("application files bound to records",),
        avoid_when=("raw bucket keys or vendor SDKs in generated apps",),
        compatible_with=("files.attachments",),
        states=("idle", "loading", "success", "error", "unauthorized", "configuration_required"),
    ),
    _c(
        "adapter.storage",
        "adapter",
        "Raw object-storage client (storage.file.*). Not application files.",
        use_when=("explicit storage.file ports",),
        avoid_when=("substituting this for adapter.files on App Block attachments",),
        compatible_with=("storage.file.put",),
        states=("idle", "loading", "success", "error", "unauthorized"),
    ),
    _c(
        "adapter.permissions",
        "adapter",
        "Platform permissions client. Frontend gates are UX; backend remains authoritative.",
        use_when=("hiding actions the session cannot perform",),
        avoid_when=("treating a client gate as enforcement",),
        compatible_with=("auth.sign_in",),
        states=("idle", "unauthorized"),
    ),
    _c(
        "adapter.events",
        "adapter",
        "Platform event client for reading activity. Generated apps do not emit PackEvent ids.",
        use_when=("displaying activity already produced by Cloud mutations",),
        avoid_when=("POST /events from generated code",),
        compatible_with=("activity.timeline",),
        states=("idle", "loading", "success", "empty", "error"),
    ),
    _c(
        "ui.combobox",
        "primitive",
        "Searchable single-value selection with keyboard navigation and async options.",
        use_when=("large or remote option sets", "entity references and provider-neutral pickers"),
        avoid_when=("small static option sets — use ui.select",),
        supports=("search", "async_options", "clear", "keyboard_navigation"),
        requires=("form.field",),
        states=("idle", "loading", "empty", "error"),
    ),
    _c(
        "ui.currency_input",
        "primitive",
        "Locale-aware currency amount input that preserves a canonical numeric value.",
        use_when=("prices, invoices, subscriptions, and checkout forms",),
        avoid_when=("provider-specific money objects in generated UI",),
        supports=("currency", "minor_units", "locale_formatting", "validation"),
        requires=("form.field", "ui.input"),
        states=("idle", "error"),
    ),
    _c(
        "ui.richtext",
        "primitive",
        "Constrained rich text editing with a portable document value.",
        use_when=("posts, pages, comments, and long-form descriptions",),
        avoid_when=("embedding a vendor editor or raw HTML contract",),
        supports=("portable_value", "links", "lists", "headings", "safe_output"),
        requires=("form.field",),
        states=("idle", "error"),
    ),
    _c(
        "ui.command_menu",
        "primitive",
        "Keyboard-first command/search menu over declared application actions.",
        use_when=("workspace navigation, actions, and search shortcuts",),
        avoid_when=("provider-specific command APIs or hidden permission bypasses",),
        supports=("keyboard_navigation", "grouping", "search", "permission_aware_actions"),
        requires=("ui.input",),
        states=("idle", "empty", "unauthorized"),
    ),
]

_PROPOSED = [
    _c(
        "token.border",
        "foundation",
        "Border width and style tokens.",
        admission="proposed",
        use_when=("control and card outlines",),
        avoid_when=("one-off border classes per view",),
        supports=("width", "style"),
    ),
    _c(
        "token.breakpoint",
        "foundation",
        "Responsive breakpoints.",
        admission="proposed",
        use_when=("layout that changes at sm/md/lg/xl",),
        avoid_when=("ad-hoc media queries per generated page",),
        supports=("sm", "md", "lg", "xl"),
    ),
    _c(
        "token.z_index",
        "foundation",
        "Stacking tokens for overlays.",
        admission="proposed",
        use_when=("dialog, drawer, toast, popover layering",),
        avoid_when=("magic z-index numbers in project CSS",),
        supports=("dropdown", "dialog", "toast"),
    ),
    _c(
        "token.motion",
        "foundation",
        "Duration and easing tokens, including reduced-motion.",
        admission="proposed",
        use_when=("enter/exit and loading motion",),
        avoid_when=("ignoring prefers-reduced-motion",),
        supports=("duration", "easing", "reduced_motion"),
    ),
    _c(
        "token.container",
        "foundation",
        "Application and full-width container widths.",
        admission="proposed",
        use_when=("page max-width and full-bleed sections",),
        avoid_when=("hard-coded max-width per page",),
        supports=("app", "full"),
    ),
    _c(
        "ui.datepicker",
        "primitive",
        "Date, time, datetime, and range pickers. Timezone is an application concern.",
        admission="proposed",
        use_when=("scheduling, due dates, reporting windows as UI",),
        avoid_when=("booking.availability engines", "storing timezone only in the widget"),
        supports=("date", "time", "datetime", "range"),
        requires=("form.field",),
        states=("idle", "error"),
    ),
    _c(
        "ui.multiselect",
        "primitive",
        "Multiple choice for tags, categories, permissions, and filters.",
        admission="proposed",
        use_when=("several values from one list",),
        avoid_when=("introducing a third-party select pack per generated app",),
        supports=("search", "tags", "async_options"),
        requires=("form.field",),
        states=("idle", "loading", "empty", "error"),
    ),
    _c(
        "ui.icon",
        "primitive",
        "Single project icon system. Do not mix Lucide, Heroicons, and FontAwesome.",
        admission="proposed",
        use_when=("navigation, empty states, and inline actions",),
        avoid_when=("a second icon pack in the same generated project",),
        supports=("named_set",),
        states=("idle",),
    ),
    _c(
        "layout.container",
        "composite",
        "Standard application or full-width container.",
        admission="proposed",
        use_when=("page width constraints",),
        avoid_when=("hand-rolled max-width wrappers",),
        requires=("token.container",),
        states=("idle",),
    ),
    _c(
        "layout.stack",
        "composite",
        "Vertical stack with tokenized gap.",
        admission="proposed",
        use_when=("vertical rhythm",),
        avoid_when=("one-off margin utilities on every child",),
        requires=("token.spacing",),
        states=("idle",),
    ),
    _c(
        "layout.cluster",
        "composite",
        "Wrapping horizontal cluster with tokenized gap.",
        admission="proposed",
        use_when=("inline actions, chips, and toolbars",),
        avoid_when=("a true grid of equal columns — use layout.grid",),
        requires=("token.spacing",),
        states=("idle",),
    ),
    _c(
        "layout.grid",
        "composite",
        "Responsive columns.",
        admission="proposed",
        use_when=("card grids and form columns",),
        avoid_when=("hand-crafted responsive CSS per page",),
        requires=("token.breakpoint",),
        supports=("sm", "md", "xl"),
        states=("idle",),
    ),
    _c(
        "stat.card",
        "composite",
        "Dashboard figure: value, optional comparison, icon, link. Not Reporting.",
        admission="proposed",
        use_when=("operational counts on a dashboard built from current lists",),
        avoid_when=("analytics time-series", "metric-definition systems", "use admitted metric.card"),
        supports=("value", "comparison", "trend", "icon", "link"),
        requires=("record.summary",),
        states=("idle", "loading", "empty", "error"),
    ),
    _c(
        "gate.permission",
        "composite",
        "UX hide/show for a permission. Backend remains the authority.",
        admission="proposed",
        use_when=("hiding delete/edit when the session lacks permission",),
        avoid_when=("using this as the security boundary"),
        requires=("adapter.permissions",),
        states=("idle", "unauthorized"),
        fallbacks={"unauthorized": "omit the action; do not fake success"},
    ),
]


def _index(items: list[StarterContract]) -> dict[str, StarterContract]:
    indexed = {item.id: item for item in items}
    if len(indexed) != len(items):
        raise ValueError("duplicate Starter contract id")
    return indexed


_CONTRACTS = _index(_ADMITTED)
_PROPOSED_CONTRACTS = _index(_PROPOSED)


def list_contracts() -> list[StarterContract]:
    return list(_CONTRACTS.values())


def list_proposed_contracts() -> list[StarterContract]:
    return list(_PROPOSED_CONTRACTS.values())


def get_contract(contract_id: str) -> StarterContract:
    contract = _CONTRACTS.get(contract_id)
    if contract is None:
        raise UnknownStarterContractError(contract_id)
    return contract


def known_contract_ids() -> frozenset[str]:
    return frozenset(_CONTRACTS)


def known_proposed_ids() -> frozenset[str]:
    return frozenset(_PROPOSED_CONTRACTS)


def _row(item: StarterContract) -> dict:
    payload = item.model_dump(mode="json")
    payload["implementation"] = None
    return payload


def contract_map_payload() -> dict:
    return {
        "starter_contract_version": STARTER_CONTRACT_VERSION,
        "catalog_schema_version": STARTER_CATALOG_SCHEMA_VERSION,
        "codegen": dict(CODEGEN_RULES),
        "accessibility_baseline": list(A11Y_BASELINE),
        "states": list(DATA_STATES),
        "contracts": [_row(item) for item in list_contracts()],
        "proposed": [_row(item) for item in list_proposed_contracts()],
    }


def merge_contract_map(existing: dict | None, catalog: dict | None = None) -> dict:
    """Preserve host implementation paths when Core refreshes the catalog."""
    catalog = catalog or contract_map_payload()
    impls: dict[str, str] = {}
    if isinstance(existing, dict):
        for item in existing.get("contracts") or []:
            if not isinstance(item, dict):
                continue
            path = item.get("implementation")
            contract_id = item.get("id")
            if contract_id and isinstance(path, str) and path.strip():
                impls[str(contract_id)] = path.strip()
    merged = []
    for item in catalog.get("contracts") or []:
        row = dict(item)
        if row.get("id") in impls:
            row["implementation"] = impls[row["id"]]
        merged.append(row)
    return {**catalog, "contracts": merged}


__all__ = [
    "CODEGEN_RULES",
    "FORBIDDEN_STARTER_IDS",
    "STARTER_CATALOG_SCHEMA_VERSION",
    "STARTER_CONTRACT_VERSION",
    "contract_map_payload",
    "get_contract",
    "known_contract_ids",
    "known_proposed_ids",
    "list_contracts",
    "list_proposed_contracts",
    "merge_contract_map",
]
