"""7O Forms Codegen policy. Consumes the 7N public contract; does not bind runtime."""
# Policy prose is intentionally long; wrap would hide the generation rules.
# ruff: noqa: E501
from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Literal

from src.domain.forms.canonical import FormDefinition
from src.domain.forms.conditions import FormConditionSet
from src.domain.forms.steps import FormStepPlan
from src.domain.forms.validation import ERROR_CODES as VALIDATION_ERROR_CODES

INSTALLED_FORMS_SUBMIT_ROUTE = "POST /api/forms/{form_key}/submissions"
DASHBOARD_FORMS_SUBMIT_ROUTE = (
    "POST /api/projects/{project_id}/forms/{form_key}/submissions"
)
PUBLIC_REQUEST_KEYS = ("values", "challenge")
LEGACY_CONTACT_ROUTE = "POST /api/contact"
LEGACY_SUBMIT_FORM_ENTRY = "submitFormEntry"

FORMS_CODEGEN_POLICY = """\
For generic public submission forms (contact, quote, lead capture, intake, inquiry, application, survey-like one-shot), generate the canonical P2HS Forms client.

Render FormDefinition. Mirror show_when / require_when for UX only (equals, not_equals, is_present, is_missing; missing ≠ null; false and 0 are present; no JS truthiness). Multi-step is client-only: Next does not POST; Final Submit POSTs once with a flat values object.

Browser calls host-relative POST /api/forms/{formKey}/submissions on the installed app origin. Do not call Dashboard, Core, or Cloud hostnames. Do not send project_id, workspace_id, user_id, client_identity, definition, actions, persist_first, workflow, binding, or provider_hint.

Success is ok=true and status=accepted. Do not require submission_id (honeypot fake success omits it). Do not interpret accepted as CRM/email/automation completion.

Do not generate CRM, email.send, automation.workflow.trigger, or HTTP forward from the form client. Do not call /api/projects/.../capabilities/* for generic form submit. New contact/quote/intake installs POST /api/forms/{formKey}/submissions. Do not generate POST /api/contact, p2hs-contact notify helpers, or inline email copy for new projects. Leave POST /api/contact and submitFormEntry only for already-generated apps and non-Forms entity create. Do not copy catalog bodies of contact_capture.admin_notify or contact_capture.confirmation into generated source — reference those template keys via Forms post-submit actions.

Auth, CRUD editors, search/filter, checkout, and settings are not canonical Forms.

A generated form is production-callable when 7P has bound (project_id, form_key) from installed composition. Unbound projects correctly return form_unavailable.
"""

FORMS_CODEGEN_RULES = (
    "Classify intent before emitting. public_submission_form → canonical Forms. auth/CRUD/search/checkout/settings → existing product runtime. Explicit HubSpot/Salesforce/Make native form → provider escape hatch only.",
    "Installed generated apps POST /api/forms/{form_key}/submissions (no project in the URL). Dashboard session path remains /api/projects/{project_id}/forms/{form_key}/submissions. Same 7N JSON contract.",
    "Client-safe render config only: key, types, labels, options, placeholders, steps, show_when, require_when, success copy. Never ship persist_first, post_submit_plan, run_when, bindings, workflow aliases, forward URLs, provider hints, EmailTemplateInvocation, or {{var}} renderers. New Forms contact/quote installs own notifications in server-side post-submit actions (canonical template invocation). Do not generate a parallel `/api/contact` notify for the same new install.",
    "Client validation is UX only. Server 7C/7L remain authority. Omit hidden fields before submit. Map aggregated field codes including conditional_field_not_allowed and invalid_file.",
    "File fields: use the installed provider-neutral uploadApplicationFile adapter (or its Forms BFF uploadFormFile wrapper) → {file_id}. Compiled record schemas mark these fields as type=text, semantic_type=file_reference. Never multipart final submit. Never choose a provider or emit raw storage.file.* / storage.presign_upload calls, object_key, or provider names.",
    "Do not retry uncertain network failures automatically. Debounce the submit button only.",
)

FormIntent = Literal[
    "public_submission_form",
    "legacy_contact_block",
    "auth",
    "crud",
    "search",
    "checkout",
    "settings",
    "provider_escape",
]


@dataclass(frozen=True)
class FormsGenerationDecision:
    intent: FormIntent
    runtime: str
    route: str | None
    notes: tuple[str, ...] = ()


PUBLIC_SUBMISSION_HINTS = (
    "contact form",
    "quote request",
    "lead capture",
    "intake form",
    "inquiry form",
    "application form",
    "newsletter signup",
    "survey",
    "request form",
    "send me an email when the form submits",
    "send confirmation to the customer",
    "notify the owner",
    "notify site owner",
)
NOT_FORMS_HINTS = {
    "auth": ("login", "sign up", "signup", "password reset", "magic link"),
    "crud": ("edit record", "update profile", "admin editor", "crud"),
    "search": ("search form", "filter form", "faceted"),
    "checkout": ("checkout", "payment form", "billing card"),
    "settings": ("settings panel", "account settings"),
}
PROVIDER_ESCAPE_HINTS = (
    "hubspot form",
    "embed hubspot",
    "salesforce web-to-lead",
    "web to lead",
    "native make webhook form",
)

