"""Frozen Forms vocabulary. Not invoke routes, persistence, or rendering.

`FormDefinition` is renderer-neutral captured-data schema.
`FormSubmission` is the canonical payload. It does not imply a row, job, or CRM object.
"""
from __future__ import annotations

from typing import Any, Literal

from pydantic import (
    BaseModel,
    ConfigDict,
    Field,
    StrictBool,
    StrictFloat,
    StrictInt,
    StrictStr,
    field_validator,
    model_validator,
)

FormFieldType = Literal[
    "text",
    "email",
    "phone",
    "number",
    "textarea",
    "select",
    "radio",
    "checkbox",
    "date",
    "file",
    "hidden",
    "multi_select",
]

FormScalar = StrictStr | StrictInt | StrictFloat | StrictBool | None
FormValue = FormScalar | list[StrictStr] | "FormFileRef"

OPTION_FIELD_TYPES: frozenset[str] = frozenset({"select", "radio", "multi_select"})
STRING_FIELD_TYPES: frozenset[str] = frozenset(
    {"text", "email", "phone", "textarea", "date", "hidden", "select", "radio"}
)

# App Block slots say "checkbox"; starter is ui.checkbox; submitFormEntry uses
# boolean vs multi_select as separate coerce types. Canonical checkbox is boolean.
CHECKBOX_MEANS = "boolean"
MULTI_SELECT_MEANS = "list_of_option_values"
RADIO_MEANS = "single_option_value"
# 7F: type=file is the deferred 7B field, now a single platform file_id reference.
FILE_RUNTIME_SUPPORT = True
FILE_CARDINALITY = "one"
FILE_ID_PREFIX = "file_"
FILE_ID_PATTERN = r"^file_[0-9a-f]{32}$"
DEFAULT_MEANS = "initial_rendered_value"
HIDDEN_IS_TRUSTED = False

LOGICAL_KEY_PATTERN = r"^[a-z][a-z0-9_]{0,62}$"

NAMING_COLLISIONS: dict[str, str] = {
    "form.standard": "starter_ui_composite",
    "form.field": "starter_ui_composite",
    "form.section": "starter_ui_layout",
    "form.public_create": "composer_anonymous_create_primitive",
    "form.public_submission": "pack_provide_id_aliased_to_contact_capture",
    "forms.public_submission": "semantic_pack_alias_to_contact_capture",
    "form.forward": "cloud_http_forward_job",
    "forms.contact": "contact_form_app_block",
    "forms.quote_request": "quote_request_app_block",
}

REJECTED_EQUIVALENCES: tuple[tuple[str, str], ...] = (
    ("form.standard", "FormDefinition"),
    ("form.public_submission", "FormSubmission"),
    ("form.forward", "canonical_post_submit_api"),
    ("forms.contact", "FormDefinition"),
    ("form.public_create", "form.create"),
)

POST_SUBMIT_CONSUMERS: tuple[str, ...] = (
    "crm.contact.upsert",
    "email.send",
    "automation.workflow.trigger",
    "http_forward",
    "internal_local_behavior",
)

RESERVED_CONTEXT_KEYS: frozenset[str] = frozenset(
    {
        "ip_address",
        "user_agent",
        "turnstile_token",
        "turnstile_site_key",
        "turnstile_secret",
        "captcha_provider",
        "cloudflare_ray_id",
        "connection_id",
        "provider",
        "portal_id",
        "org_id",
        "instance_url",
        "webhook_url",
        "target_url",
        "job_id",
        "crm_external_id",
        "hs_object_id",
        "AccountId",
        "dealstage",
        "filterGroups",
        "SOQL",
        "hubspot_field",
        "salesforce_field",
        "mailchimp_list_id",
    }
)

FORBIDDEN_PROVIDER_TOKENS: tuple[str, ...] = (
    "hubspot",
    "salesforce",
    "mailchimp",
    "connection_id",
    "portal_id",
    "org_id",
    "instance_url",
    "AccountId",
    "dealstage",
    "filterGroups",
    "SOQL",
)


class FormCanonicalError(ValueError):
    def __init__(self, code: str) -> None:
        self.code = code
        super().__init__(code)


class FormFileRef(BaseModel):
    """Opaque platform file identity. Not bytes, URLs, or provider storage keys.

    `file_id` reuses Cloud `randomId("file")` (`file_` + 32 hex). Independent of
    `fsb_*`. The only structured canonical form value 7F introduces.
    """

    model_config = ConfigDict(extra="forbid", frozen=True)
    file_id: StrictStr = Field(pattern=FILE_ID_PATTERN)


