"""Action flows — an ordered list of steps any trigger can run: a page
button, a data list's row click or row action, a page's or form's events, a
form's button field or a field's on-change. Each step may have a `when`
condition (an expression, app/core/expr.py) and decides whether a failure
stops the flow. Steps run in the browser (frontend/src/lib/actionEngine.ts);
every step that changes data goes through the regular API, so access
policies, field permissions and validation apply exactly as for a person
doing the same by hand.

Text in a step (a message, URL, parameter value) may hold "{{ expression }}"
placeholders; `value` / `record_id` / field values are expressions.
What a flow can read: the page's URL parameters by name, `vars` (page
variables), `user`, `session`, `row` (the clicked row), `values` and the
form's fields by name (form triggers), and `steps.<id>` (earlier steps'
results, e.g. the record a create step made)."""

import re
from typing import Literal

from pydantic import BaseModel, Field, model_validator

from app.core.expr import expression_error

ActionStepType = Literal[
    "run_task",  # task_id; the flow's context goes in as the task's input
    "trigger_workflow",  # workflow_id
    "navigate",  # target_type + target_id (+ params as query parameters)
    "open_form",  # form_id (+ params pre-filling fields)
    "open_popup",  # popup_block_id (+ params readable inside it)
    "close_popup",
    "open_external_url",  # url
    "show_message",  # message, level
    "confirm",  # message; the flow stops unless the person confirms
    "set_variable",  # variable (a page variable) = value
    "set_field",  # field (a form field) = value
    "create_record",  # source_type/source_id + field_values
    "update_record",  # source_type/source_id + record_id + field_values
    "delete_record",  # source_type/source_id + record_id
    "refresh",  # reload the data on the page
    "stop",  # end the flow here (use with `when`)
]
ActionTargetType = Literal["page", "master", "form", "dashboard"]
MessageLevel = Literal["success", "info", "error"]

_TEMPLATE = re.compile(r"\{\{(.+?)\}\}")


def template_errors(text: str | None) -> list[str]:
    """Syntax errors in the {{ expression }} placeholders of `text`."""
    if not text:
        return []
    return [err for err in (expression_error(m.strip()) for m in _TEMPLATE.findall(text)) if err]


class ActionParam(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    value: str = Field(default="", max_length=2000)  # text with {{ }} placeholders


class ActionFieldValue(BaseModel):
    field: str = Field(min_length=1, max_length=200)
    value: str = Field(default="", max_length=2000)  # an expression


class ActionStep(BaseModel):
    id: str = Field(min_length=1, max_length=50)
    type: ActionStepType
    when: str | None = None
    on_error: Literal["stop", "continue"] = "stop"
    task_id: str | None = None
    workflow_id: str | None = None
    target_type: ActionTargetType | None = None
    target_id: str | None = None
    form_id: str | None = None
    popup_block_id: str | None = None
    url: str | None = None
    message: str | None = Field(default=None, max_length=2000)
    level: MessageLevel = "success"
    variable: str | None = None
    field: str | None = None
    value: str | None = Field(default=None, max_length=2000)
    source_type: Literal["master", "form"] | None = None
    source_id: str | None = None
    record_id: str | None = None
    field_values: list[ActionFieldValue] = []
    params: list[ActionParam] = []

    @model_validator(mode="after")
    def _check(self) -> "ActionStep":
        need: dict[str, tuple[str, ...]] = {
            "run_task": ("task_id",),
            "trigger_workflow": ("workflow_id",),
            "navigate": ("target_type", "target_id"),
            "open_form": ("form_id",),
            "open_popup": ("popup_block_id",),
            "open_external_url": ("url",),
            "show_message": ("message",),
            "confirm": ("message",),
            "set_variable": ("variable",),
            "set_field": ("field",),
            "create_record": ("source_type", "source_id"),
            "update_record": ("source_type", "source_id", "record_id"),
            "delete_record": ("source_type", "source_id", "record_id"),
        }
        missing = [k for k in need.get(self.type, ()) if not getattr(self, k)]
        if missing:
            raise ValueError(f"step '{self.id}' ({self.type}) needs {', '.join(missing)}")
        for label, expression in (("condition", self.when), ("value", self.value), ("record", self.record_id)):
            if expression and expression.strip():
                err = expression_error(expression)
                if err:
                    raise ValueError(f"step '{self.id}' {label}: {err}")
        for fv in self.field_values:
            if fv.value.strip():
                err = expression_error(fv.value)
                if err:
                    raise ValueError(f"step '{self.id}' field '{fv.field}': {err}")
        for text in (self.message, self.url, *(p.value for p in self.params)):
            errs = template_errors(text)
            if errs:
                raise ValueError(f"step '{self.id}': {errs[0]}")
        return self


def check_flow(steps: list[ActionStep]) -> None:
    ids = [s.id for s in steps]
    dupes = {i for i in ids if ids.count(i) > 1}
    if dupes:
        raise ValueError(f"step ids must be unique: {', '.join(sorted(dupes))}")


class PageVariableDef(BaseModel):
    """A page variable — a value the page keeps while it's open, set by a
    flow's set_variable step and readable everywhere as vars.<name> (block
    filters "{{vars.status}}", conditions, messages)."""

    name: str = Field(min_length=1, max_length=100, pattern=r"^[A-Za-z_][A-Za-z0-9_]*$")
    # An expression for its starting value (may read URL parameters, user, session).
    default: str | None = None

    @model_validator(mode="after")
    def _check(self) -> "PageVariableDef":
        if self.default and self.default.strip():
            err = expression_error(self.default)
            if err:
                raise ValueError(f"variable '{self.name}' default: {err}")
        return self