CLIENT_SAFE_FIELD_KEYS = (
    "key",
    "type",
    "label",
    "placeholder",
    "required",
    "options",
)
FORBIDDEN_CLIENT_CONFIG_KEYS = frozenset(
    {
        "actions",
        "plan",
        "persist_first",
        "workflow",
        "provider_hint",
        "binding",
        "target",
        "target_url",
        "run_when",
        "anti_abuse",
        "forward_url",
    }
)
GENERIC_FORMS_LEAK_TOKENS: tuple[str, ...] = (
    "hubspot.create_contact",
    "salesforce.create_contact",
    "resend.send_email",
    "mailgun.send_email",
    "ses.send_email",
    "make_com.trigger_scenario",
    "n8n.trigger_workflow",
    "/capabilities/crm.contact.upsert",
    "/capabilities/email.send",
    "/capabilities/automation.workflow.trigger",
    "submitFormEntry",
    "FormRuntimeStore",
    "ResolvedFormRuntime",
    "beapipie",
    "P2HS_BACKEND_URL",
    "CORE_API_BASE_URL",
    "api.hubapi.com",
    "hooks.make.com",
)
FIELD_ERROR_CODES: tuple[str, ...] = VALIDATION_ERROR_CODES + (
    "conditional_field_not_allowed",
    "invalid_file",
)

FORMS_CODEGEN_PHASE_7O: dict[str, Any] = {
    "slice_id": "7o_codegen_exposure",
    "status": "frozen",
    "installed_route": INSTALLED_FORMS_SUBMIT_ROUTE,
    "dashboard_route": DASHBOARD_FORMS_SUBMIT_ROUTE,
    "legacy_contact_unchanged": True,
    "submit_form_entry_unchanged": True,
    "app_blocks_unchanged": True,
    "runtime_binding": "7p",
    "form_submit_capability": False,
    "docs": "docs/audits/FORMS_CODEGEN_7O.md",
}


def classify_form_intent(requirement: str) -> FormIntent:
    text = requirement.strip().lower()
    if any(hint in text for hint in PROVIDER_ESCAPE_HINTS):
        return "provider_escape"
    if "legacy /api/contact" in text or "keep /api/contact" in text:
        return "legacy_contact_block"
    if any(
        token in text
        for token in ("forms.contact", "forms.quote_request", "contact-capture")
    ):
        return "public_submission_form"
    for intent, hints in NOT_FORMS_HINTS.items():
        if any(hint in text for hint in hints):
            return intent  # type: ignore[return-value]
    if any(hint in text for hint in PUBLIC_SUBMISSION_HINTS):
        return "public_submission_form"
    if "form" in text and not any(
        n in text for n in ("login", "search", "settings", "checkout", "edit")
    ):
        return "public_submission_form"
    return "crud"


def select_forms_generation(requirement: str) -> FormsGenerationDecision:
    intent = classify_form_intent(requirement)
    if intent == "public_submission_form":
        return FormsGenerationDecision(
            intent=intent,
            runtime="canonical_forms",
            route=INSTALLED_FORMS_SUBMIT_ROUTE,
            notes=("bound_when_composition_installed",),
        )
    if intent == "legacy_contact_block":
        return FormsGenerationDecision(
            intent=intent,
            runtime="legacy_contact_adapter",
            route=LEGACY_CONTACT_ROUTE,
            notes=("legacy_deployments_retained", "deprecated_for_new_projects"),
        )
    if intent == "provider_escape":
        return FormsGenerationDecision(
            intent=intent,
            runtime="provider_escape",
            route=None,
            notes=("not_canonical_forms",),
        )
    return FormsGenerationDecision(
        intent=intent,
        runtime="existing_product",
        route=None,
        notes=("not_canonical_forms",),
    )


def project_client_form_config(
    definition: FormDefinition,
    *,
    conditions: FormConditionSet | None = None,
    step_plan: FormStepPlan | None = None,
    success_copy: str = "Thanks — your information was submitted.",
) -> dict[str, Any]:
    """Client-safe render projection. Never includes action/runtime trust config."""
    fields = []
    for field in definition.fields:
        item: dict[str, Any] = {
            "key": field.key,
            "type": field.type,
            "required": bool(field.required),
        }
        if field.label:
            item["label"] = field.label
        if field.placeholder:
            item["placeholder"] = field.placeholder
        if field.options:
            item["options"] = [
                {"value": option.value, "label": option.label} for option in field.options
            ]
        fields.append(item)
    show_when: list[dict[str, Any]] = []
    require_when: list[dict[str, Any]] = []
    if conditions is not None:
        for rule in conditions.field_rules:
            payload = {
                "target_field": rule.target_field,
                "field_key": rule.condition.field_key,
                "operator": rule.condition.operator,
            }
            if "value" in rule.condition.model_fields_set:
                payload["value"] = rule.condition.value
            if rule.effect == "show_when":
                show_when.append(payload)
            elif rule.effect == "require_when":
                require_when.append(payload)
    steps = None
    if step_plan is not None:
        steps = [
            {
                "key": step.key,
                "field_keys": list(step.field_keys),
                **({"title": step.title} if step.title else {}),
            }
            for step in step_plan.steps
        ]
    config = {
        "form_key": definition.key,
        "fields": fields,
        "show_when": show_when,
        "require_when": require_when,
        "success_copy": success_copy,
        "submit": {
            "method": "POST",
            "path": f"/api/forms/{definition.key}/submissions",
        },
    }
    if steps is not None:
        config["steps"] = steps
    return config


def generic_forms_snippet_leaks(source: str) -> tuple[str, ...]:
    lowered = source.lower()
    return tuple(token for token in GENERIC_FORMS_LEAK_TOKENS if token.lower() in lowered)


def example_canonical_form_submit(form_key: str = "intake") -> dict[str, Any]:
    return {
        "path": f"/api/forms/{form_key}/submissions",
        "body": {
            "values": {"email": "ada@example.com", "message": "Hello"},
            "challenge": "<challenge-proof>",
        },
    }