def parse_form_file_ref(value: Any) -> FormFileRef | None:
    if isinstance(value, FormFileRef):
        return value
    if isinstance(value, dict) and set(value.keys()) == {"file_id"}:
        file_id = value["file_id"]
        if isinstance(file_id, str):
            try:
                return FormFileRef(file_id=file_id)
            except Exception:
                return None
    return None


def _is_scalar_number(value: Any) -> bool:
    return isinstance(value, (int, float)) and not isinstance(value, bool)


def assert_form_value(value: Any) -> None:
    if value is None or isinstance(value, bool) or isinstance(value, str):
        return
    if _is_scalar_number(value):
        return
    if isinstance(value, list) and all(isinstance(item, str) for item in value):
        return
    if parse_form_file_ref(value) is not None:
        return
    raise FormCanonicalError("unsupported_submission_value")


def _default_matches_type(field_type: str, default: Any) -> bool:
    if default is None:
        return True
    if field_type == "file":
        return False
    if field_type == "checkbox":
        return isinstance(default, bool)
    if field_type == "number":
        return _is_scalar_number(default)
    if field_type == "multi_select":
        return isinstance(default, list) and all(isinstance(item, str) for item in default)
    if field_type in STRING_FIELD_TYPES:
        return isinstance(default, str)
    return False


class FormFieldOption(BaseModel):
    """Stable stored value. `label` is presentation only."""

    model_config = ConfigDict(extra="forbid")
    value: StrictStr = Field(min_length=1, max_length=128)
    label: StrictStr | None = Field(default=None, max_length=200)

    @field_validator("value")
    @classmethod
    def value_is_machine_identity(cls, value: str) -> str:
        if value.strip() != value:
            raise FormCanonicalError("invalid_option_value")
        return value


class FormField(BaseModel):
    """Captured-data semantics. Not CSS, layout, or provider property maps."""

    model_config = ConfigDict(extra="forbid")
    key: StrictStr = Field(min_length=1, max_length=64, pattern=LOGICAL_KEY_PATTERN)
    type: FormFieldType
    required: bool = False
    label: StrictStr | None = Field(default=None, max_length=200)
    placeholder: StrictStr | None = Field(default=None, max_length=200)
    default: FormValue = None
    options: list[FormFieldOption] | None = None

    @field_validator("key")
    @classmethod
    def key_is_not_context_or_provider(cls, value: str) -> str:
        if value in RESERVED_CONTEXT_KEYS:
            raise FormCanonicalError("reserved_field_key")
        lowered = value.lower()
        if any(token.lower() == lowered for token in FORBIDDEN_PROVIDER_TOKENS):
            raise FormCanonicalError("provider_field_key")
        return value

    @model_validator(mode="after")
    def structural_field_integrity(self):
        if self.type in OPTION_FIELD_TYPES:
            if not self.options:
                raise FormCanonicalError("options_required")
            values = [option.value for option in self.options]
            if len(values) != len(set(values)):
                raise FormCanonicalError("duplicate_option_value")
        elif self.options:
            raise FormCanonicalError("options_not_allowed")
        if not _default_matches_type(self.type, self.default):
            raise FormCanonicalError("default_type_mismatch")
        if self.type == "file" and self.default is not None:
            raise FormCanonicalError("file_default_not_allowed")
        if self.type == "multi_select" and isinstance(self.default, list) and self.options:
            allowed = {option.value for option in self.options}
            if any(item not in allowed for item in self.default):
                raise FormCanonicalError("default_option_unknown")
        if self.type in {"select", "radio"} and isinstance(self.default, str) and self.options:
            allowed = {option.value for option in self.options}
            if self.default not in allowed:
                raise FormCanonicalError("default_option_unknown")
        return self


class FormDefinition(BaseModel):
    """Logical definition identity. Not a durable form_id and not a renderer contract."""

    model_config = ConfigDict(extra="forbid")
    key: StrictStr = Field(min_length=1, max_length=64, pattern=LOGICAL_KEY_PATTERN)
    fields: list[FormField] = Field(min_length=1)
    title: StrictStr | None = Field(default=None, max_length=200)
    description: StrictStr | None = Field(default=None, max_length=2000)

    @model_validator(mode="after")
    def unique_field_keys(self):
        keys = [field.key for field in self.fields]
        if len(keys) != len(set(keys)):
            raise FormCanonicalError("duplicate_field_key")
        return self


