"""Multi-step intake grouping. Not FormDefinition, drafts, or a workflow.

Steps order field collection for a renderer. Canonical captured data stays a
single flat FormSubmission after final 7D acceptance.
"""
from __future__ import annotations

from collections.abc import Mapping, Sequence
from typing import Any

from pydantic import BaseModel, ConfigDict, Field, StrictStr, model_validator

from src.domain.forms.canonical import LOGICAL_KEY_PATTERN, FormDefinition
from src.domain.forms.conditions import FieldConditionalState

STEP_PLAN_ERROR_CODES: tuple[str, ...] = (
    "duplicate_step_key",
    "unknown_step_field",
    "duplicate_step_field",
    "empty_step",
    "unassigned_input_field",
    "form_key_mismatch",
)

FORM_SECTION_IS_NOT_FORM_STEP = True
COLLECTION_MODEL = "client_assembled"
DRAFT_PERSISTENCE = False
STEP_GRAPH = "linear"
HIDDEN_FIELD_MEMBERSHIP = "optional"
INPUT_FIELD_MEMBERSHIP = "exactly_one"

FORMS_MULTI_STEP: dict[str, Any] = {
    "slice_id": "7m_multi_step_forms",
    "status": "frozen",
    "not_form_definition": True,
    "not_form_section": FORM_SECTION_IS_NOT_FORM_STEP,
    "collection_model": COLLECTION_MODEL,
    "draft_persistence": DRAFT_PERSISTENCE,
    "server_draft": False,
    "step_graph": STEP_GRAPH,
    "step_expressions": False,
    "hidden_field_membership": HIDDEN_FIELD_MEMBERSHIP,
    "input_field_membership": INPUT_FIELD_MEMBERSHIP,
    "values_remain_flat": True,
    "fsb_per_step": False,
    "partial_persistence": False,
    "partial_actions": False,
    "anti_abuse": "final_request_only",
    "schema_migration": False,
    "public_bff": False,
    "form_submit_capability": False,
    "codegen": False,
    "app_block_migrated": False,
    "docs": "docs/audits/FORMS_MULTI_STEP_7M.md",
}


class FormStep(BaseModel):
    """Ordered grouping of field keys. Not a submission or renderer section."""

    model_config = ConfigDict(extra="forbid", frozen=True)
    key: StrictStr = Field(min_length=1, max_length=64, pattern=LOGICAL_KEY_PATTERN)
    field_keys: tuple[StrictStr, ...] = 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):
        if len(self.field_keys) != len(set(self.field_keys)):
            raise ValueError("duplicate_step_field")
        return self


class FormStepPlan(BaseModel):
    """Opt-in collection/presentation plan. Empty usage is 'no plan'."""

    model_config = ConfigDict(extra="forbid", frozen=True)
    steps: tuple[FormStep, ...] = Field(min_length=1)
    form_key: StrictStr | None = Field(
        default=None, min_length=1, max_length=64, pattern=LOGICAL_KEY_PATTERN
    )

    @model_validator(mode="after")
    def unique_step_keys(self):
        keys = [step.key for step in self.steps]
        if len(keys) != len(set(keys)):
            raise ValueError("duplicate_step_key")
        return self


def validate_step_plan(
    definition: FormDefinition,
    plan: FormStepPlan,
) -> tuple[str, ...]:
    """Structural membership checks only. No submission evaluation."""
    errors: list[str] = []
    if plan.form_key is not None and plan.form_key != definition.key:
        errors.append("form_key_mismatch")
    declared = {field.key: field for field in definition.fields}
    seen_fields: set[str] = set()
    seen_steps: set[str] = set()
    for step in plan.steps:
        if step.key in seen_steps:
            errors.append("duplicate_step_key")
            continue
        seen_steps.add(step.key)
        if not step.field_keys:
            errors.append("empty_step")
            continue
        for field_key in step.field_keys:
            field = declared.get(field_key)
            if field is None:
                errors.append("unknown_step_field")
                continue
            if field_key in seen_fields:
                errors.append("duplicate_step_field")
                continue
            seen_fields.add(field_key)
    for field in definition.fields:
        if field.key in seen_fields:
            continue
        if field.type == "hidden":
            continue
        errors.append("unassigned_input_field")
    return tuple(dict.fromkeys(errors))


def field_step_map(plan: FormStepPlan) -> dict[str, str]:
    """Deterministic field_key → step_key. Declaration order."""
    mapping: dict[str, str] = {}
    for step in plan.steps:
        for field_key in step.field_keys:
            mapping[field_key] = step.key
    return mapping


def active_steps(
    plan: FormStepPlan,
    visible_fields: Sequence[str] | FieldConditionalState,
    *,
    definition: FormDefinition | None = None,
) -> tuple[str, ...]:
    """Steps with at least one currently visible assigned field.

    Rendering support only. Not an acceptance requirement.
    """
    if isinstance(visible_fields, FieldConditionalState):
        visible = set(visible_fields.visible_fields)
    else:
        visible = set(visible_fields)
    hidden_keys: set[str] = set()
    if definition is not None:
        hidden_keys = {field.key for field in definition.fields if field.type == "hidden"}
    active: list[str] = []
    for step in plan.steps:
        for field_key in step.field_keys:
            if field_key in hidden_keys:
                continue
            if field_key in visible:
                active.append(step.key)
                break
    return tuple(active)


def assemble_candidate_values(*parts: Mapping[str, Any]) -> dict[str, Any]:
    """Client-side collection helper. Later maps overwrite earlier keys."""
    assembled: dict[str, Any] = {}
    for part in parts:
        assembled.update(dict(part))
    return assembled
