"""The app's shared vocabularies — statuses, aspects, the rating scale.

Single authority (frontend.md: "Enum values, option lists, and status
vocabularies come from one authority"). The UI reads these from /api/meta rather
than re-typing labels, so adding a status here reaches every screen.
"""

from enum import StrEnum

# Access levels are DATA, not an enum: they live in app_levels and the owner can
# add or edit them without a code change. These names are the seeded set
# (migration 0003) that Scout's own logic refers to; `rian` is the owner and is
# not a level at all — the accounts kit synthesizes the super admin.
LEVEL_ADMIN = "admin"
LEVEL_LEAD = "lead"
LEVEL_REVIEWER = "reviewer"

# Display labels for the seeded levels. A level added later that is missing here
# falls back to its own name, so this never blocks creating one.
LEVEL_LABELS: dict[str, str] = {
    LEVEL_ADMIN: "Admin",
    LEVEL_LEAD: "Lead",
    LEVEL_REVIEWER: "Reviewer",
}

DEFAULT_LEVEL = LEVEL_REVIEWER

# Scout's own permission strings, alongside the accounts kit's standard
# vocabulary. Kept here so the seed below and app/services/levels.py agree.
PERM_REVIEW = "scout.review"
PERM_RESULTS_VIEW = "scout.results.view"
PERM_PROJECT_MANAGE = "scout.project.manage"
PERM_PROJECT_MEMBERS = "scout.project.members"
PERM_PROJECTS_CREATE = "scout.projects.create"
PERM_PROJECTS_DELETE = "scout.projects.delete"
PERM_VIEW_AS = "scout.view_as"

# Every permission Scout can render meaningfully: the kit's standard vocabulary
# plus Scout's own. The kit itself ships no metadata for permission strings, so
# this catalog is Scout's (a recorded feedback item — every app re-writes this).
# A level may carry a string that is NOT here; the UI shows it as "custom — not
# enforced by Scout", which is the literal truth until code checks it.
PERMISSION_CATALOG: list[dict[str, str | bool]] = [
    # kit-standard (enforced by the kit's own guarded mutations)
    {"value": "accounts.view", "label": "See all accounts", "scope": "app",
     "description": "View the People screen and the access matrix.", "enforced": True},
    {"value": "accounts.add", "label": "Add & invite accounts", "scope": "app",
     "description": "Add existing BW users to Scout and send invite emails to new ones.",
     "enforced": True},
    {"value": "accounts.delete", "label": "Remove accounts", "scope": "app",
     "description": "Deactivate accounts and drop their project access.", "enforced": True},
    {"value": "accounts.reset_password", "label": "Send password resets", "scope": "app",
     "description": "Email a password-reset link to a member.", "enforced": True},
    {"value": "accounts.change_level", "label": "Change app-wide levels", "scope": "app",
     "description": "Set a member's app-wide level (within what yours may assign).",
     "enforced": True},
    {"value": "levels.create", "label": "Create levels", "scope": "app",
     "description": "Define new access levels. Held only by the owner by default.",
     "enforced": True},
    {"value": "levels.edit_permissions", "label": "Edit levels", "scope": "app",
     "description": "Change a level's permissions or delete it. Owner-only by default.",
     "enforced": True},
    {"value": "instances.create", "label": "Register projects (kit)", "scope": "app",
     "description": "Unused in Scout: projects are created by Scout itself.",
     "enforced": True},
    {"value": "instances.grant", "label": "Grant project access", "scope": "app",
     "description": "Give a member a level on a project.", "enforced": True},
    {"value": "instances.default_all", "label": "Grant all-projects access", "scope": "app",
     "description": "Set a member's level to apply to every project.", "enforced": True},
    {"value": "instances.change_user_level", "label": "Change project levels", "scope": "app",
     "description": "Change a member's level on one project.", "enforced": True},
    # scout-specific (enforced by Scout's routes)
    {"value": PERM_REVIEW, "label": "Review options", "scope": "project",
     "description": "Play the review game on a project.", "enforced": True},
    {"value": PERM_RESULTS_VIEW, "label": "See results", "scope": "project",
     "description": "See a project's rollup and download its Scouting Report.",
     "enforced": True},
    {"value": PERM_PROJECT_MANAGE, "label": "Manage a project", "scope": "project",
     "description": "Options, materials, import/export, members — everything inside one project.",
     "enforced": True},
    {"value": PERM_PROJECT_MEMBERS, "label": "Manage a project's people", "scope": "project",
     "description": "Add, remove, and set levels for members of one project — nothing else.",
     "enforced": True},
    {"value": PERM_PROJECTS_CREATE, "label": "Create projects", "scope": "app",
     "description": "Create new scouting projects.", "enforced": True},
    {"value": PERM_PROJECTS_DELETE, "label": "Delete projects", "scope": "project",
     "description": "Delete a project (asks twice once reviews exist).", "enforced": True},
    {"value": PERM_VIEW_AS, "label": "View as another user", "scope": "app",
     "description": "Render Scout as a lower-ranked user, read-only. The owner always can.",
     "enforced": True},
]