class FormSubmission(BaseModel):
    """Canonical captured payload. Persistence, ids, and security context are out of band."""

    model_config = ConfigDict(extra="forbid")
    form_key: StrictStr = Field(min_length=1, max_length=64, pattern=LOGICAL_KEY_PATTERN)
    values: dict[str, Any] = Field(default_factory=dict)

    @model_validator(mode="after")
    def structural_values(self):
        for key, value in self.values.items():
            if not key or key in RESERVED_CONTEXT_KEYS:
                raise FormCanonicalError("reserved_submission_key")
            if not isinstance(key, str) or not key:
                raise FormCanonicalError("invalid_value_key")
            assert_form_value(value)
        return self


def contact_block_compatible_definition() -> FormDefinition:
    """Prove forms.contact data can be represented. Does not migrate the App Block."""
    return FormDefinition(
        key="contact",
        title="Contact us",
        fields=[
            FormField(key="name", type="text", required=True, label="Name"),
            FormField(key="email", type="email", required=True, label="Email"),
            FormField(key="phone", type="phone", required=False, label="Phone"),
            FormField(key="message", type="textarea", required=True, label="Message"),
        ],
    )


def quote_request_block_compatible_definition() -> FormDefinition:
    """Prove forms.quote_request can be represented independently. File is one FormFileRef."""
    return FormDefinition(
        key="quote_request",
        title="Request a quote",
        fields=[
            FormField(key="name", type="text", required=True, label="Name"),
            FormField(key="email", type="email", required=True, label="Email"),
            FormField(
                key="project_type",
                type="select",
                required=False,
                label="Project type",
                options=[
                    FormFieldOption(value="repair", label="Repair"),
                    FormFieldOption(value="install", label="Install"),
                ],
            ),
            FormField(key="budget", type="number", required=False, label="Budget"),
            FormField(key="message", type="textarea", required=True, label="Message"),
            FormField(key="attachment", type="file", required=False, label="Attachment"),
        ],
    )


def project_form_definition(
    *, form_key: str, config: dict[str, Any] | None
) -> FormDefinition:
    """Resolve an optional project-authored definition from trusted pack config.

    Pack config is control-plane state, never browser input. An absent
    definition preserves the historical App Block contract. Invalid explicit
    definitions fail closed instead of silently selecting another schema.
    """
    definitions = (config or {}).get("form_definitions")
    if isinstance(definitions, dict) and form_key in definitions:
        raw = definitions[form_key]
        if not isinstance(raw, dict):
            raise FormCanonicalError("invalid_project_form_definition")
        try:
            definition = FormDefinition.model_validate(raw)
        except Exception as exc:
            raise FormCanonicalError("invalid_project_form_definition") from exc
        if definition.key != form_key:
            raise FormCanonicalError("project_form_key_mismatch")
        return definition

    fallback = (
        contact_block_compatible_definition()
        if form_key == "contact"
        else quote_request_block_compatible_definition()
        if form_key == "quote_request"
        else None
    )
    if fallback is None:
        raise FormCanonicalError("unsupported_project_form")
    return fallback


FORMS_CANONICAL_MODEL: dict[str, Any] = {
    "slice_id": "7b_canonical_form_model",
    "status": "frozen",
    "verbs_frozen": False,
    "frozen_types": (
        "FormDefinition",
        "FormField",
        "FormFieldOption",
        "FormSubmission",
        "FormFileRef",
    ),
    "not_equivalent": REJECTED_EQUIVALENCES,
    "naming_collisions": NAMING_COLLISIONS,
    "checkbox_means": CHECKBOX_MEANS,
    "multi_select_means": MULTI_SELECT_MEANS,
    "radio_means": RADIO_MEANS,
    "file_runtime_support": FILE_RUNTIME_SUPPORT,
    "default_means": DEFAULT_MEANS,
    "hidden_is_trusted": HIDDEN_IS_TRUSTED,
    "value_types": ("string", "number", "boolean", "null", "list[string]", "FormFileRef"),
    "submission_implies_persistence": False,
    "submission_id": "runtime_envelope_7d",
    "runtime_validation": "frozen_7c",
    "post_submit": "boundary_only",
    "post_submit_consumers": POST_SUBMIT_CONSUMERS,
    "forms_contact_is_form_definition": False,
    "form_standard_is_form_definition": False,
    "form_public_submission_is_form_submission": False,
    "form_forward_is_canonical_runtime": False,
    "auth_is_not_forms": True,
    "email_templates_are_phase_8": True,
    "no_forms_connector": True,
    "no_forms_registry": True,
    "docs": "docs/audits/FORMS_CANONICAL_7B.md",
}
