"""
Dynamic WTForms form class factory for orama figure strategies.
"""

from enum import Enum
from typing import Any

from babel.support import Translations
from fields_metadata import FieldMetadata
from therismos.grouping import AggregationFunction
from wtforms import (
    BooleanField,
    FieldList,
    FloatField,
    Form,
    FormField,
    IntegerField,
    SelectField,
    SelectMultipleField,
    StringField,
    TextAreaField,
)
from wtforms.validators import Optional

from orama.enums import GeoProjection, LineInterpolation, LinePlacement, LineSortOrder, SortOrder
from orama.options import (
    BooleanOption,
    FloatOption,
    GeoProjectionOption,
    IntOption,
    LineInterpolationOption,
    LinePlacementOption,
    LineSortOrderOption,
    SortOrderOption,
    StringOption,
)
from orama.strategies.base import FigureStrategy
from orama.variables import CategoricalVariable, NumericalAggregationVariable
from orama.web.forms import ColorRuleForm, PathVariableEntryForm

VAR_PREFIX: str = "var__"
OPT_PREFIX: str = "opt__"
LBL_PREFIX: str = "lbl__"

_ENUM_OPTION_TYPES: dict[type, tuple[type[Enum], dict[str, Any]]] = {
    SortOrderOption: (SortOrder, {"coerce": int}),
    LinePlacementOption: (LinePlacement, {}),
    LineInterpolationOption: (LineInterpolation, {}),
    LineSortOrderOption: (LineSortOrder, {}),
    GeoProjectionOption: (GeoProjection, {}),
}


def label_param_name(var_desc: dict[str, Any]) -> str:
    """
    Derive the label parameter name for a variable descriptor.

    :param var_desc: Variable descriptor returned by ``ChartVariable.describe()``.
    :type var_desc: dict[str, Any]
    :return: The label parameter name (e.g. ``"value_label"`` or ``"category_label"``).
    :rtype: str
    """
    if var_desc["type"] == NumericalAggregationVariable:
        return "value_label"
    map_to: str = var_desc["map_to"]
    for suffix in ("_category_variables", "_variables", "_variable"):
        if map_to.endswith(suffix):
            return map_to[: -len(suffix)] + "_label"
    return map_to + "_label"


def _add_variable_field(
    attrs: dict[str, Any],
    var_desc: dict[str, Any],
    fields_metadata: dict[str, FieldMetadata],
    tr: Translations | None = None,
) -> None:
    """
    Add the appropriate WTForms field(s) to ``attrs`` for the given variable descriptor.

    :param attrs: Mutable dict used to build the dynamic form class.
    :param var_desc: Variable descriptor returned by ``ChartVariable.describe()``.
    :param fields_metadata: Mapping of field paths to their metadata.
    :param tr: Optional translations instance for field labels. Defaults to ``None``.
    :type tr: Translations | None
    """
    _t = tr.gettext if tr is not None else (lambda s: s)
    map_to: str = var_desc["map_to"]
    var_type: type = var_desc["type"]
    candidates: list[str] = var_desc["candidate_fields"]
    choices = [(f, f) for f in candidates]
    default_choice = choices[0][0] if choices else ""

    if var_type == NumericalAggregationVariable:
        attrs[f"{VAR_PREFIX}{map_to}"] = SelectField(
            _t(var_desc["name"]),
            choices=choices,
            default=default_choice,
            validate_choice=False,
        )
        agg_choices = [(f.value, _t(f.name.replace("_", " ").title())) for f in AggregationFunction]
        attrs[f"{VAR_PREFIX}{map_to}__function"] = SelectField(
            f"{_t(var_desc['name'])} {_t('function')}",
            choices=agg_choices,
            default=agg_choices[0][0] if agg_choices else "",
            validate_choice=False,
        )
    elif var_type == CategoricalVariable and var_desc.get("ordered", False):
        attrs[f"{VAR_PREFIX}{map_to}"] = FieldList(FormField(PathVariableEntryForm), min_entries=0)
    elif var_type == CategoricalVariable and var_desc.get("multicategorical", False):
        attrs[f"{VAR_PREFIX}{map_to}"] = SelectMultipleField(
            _t(var_desc["name"]),
            choices=choices,
            default=[default_choice] if default_choice else [],
            validate_choice=False,
        )
    else:
        extra = [("", _t("— none —"))] if var_desc.get("optional") else []
        attrs[f"{VAR_PREFIX}{map_to}"] = SelectField(
            _t(var_desc["name"]),
            choices=extra + choices,
            default="" if extra else default_choice,
            validate_choice=False,
        )