# The levels a fresh Scout database starts with. Migration 0003 writes these,
# and a test asserts the live rows still match — levels are editable data, so
# this is the baseline, not a constraint. `admin` deliberately holds no
# `levels.*` permission (only the owner may rewrite the permission model) and
# cannot assign `admin`, which is what stops an admin minting another admin.
SEED_LEVELS: dict[str, dict[str, list[str]]] = {
    LEVEL_ADMIN: {
        "permissions": [
            "accounts.view", "accounts.add", "accounts.delete",
            "accounts.reset_password", "accounts.change_level",
            "instances.create", "instances.grant",
            "instances.default_all", "instances.change_user_level",
            PERM_PROJECTS_CREATE, PERM_PROJECTS_DELETE,
            PERM_PROJECT_MANAGE, PERM_PROJECT_MEMBERS,
            PERM_RESULTS_VIEW, PERM_REVIEW,
        ],
        "assignable": [LEVEL_LEAD, LEVEL_REVIEWER],
    },
    LEVEL_LEAD: {"permissions": [PERM_REVIEW, PERM_RESULTS_VIEW], "assignable": []},
    LEVEL_REVIEWER: {"permissions": [PERM_REVIEW], "assignable": []},
}


class ProjectStatus(StrEnum):
    DRAFT = "draft"
    ACTIVE = "active"
    ARCHIVED = "archived"


class OptionStatus(StrEnum):
    DRAFT = "draft"
    PUBLISHED = "published"


class MaterialKind(StrEnum):
    NOTE = "note"
    FILE = "file"
    LINK = "link"


PROJECT_STATUS_LABELS: dict[str, str] = {
    ProjectStatus.DRAFT: "Draft",
    ProjectStatus.ACTIVE: "Active",
    ProjectStatus.ARCHIVED: "Archived",
}

OPTION_STATUS_LABELS: dict[str, str] = {
    OptionStatus.DRAFT: "Draft",
    OptionStatus.PUBLISHED: "Published",
}

# The fixed aspect vocabulary the review game votes on (locked decision D10).
# Keys are stored in aspect_votes.aspect; labels are client-facing wording.
ASPECT_LABELS: dict[str, str] = {
    "colours": "Colours",
    "typography": "Fonts & type",
    "imagery": "Photos & imagery",
    "layout": "Layout & spacing",
    "personality": "Personality",
    "navigation": "Menus & structure",
}

# The 4-point forced-choice gut scale (locked decision D9). No neutral midpoint
# on purpose: "fine I guess" is not a data point. Keys are the stored ints,
# serialized as strings for the shared VocabularyItem shape.
RATING_LABELS: dict[str, str] = {
    "0": "Not for us",
    "1": "Could work",
    "2": "Like it",
    "3": "Love it",
}
RATING_MIN, RATING_MAX = 0, 3

# Upload constraints (locked decision D13). The allowlist is what a briefing
# package realistically contains; anything executable is deliberately absent.
MAX_UPLOAD_BYTES = 25 * 1024 * 1024
ALLOWED_UPLOAD_EXTENSIONS = frozenset(
    {"png", "jpg", "jpeg", "webp", "pdf", "txt", "md", "eml", "docx"}
)

# The research-package import contract version this build understands.
IMPORT_SCHEMA_VERSION = 1


def vocabulary(labels: dict[str, str]) -> list[dict[str, str]]:
    """Serialize a vocabulary for /api/meta as [{value, label}]."""
    return [{"value": value, "label": label} for value, label in labels.items()]