def _add_option_field(
    attrs: dict[str, Any],
    opt_desc: dict[str, Any],
    tr: Translations | None = None,
) -> None:
    """
    Add the appropriate WTForms field to ``attrs`` for the given option descriptor.

    :param attrs: Mutable dict used to build the dynamic form class.
    :param opt_desc: Option descriptor returned by ``ChartOption.describe()``.
    :param tr: Optional translations instance for field labels. Defaults to ``None``.
    :type tr: Translations | None
    """
    _t = tr.gettext if tr is not None else (lambda s: s)
    map_to: str = opt_desc["map_to"]
    opt_type: type = opt_desc["type"]
    default = opt_desc.get("default_value")

    if opt_type == BooleanOption:
        attrs[f"{OPT_PREFIX}{map_to}"] = BooleanField(
            _t(opt_desc["name"]),
            default=bool(default),
        )
    elif opt_type == FloatOption:
        attrs[f"{OPT_PREFIX}{map_to}"] = FloatField(
            _t(opt_desc["name"]),
            default=default,
            validators=[Optional()],
        )
    elif opt_type == StringOption:
        attrs[f"{OPT_PREFIX}{map_to}"] = StringField(
            _t(opt_desc["name"]),
            default=default or "",
        )
    elif opt_type == IntOption:
        attrs[f"{OPT_PREFIX}{map_to}"] = IntegerField(
            _t(opt_desc["name"]),
            default=default,
            validators=[Optional()],
        )
    elif opt_type in _ENUM_OPTION_TYPES:
        enum_cls, extra = _ENUM_OPTION_TYPES[opt_type]
        choices = [(e.value, _t(e.name.replace("_", " ").title())) for e in enum_cls]
        attrs[f"{OPT_PREFIX}{map_to}"] = SelectField(
            _t(opt_desc["name"]),
            choices=choices,
            default=default.value if isinstance(default, enum_cls) else list(enum_cls)[0].value,
            validate_choice=False,
            **extra,
        )


def create_figure_form_class(
    strategy_cls: type[FigureStrategy],
    fields_metadata: dict[str, FieldMetadata],
    tr: Translations | None = None,
) -> type:
    """
    Return a WTForms Form subclass for the given strategy and field metadata.

    The returned class contains:

    - ``var__{map_to}`` fields for each variable (SelectField or SelectMultipleField).
    - ``var__{map_to}__function`` for NumericalAggregationVariable.
    - ``opt__{map_to}`` fields for each option.
    - ``color_rules`` FieldList of FormField(ColorRuleForm).

    :param strategy_cls: The figure strategy class to build a form for.
    :type strategy_cls: type[FigureStrategy]
    :param fields_metadata: Mapping of field paths to their metadata.
    :type fields_metadata: dict[str, FieldMetadata]
    :param tr: Optional translations instance for form labels. Defaults to ``None``.
    :type tr: Translations | None
    :return: A dynamically created WTForms Form subclass.
    :rtype: type
    """
    _t = tr.gettext if tr is not None else (lambda s: s)
    description = strategy_cls.describe(fields_metadata)
    attrs: dict[str, Any] = {}

    for var_desc in description.variables.values():
        _add_variable_field(attrs, var_desc, fields_metadata, tr)

    for opt_desc in description.options.values():
        _add_option_field(attrs, opt_desc, tr)

    attrs["color_rules"] = FieldList(FormField(ColorRuleForm), min_entries=0)

    attrs[f"{LBL_PREFIX}title"] = StringField(_t("Title"), default="")
    attrs[f"{LBL_PREFIX}subtitle"] = StringField(_t("Subtitle"), default="")
    attrs[f"{LBL_PREFIX}caption"] = StringField(_t("Caption"), default="")
    attrs[f"{LBL_PREFIX}description"] = TextAreaField(_t("Description"), default="")
    attrs[f"{LBL_PREFIX}storytelling_context"] = TextAreaField(_t("Storytelling Context"), default="")

    seen_label_params: set[str] = set()
    for var_desc in description.variables.values():
        label_param = label_param_name(var_desc)
        if label_param not in seen_label_params:
            seen_label_params.add(label_param)
            attrs[f"{LBL_PREFIX}{label_param}"] = StringField(
                f"{_t(var_desc['name'])} {_t('label')}",
                default="",
            )

    return type("FigureForm", (Form,), attrs)


__all__ = [
    "VAR_PREFIX",
    "OPT_PREFIX",
    "LBL_PREFIX",
    "label_param_name",
    "create_figure_form_class",
]
