import abc
import datetime
import decimal
import re
from collections.abc import Callable
from decimal import Decimal
from typing import TYPE_CHECKING, Any, ClassVar, cast
from typing import Optional as TOptional

# Define any "managed" expressions that can be applied to common conditions or validations
# that are built through the UI. These will be used alongside custom expressions
from uuid import UUID

from flask import render_template
from flask_wtf import FlaskForm
from govuk_frontend_wtf.wtforms_widgets import (
    GovCheckboxesInput,
    GovCheckboxInput,
    GovDateInput,
    GovRadioInput,
    GovSubmitInput,
    GovTextArea,
    GovTextInput,
)
from markupsafe import Markup
from pydantic import TypeAdapter
from wtforms import BooleanField, DateField, SelectField, SelectMultipleField, StringField
from wtforms.fields.core import Field
from wtforms.validators import DataRequired, InputRequired, Optional, ReadOnly, ValidationError

from app.common.data.types import ManagedExpressionsEnum, QuestionDataType
from app.common.expressions import EvaluatableExpression
from app.common.expressions.references import EvaluationStatement, ExpressionReference, InterpolationStatement
from app.common.expressions.registry import lookup_managed_expression, register_managed_expression
from app.common.filters import format_date_approximate, format_date_short
from app.common.forms.fields import DecimalWithCommasField, MHCLGApproximateDateInput
from app.deliver_grant_funding.session_models import AddContextToExpressionsModel
from app.types import TRadioItem

if TYPE_CHECKING:
    from app.common.data.models import Expression, Question
    from app.common.expressions.forms import _ManagedExpressionForm


class ManagedExpression(EvaluatableExpression):
    name: ClassVar[ManagedExpressionsEnum]

    # This is the 'center' of the managed expression:
    # For managed conditions, the value that should be compared against
    # For managed validations, the question that is to be validated
    subject_reference: ExpressionReference

    supported_condition_data_types: ClassVar[set[QuestionDataType]]
    supported_validator_data_types: ClassVar[set[QuestionDataType]]
    managed_expression_form_template: ClassVar[str | None]
    _key: ManagedExpressionsEnum

    @property
    def required_functions(self) -> dict[str, Callable[[Any], Any] | type]:
        """
        Used when we evaluate an expression to add specific functions to the list of what simpleeval will accept
        and parse.
        Provides a default implementation that returns an empty dict (no additional functions).

        If your ManagedExpression needs a specific function to evaluate the statement,
        eg. q_543 < calculate_something_complex(),
        override this function as follows:

            @property
            def required_functions(self) -> dict[str, Union[Callable[[Any], Any], type]]:
                return dict(calculate_something_complex=app.stuff.calculate_something_complex)

        Where the keys of the dict are the function names as they will appear in the expression statement, and the
        values are the function definitions.

        """
        return dict()

    @property
    def referenced_question(self) -> Question:
        # todo: this might do a database query per expression on the question - for now we'd anticipate
        #       questions only have one or two managed expressions but in the future we should probably
        #       optimise this to fetch the full schema once and then re-use that throughout these helpers

        # TODO[FSPT-1284]: we need to remove this and - probably - just point at the `subject_reference` here instead?
        #                  That will need a helper to identify what the data type is for the reference; used by
        #                  `build_managed_expression_form` in order to limit what can be selected to compare against
        if not (question := self.subject_reference.question):
            raise ValueError(
                f"ManagedExpression.referenced_question called on {self!r} whose subject_reference "
                f"is not a question reference"
            )
        return question

    @property
    def expression_referenced_question_ids(self) -> list[UUID]:
        """
        Returns a list of question IDs where the safe_qids are as referenced values in the expression itself.
        This is used to create ComponentReferences when the expression is saved.

        Not all expressions are able to reference other questions in the expression themselves so we return an empty
        list by default and this should be overriden in subclasses that have references (currently Integer and Date
        expressions).
        """
        return []

    # implementing these two fields will update the "add/edit condition/validation" pages for any question types
    # that are defined in `question_data_types`.
    @staticmethod
    @abc.abstractmethod
    def get_form_fields(
        subject_reference: ExpressionReference, expression: TOptional[Expression] = None
    ) -> dict[str, Field]:
        """
        A hook used by `build_managed_expression_form`. It should return the set of form fields which need to be
        added to the managed expression form. The fields returned should collect the data needed to define an instance
        of the managed expression.

        It is also called by `concatenate_all_wtf_fields_html` to support conditionally-revealed the fields that a user
        needs to complete when they select this managed expression type from the radio list of available managed
        expressions.

        class GreaterThan(ManagedExpression):
            key: ManagedExpressionsEnum = ManagedExpressionsEnum.GREATER_THAN

        Eg:
            return {
                "greater_than_value": IntegerField(
                    "Minimum value",
                    widget=GovTextInput(),
                    validators=[Optional()],
                    render_kw={"params": {"classes": "govuk-input--width-10"}},
                ),
            }

        Note:   Only add the `Optional()` validator here. If further validation is required (eg. making sure the answer
                is a number) add these validators in `update_validators` instead. This is because if there are multiple
                managed expressions available for a question type they all appear on the same page, and adding
                validation to the fields directly means they are validated even if a different type of expression was
                selected.

        Note: because these are fed into dynamic form generation, and these dynamic forms are rendered automatically,
              we need to specify the parameters that tweak rendering here. `render_kw['params']` is of the same format
              and structure as in Jinja2 templates directly, which closely follows the official GOV.UK Frontend
              nunjucks macros that you can find in the Design System, eg at https://design-system.service.gov.uk/components/text-input/.
        """  # noqa: E501
        ...

    @staticmethod
    @abc.abstractmethod
    def update_validators(form: _ManagedExpressionForm) -> None:
        """
        A hook used by `build_managed_expression_form`. If this managed expression has been selected, then (some or all
        of) the fields are likely to required to correctly define the instance. Mutate the fields on the form to set
        those validators here.

        Set these validators on the form fields that were added in `get_form_fields`. See notes there about why we set
        the validators in a separate function.

        Eg:
            def update_validators(form: "_ManagedExpressionForm") -> None:
                form.greater_than_value.validators = [InputRequired("Enter the minimum value allowed for this question")]
        """  # noqa: E501
        ...

    @classmethod
    def concatenate_all_wtf_fields_html(
        cls, form: _ManagedExpressionForm, subject_reference: ExpressionReference
    ) -> Markup:
        """
        A hook used by `build_managed_expression_form` to support conditionally-revealed the fields that a user needs
        to complete when they select this managed expression type from the radio list of available managed expressions.

        This hook should work automatically for all subclasses, but allows each class to define a
        `managed_expression_form_template` where needed. The use-case for this is when managed expressions can instead
        refer to pieces of data in the system rather than static values.
        """
        if cls.managed_expression_form_template:
            return Markup(render_template(cls.managed_expression_form_template, form=form))

        # FIXME: Re-using cls.get_form_fields() is a 🤏 bit wasteful (building form fields that aren't used).
        fields = [
            getattr(form, field_name)()
            for field_name in cls.get_form_fields(subject_reference=subject_reference).keys()
        ]

        return Markup("\n".join(fields))

    @staticmethod
    @abc.abstractmethod
    def build_from_form(form: _ManagedExpressionForm, subject_reference: ExpressionReference) -> ManagedExpression:
        """
        A hook used by `build_managed_expression_form`. If this managed expression type has been selected during form
        submission, this hook will be called. It should pull data from the form and use that to instantiate and return
        the managed expression.

        Eg:
            def build_from_form(
                form: "_ManagedExpressionForm", subject_reference: "ExpressionReference"
            ) -> "GreaterThan":
                return GreaterThan(
                    subject_reference=subject_reference,
                    minimum_value=form.greater_than_value.data,
                    inclusive=form.greater_than_inclusive.data,
                )
        """
        ...


class BottomOfRangeIsLower:
    def __init__(self, message: str | None = None):
        if not message:
            message = "The minimum value must be lower than the maximum value"
        self.message = message

    def __call__(self, form: FlaskForm, field: Field) -> None:
        bottom_of_range = form.between_bottom_of_range and form.between_bottom_of_range.data  # ty: ignore[unresolved-attribute]
        top_of_range = form.between_top_of_range and form.between_top_of_range.data  # ty: ignore[unresolved-attribute]
        if bottom_of_range and top_of_range:
            if bottom_of_range >= top_of_range:
                raise ValidationError(self.message)


@register_managed_expression
class GreaterThan(ManagedExpression):
    name: ClassVar[ManagedExpressionsEnum] = ManagedExpressionsEnum.GREATER_THAN
    supported_condition_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.NUMBER}
    supported_validator_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.NUMBER}
    managed_expression_form_template: ClassVar[str | None] = (
        "deliver_grant_funding/collections/managed_expressions/greater_than.html"
    )

    _key: ManagedExpressionsEnum = name

    minimum_value: int | Decimal | None
    minimum_expression: ExpressionReference | None = None
    inclusive: bool = False

    @property
    def description(self) -> str:
        return f"Is greater than{' or equal to' if self.inclusive else ''}"

    @property
    def message(self) -> InterpolationStatement:
        return InterpolationStatement(
            f"The answer must be greater than {'or equal to ' if self.inclusive else ''}"
            + f"{self.minimum_expression.wrapped if self.minimum_expression else self.minimum_value}"
        )

    @property
    def statement(self) -> EvaluationStatement:
        min_value_for_stmt = f"Decimal('{self.minimum_value}')"
        return EvaluationStatement(
            f"{self.subject_reference.unwrapped} >{'=' if self.inclusive else ''} "
            + f"{self.minimum_expression.unwrapped if self.minimum_expression else min_value_for_stmt}"
        )

    @property
    def reference_aware_fields(self) -> set[str]:
        return {"minimum_expression"}

    @property
    def expression_referenced_question_ids(self) -> list[UUID]:
        if self.minimum_expression and (question_id := self.minimum_expression.question_id):
            return [question_id]
        return []

    @staticmethod
    def get_form_fields(
        subject_reference: ExpressionReference,
        expression: TOptional[Expression] = None,
    ) -> dict[str, Field]:
        return {
            "greater_than_value": DecimalWithCommasField(
                "Minimum value",
                default=cast(Decimal, expression.context["minimum_value"]) if expression else None,
                places=None,
                rounding=None,
                use_locale=False,
                validators=[Optional()],
                widget=GovTextInput(),
            ),
            "greater_than_expression": StringField(
                "Minimum value",
                default=ExpressionReference(str(minimum_expression)).wrapped
                if expression and (minimum_expression := expression.context.get("minimum_expression"))
                else "",
                widget=GovTextArea(),
            ),
            "greater_than_inclusive": BooleanField(
                "An answer of exactly the minimum value is allowed",
                default=cast(bool, expression.context["inclusive"]) if expression else None,
                widget=GovCheckboxInput(),
            ),
            "add_context": StringField(
                "Reference data",
                widget=GovSubmitInput(),
            ),
            "remove_context": StringField(
                "Remove data",
                widget=GovSubmitInput(),
            ),
        }

    @staticmethod
    def update_validators(form: _ManagedExpressionForm) -> None:
        form.greater_than_value.validators = (  # ty: ignore[unresolved-attribute]
            [InputRequired("Enter the minimum value allowed for this question")]
            if not form.greater_than_expression.data  # ty: ignore[unresolved-attribute]
            else [Optional()]
        )
        form.greater_than_expression.validators = [ReadOnly()]  # ty: ignore[unresolved-attribute]

    @staticmethod
    def build_from_form(
        form: _ManagedExpressionForm,
        subject_reference: ExpressionReference,
        expression: TOptional[Expression] = None,
    ) -> GreaterThan:
        return GreaterThan(
            subject_reference=subject_reference,
            minimum_value=form.greater_than_value.data if not form.greater_than_expression.data else None,  # ty: ignore[unresolved-attribute]
            minimum_expression=form.greater_than_expression.data if form.greater_than_expression.data else None,  # ty: ignore[unresolved-attribute]
            inclusive=form.greater_than_inclusive.data,  # ty: ignore[unresolved-attribute]
        )

    @property
    def required_functions(self) -> dict[str, Callable[[Any], Any] | type[Any]]:
        return {"Decimal": decimal.Decimal}


@register_managed_expression
class LessThan(ManagedExpression):
    name: ClassVar[ManagedExpressionsEnum] = ManagedExpressionsEnum.LESS_THAN
    supported_condition_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.NUMBER}
    supported_validator_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.NUMBER}
    managed_expression_form_template: ClassVar[str | None] = (
        "deliver_grant_funding/collections/managed_expressions/less_than.html"
    )

    _key: ManagedExpressionsEnum = name

    maximum_value: int | Decimal | None
    maximum_expression: ExpressionReference | None = None
    inclusive: bool = False

    @property
    def description(self) -> str:
        return f"Is less than{' or equal to' if self.inclusive else ''}"

    @property
    def message(self) -> InterpolationStatement:
        return InterpolationStatement(
            f"The answer must be less than {'or equal to ' if self.inclusive else ''}"
            + f"{self.maximum_expression.wrapped if self.maximum_expression else self.maximum_value}"
        )

    @property
    def statement(self) -> EvaluationStatement:
        max_value_for_stmt = f"Decimal('{self.maximum_value}')"
        return EvaluationStatement(
            f"{self.subject_reference.unwrapped} <{'=' if self.inclusive else ''} "
            + f"{self.maximum_expression.unwrapped if self.maximum_expression else max_value_for_stmt}"
        )

    @property
    def expression_referenced_question_ids(self) -> list[UUID]:
        if self.maximum_expression:
            if question_id := self.maximum_expression.question_id:
                return [question_id]
        return []

    @property
    def reference_aware_fields(self) -> set[str]:
        return {"maximum_expression"}

    @staticmethod
    def get_form_fields(
        subject_reference: ExpressionReference, expression: TOptional[Expression] = None
    ) -> dict[str, Field]:
        return {
            "less_than_value": DecimalWithCommasField(
                "Maximum value",
                default=cast(Decimal, expression.context["maximum_value"]) if expression else None,
                places=None,
                rounding=None,
                use_locale=False,
                validators=[Optional()],
                widget=GovTextInput(),
            ),
            "less_than_expression": StringField(
                "Maximum value",
                default=ExpressionReference(str(maximum_expression)).wrapped
                if expression and (maximum_expression := expression.context.get("maximum_expression"))
                else "",
                widget=GovTextArea(),
            ),
            "less_than_inclusive": BooleanField(
                "An answer of exactly the maximum value is allowed",
                default=cast(bool, expression.context["inclusive"]) if expression else None,
                widget=GovCheckboxInput(),
            ),
            "add_context": StringField(
                "Reference data",
                widget=GovSubmitInput(),
            ),
            "remove_context": StringField(
                "Remove data",
                widget=GovSubmitInput(),
            ),
        }

    @staticmethod
    def update_validators(form: _ManagedExpressionForm) -> None:
        form.less_than_value.validators = (  # ty: ignore[unresolved-attribute]
            [InputRequired("Enter the maximum value allowed for this question")]
            if not form.less_than_expression.data  # ty: ignore[unresolved-attribute]
            else [Optional()]
        )
        form.less_than_expression.validators = [ReadOnly()]  # ty: ignore[unresolved-attribute]

    @staticmethod
    def build_from_form(form: _ManagedExpressionForm, subject_reference: ExpressionReference) -> LessThan:
        return LessThan(
            subject_reference=subject_reference,
            maximum_value=form.less_than_value.data if not form.less_than_expression.data else None,  # ty: ignore[unresolved-attribute]
            maximum_expression=form.less_than_expression.data if form.less_than_expression.data else None,  # ty: ignore[unresolved-attribute]
            inclusive=form.less_than_inclusive.data,  # ty: ignore[unresolved-attribute]
        )

    @property
    def required_functions(self) -> dict[str, Callable[[Any], Any] | type[Any]]:
        return {"Decimal": decimal.Decimal}


@register_managed_expression
class Between(ManagedExpression):
    name: ClassVar[ManagedExpressionsEnum] = ManagedExpressionsEnum.BETWEEN
    supported_condition_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.NUMBER}
    supported_validator_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.NUMBER}
    managed_expression_form_template: ClassVar[str | None] = (
        "deliver_grant_funding/collections/managed_expressions/between.html"
    )

    _key: ManagedExpressionsEnum = name

    minimum_value: int | Decimal | None
    minimum_expression: ExpressionReference | None = None
    minimum_inclusive: bool = False
    maximum_value: int | Decimal | None
    maximum_expression: ExpressionReference | None = None
    maximum_inclusive: bool = False

    @property
    def description(self) -> str:
        return "Is between"

    @property
    def message(self) -> InterpolationStatement:
        return InterpolationStatement(
            "The answer must be between "
            + f"{self.minimum_expression.wrapped if self.minimum_expression else self.minimum_value}"
            + f"{' (inclusive)' if self.minimum_inclusive else ' (exclusive)'} and "
            + f"{self.maximum_expression.wrapped if self.maximum_expression else self.maximum_value}"
            + f"{' (inclusive)' if self.maximum_inclusive else ' (exclusive)'}"
        )

    @property
    def statement(self) -> EvaluationStatement:
        max_value_for_stmt = f"Decimal('{self.maximum_value}')"
        min_value_for_stmt = f"Decimal('{self.minimum_value}')"
        return EvaluationStatement(
            f"{self.minimum_expression.unwrapped if self.minimum_expression else min_value_for_stmt} "
            f"<{'=' if self.minimum_inclusive else ''} "
            f"{self.subject_reference.unwrapped} "
            f"<{'=' if self.maximum_inclusive else ''} "
            f"{self.maximum_expression.unwrapped if self.maximum_expression else max_value_for_stmt}"
        )

    @property
    def expression_referenced_question_ids(self) -> list[UUID]:
        referenced_ids = []

        if self.minimum_expression and (question_id := self.minimum_expression.question_id):
            referenced_ids.append(question_id)

        if self.maximum_expression and (question_id := self.maximum_expression.question_id):
            referenced_ids.append(question_id)

        return referenced_ids

    @property
    def reference_aware_fields(self) -> set[str]:
        return {"minimum_expression", "maximum_expression"}

    @staticmethod
    def get_form_fields(
        subject_reference: ExpressionReference, expression: TOptional[Expression] = None
    ) -> dict[str, Field]:
        return {
            "between_bottom_of_range": DecimalWithCommasField(
                "Minimum value",
                default=cast(Decimal, expression.context["minimum_value"]) if expression else None,
                places=None,
                rounding=None,
                use_locale=False,
                validators=[Optional()],
                widget=GovTextInput(),
            ),
            "between_bottom_of_range_expression": StringField(
                "Minimum value",
                default=ExpressionReference(str(minimum_expression)).wrapped
                if expression and (minimum_expression := expression.context.get("minimum_expression"))
                else "",
                widget=GovTextArea(),
            ),
            "between_bottom_inclusive": BooleanField(
                "An answer of exactly the minimum value is allowed",
                default=cast(bool, expression.context["minimum_inclusive"]) if expression else None,
                widget=GovCheckboxInput(),
            ),
            "between_top_of_range": DecimalWithCommasField(
                "Maximum value",
                default=cast(Decimal, expression.context["maximum_value"]) if expression else None,
                places=None,
                rounding=None,
                use_locale=False,
                validators=[Optional()],
                widget=GovTextInput(),
            ),
            "between_top_of_range_expression": StringField(
                "Maximum value",
                default=ExpressionReference(str(maximum_expression)).wrapped
                if expression and (maximum_expression := expression.context.get("maximum_expression"))
                else "",
                widget=GovTextArea(),
            ),
            "between_top_inclusive": BooleanField(
                "An answer of exactly the maximum value is allowed",
                default=cast(bool, expression.context["maximum_inclusive"]) if expression else None,
                widget=GovCheckboxInput(),
            ),
            "add_context": StringField(
                "Reference data",
                widget=GovSubmitInput(),
            ),
            "remove_context": StringField(
                "Remove data",
                widget=GovSubmitInput(),
            ),
        }

    @staticmethod
    def update_validators(form: _ManagedExpressionForm) -> None:
        form.between_bottom_of_range.validators = (  # ty: ignore[unresolved-attribute]
            [
                InputRequired("Enter the minimum value allowed for this question"),
                BottomOfRangeIsLower("The minimum value must be lower than the maximum value"),
            ]
            if not form.between_bottom_of_range_expression.data and not form.between_top_of_range_expression.data  # ty: ignore[unresolved-attribute]
            else [InputRequired("Enter the minimum value allowed for this question")]
            if not form.between_bottom_of_range_expression.data  # ty: ignore[unresolved-attribute]
            else [Optional()]
        )
        form.between_top_of_range.validators = (  # ty: ignore[unresolved-attribute]
            [
                InputRequired("Enter the maximum value allowed for this question"),
                BottomOfRangeIsLower("The maximum value must be higher than the minimum value"),
            ]
            if not form.between_bottom_of_range_expression.data and not form.between_top_of_range_expression.data  # ty: ignore[unresolved-attribute]
            else [InputRequired("Enter the maximum value allowed for this question")]
            if not form.between_top_of_range_expression.data  # ty: ignore[unresolved-attribute]
            else [Optional()]
        )

    @staticmethod
    def build_from_form(form: _ManagedExpressionForm, subject_reference: ExpressionReference) -> Between:
        return Between(
            subject_reference=subject_reference,
            minimum_value=form.between_bottom_of_range.data  # ty: ignore[unresolved-attribute]
            if not form.between_bottom_of_range_expression.data  # ty: ignore[unresolved-attribute]
            else None,
            minimum_expression=form.between_bottom_of_range_expression.data  # ty: ignore[unresolved-attribute]
            if form.between_bottom_of_range_expression.data  # ty: ignore[unresolved-attribute]
            else None,
            minimum_inclusive=form.between_bottom_inclusive.data,  # ty: ignore[unresolved-attribute]
            maximum_value=form.between_top_of_range.data if not form.between_top_of_range_expression.data else None,  # ty: ignore[unresolved-attribute]
            maximum_expression=form.between_top_of_range_expression.data  # ty: ignore[unresolved-attribute]
            if form.between_top_of_range_expression.data  # ty: ignore[unresolved-attribute]
            else None,
            maximum_inclusive=form.between_top_inclusive.data,  # ty: ignore[unresolved-attribute]
        )

    @property
    def required_functions(self) -> dict[str, Callable[[Any], Any] | type[Any]]:
        return {"Decimal": decimal.Decimal}


class BaseDataSourceManagedExpression(ManagedExpression):
    @property
    @abc.abstractmethod  # todo: decorator does nothing here because ABCMeta cant be used
    def referenced_data_source_items(self) -> list[TRadioItem]:
        raise NotImplementedError


@register_managed_expression
class AnyOf(BaseDataSourceManagedExpression):
    name: ClassVar[ManagedExpressionsEnum] = ManagedExpressionsEnum.ANY_OF
    supported_condition_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.RADIOS}
    supported_validator_data_types: ClassVar[set[QuestionDataType]] = {}  # ty: ignore[invalid-assignment]
    managed_expression_form_template: ClassVar[str | None] = None

    _key: ManagedExpressionsEnum = name

    items: list[TRadioItem]

    @property
    def description(self) -> str:
        return "any of"

    @property
    def message(self) -> InterpolationStatement:
        if len(self.items) == 1:
            return InterpolationStatement(f"The answer is “{self.items[0]['label']}”")

        return InterpolationStatement(f"The answer is one of “{'”, “'.join(c['label'] for c in self.items)}”")

    @property
    def statement(self) -> EvaluationStatement:
        item_keys = {str(item["key"]) for item in self.items}
        return EvaluationStatement(f"{self.subject_reference.unwrapped} in {item_keys}")

    @staticmethod
    def get_form_fields(
        subject_reference: ExpressionReference, expression: TOptional[Expression] = None
    ) -> dict[str, Field]:
        question = subject_reference.question
        if not question or question.data_source is None:
            raise ValueError("AnyOf managed expressions are only implemented for questions with data sources")

        return {
            "any_of": SelectMultipleField(
                "Choose from the list of options",
                default=[item["key"] for item in expression.context["items"]] if expression else None,  # ty: ignore[invalid-argument-type, not-iterable]
                widget=GovCheckboxesInput(),
                choices=[(item.key, item.label) for item in question.data_source.items],
                validators=[Optional()],
                render_kw={"params": {"fieldset": {"legend": {"classes": "govuk-visually-hidden"}}}},
            ),
        }

    @staticmethod
    def update_validators(form: _ManagedExpressionForm) -> None:
        form.any_of.validators = [  # ty: ignore[unresolved-attribute]
            DataRequired("Choose at least one option"),
        ]

    @staticmethod
    def build_from_form(form: _ManagedExpressionForm, subject_reference: ExpressionReference) -> AnyOf:
        question = subject_reference.question
        if not question or question.data_source is None:
            raise ValueError("AnyOf managed expressions are only implemented for questions with data sources")

        item_labels = {choice.key: choice.label for choice in question.data_source.items}

        items = [TRadioItem(key=key, label=item_labels[key]) for key in form.any_of.data]  # ty: ignore[unresolved-attribute]
        return AnyOf(
            subject_reference=subject_reference,
            items=items,
        )

    @property
    def referenced_data_source_items(self) -> list[TRadioItem]:
        return self.items


@register_managed_expression
class IsYes(ManagedExpression):
    name: ClassVar[ManagedExpressionsEnum] = ManagedExpressionsEnum.IS_YES
    supported_condition_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.YES_NO}
    supported_validator_data_types: ClassVar[set[QuestionDataType]] = {}  # ty: ignore[invalid-assignment]
    managed_expression_form_template: ClassVar[str | None] = None

    _key: ManagedExpressionsEnum = name

    @property
    def description(self) -> str:
        return "is yes"

    @property
    def message(self) -> InterpolationStatement:
        return InterpolationStatement("The answer is “yes”")

    @property
    def statement(self) -> EvaluationStatement:
        return EvaluationStatement(f"{self.subject_reference.unwrapped} is True")

    @staticmethod
    def get_form_fields(
        subject_reference: ExpressionReference, expression: TOptional[Expression] = None
    ) -> dict[str, Field]:
        return {}

    @staticmethod
    def update_validators(form: _ManagedExpressionForm) -> None:
        pass

    @staticmethod
    def build_from_form(form: _ManagedExpressionForm, subject_reference: ExpressionReference) -> IsYes:
        return IsYes(subject_reference=subject_reference)


@register_managed_expression
class IsNo(ManagedExpression):
    name: ClassVar[ManagedExpressionsEnum] = ManagedExpressionsEnum.IS_NO
    supported_condition_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.YES_NO}
    supported_validator_data_types: ClassVar[set[QuestionDataType]] = {}  # ty: ignore[invalid-assignment]
    managed_expression_form_template: ClassVar[str | None] = None

    _key: ManagedExpressionsEnum = name

    @property
    def description(self) -> str:
        return "is no"

    @property
    def message(self) -> InterpolationStatement:
        return InterpolationStatement("The answer is “no”")

    @property
    def statement(self) -> EvaluationStatement:
        return EvaluationStatement(f"{self.subject_reference.unwrapped} is False")

    @staticmethod
    def get_form_fields(
        subject_reference: ExpressionReference, expression: TOptional[Expression] = None
    ) -> dict[str, Field]:
        return {}

    @staticmethod
    def update_validators(form: _ManagedExpressionForm) -> None:
        pass

    @staticmethod
    def build_from_form(form: _ManagedExpressionForm, subject_reference: ExpressionReference) -> IsNo:
        return IsNo(subject_reference=subject_reference)


@register_managed_expression
class Specifically(BaseDataSourceManagedExpression):
    name: ClassVar[ManagedExpressionsEnum] = ManagedExpressionsEnum.SPECIFICALLY
    supported_condition_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.CHECKBOXES}
    supported_validator_data_types: ClassVar[set[QuestionDataType]] = {}  # ty: ignore[invalid-assignment]
    managed_expression_form_template: ClassVar[str | None] = None

    _key: ManagedExpressionsEnum = name

    item: TRadioItem

    @property
    def description(self) -> str:
        return "specifically"

    @property
    def message(self) -> InterpolationStatement:
        return InterpolationStatement(f"The answer is “{self.item['label']}”")

    @property
    def statement(self) -> EvaluationStatement:
        # TODO: This a bit fragile - another reason for referencing a data source item?
        return EvaluationStatement(f"{self.item['key']!r} in {self.subject_reference.unwrapped}")

    @staticmethod
    def get_form_fields(
        subject_reference: ExpressionReference, expression: TOptional[Expression] = None
    ) -> dict[str, Field]:
        question = subject_reference.question
        if not question or question.data_source is None:
            raise ValueError("Specifically managed expressions are only implemented for questions with data sources")

        return {
            "specifically": SelectField(
                "Choose from the list of options",
                default=expression.context["item"]["key"] if expression else None,  # ty: ignore[invalid-argument-type, not-subscriptable]
                widget=GovRadioInput(),
                choices=[(item.key, item.label) for item in question.data_source.items],
                validators=[DataRequired("Choose one option")],
                render_kw={"params": {"fieldset": {"legend": {"classes": "govuk-visually-hidden"}}}},
            ),
        }

    @staticmethod
    def update_validators(form: _ManagedExpressionForm) -> None:
        pass

    @staticmethod
    def build_from_form(form: _ManagedExpressionForm, subject_reference: ExpressionReference) -> Specifically:
        question = subject_reference.question
        if not question or question.data_source is None:
            raise ValueError("Specifically managed expressions are only implemented for questions with data sources")

        item_labels = {item.key: item.label for item in question.data_source.items}
        selected_key = form.specifically.data  # ty: ignore[unresolved-attribute]
        item: TRadioItem = {"key": selected_key, "label": item_labels[selected_key]}
        return Specifically(subject_reference=subject_reference, item=item)

    @property
    def referenced_data_source_items(self) -> list[TRadioItem]:
        return [self.item]


@register_managed_expression
class IsBefore(ManagedExpression):
    name: ClassVar[ManagedExpressionsEnum] = ManagedExpressionsEnum.IS_BEFORE
    supported_condition_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.DATE}
    supported_validator_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.DATE}
    managed_expression_form_template: ClassVar[str | None] = (
        "deliver_grant_funding/collections/managed_expressions/is_before.html"
    )

    _key: ManagedExpressionsEnum = name

    latest_value: datetime.date | None
    latest_expression: ExpressionReference | None = None
    inclusive: bool = False

    @property
    def description(self) -> str:
        return f"Is {'on or ' if self.inclusive else ''}before"

    @property
    def message(self) -> InterpolationStatement:
        if self.latest_value:
            formatted_latest_value = (
                format_date_short(self.latest_value)
                if not self.subject_reference.presentation_options.approximate_date
                else format_date_approximate(self.latest_value)
            )
        return InterpolationStatement(
            f"The answer must be {'on or ' if self.inclusive else ''}before "
            + (self.latest_expression.wrapped if self.latest_expression else formatted_latest_value)
        )

    @property
    def statement(self) -> EvaluationStatement:
        if not self.latest_expression:
            assert self.latest_value
        date_expression = (
            self.latest_expression.unwrapped
            if self.latest_expression
            else f"date({self.latest_value.year}, {self.latest_value.month}, {self.latest_value.day})"  # ty: ignore[unresolved-attribute]
        )
        return EvaluationStatement(
            f"{self.subject_reference.unwrapped} <{'=' if self.inclusive else ''} {date_expression}"
        )

    @property
    def expression_referenced_question_ids(self) -> list[UUID]:
        if self.latest_expression and (question_id := self.latest_expression.question_id):
            return [question_id]
        return []

    @property
    def reference_aware_fields(self) -> set[str]:
        return {"latest_expression"}

    @staticmethod
    def get_form_fields(
        subject_reference: ExpressionReference, expression: TOptional[Expression] = None
    ) -> dict[str, Field]:
        return {
            "latest_value": DateField(
                "Latest date",
                default=datetime.datetime.strptime(cast(str, latest_value), "%Y-%m-%d").date()
                if expression and (latest_value := expression.context.get("latest_value"))
                else None,
                widget=GovDateInput()
                if not subject_reference.presentation_options.approximate_date
                else MHCLGApproximateDateInput(),
                validators=[Optional()],
                format=["%d %m %Y", "%d %b %Y", "%d %B %Y"]
                if not subject_reference.presentation_options.approximate_date
                else ["%m %Y", "%b %Y", "%B %Y"],  # multiple formats to help user input
            ),
            "latest_expression": StringField(
                "Latest date",
                default=ExpressionReference(str(latest_expression)).wrapped
                if expression and (latest_expression := expression.context.get("latest_expression"))
                else "",
                widget=GovTextArea(),
            ),
            "latest_inclusive": BooleanField(
                "An answer of exactly the latest date is allowed",
                default=cast(bool, expression.context["inclusive"]) if expression else None,
                widget=GovCheckboxInput(),
            ),
            "add_context": StringField(
                "Reference data",
                widget=GovSubmitInput(),
            ),
            "remove_context": StringField(
                "Remove data",
                widget=GovSubmitInput(),
            ),
        }

    @staticmethod
    def update_validators(form: _ManagedExpressionForm) -> None:
        form.latest_value.validators = (  # ty: ignore[unresolved-attribute]
            [DataRequired("Enter the date which this answer must come before")]
            if not form.latest_expression.data  # ty: ignore[unresolved-attribute]
            else [Optional()]
        )
        form.latest_expression.validators = [ReadOnly()]  # ty: ignore[unresolved-attribute]

    @staticmethod
    def build_from_form(form: _ManagedExpressionForm, subject_reference: ExpressionReference) -> IsBefore:
        return IsBefore(
            subject_reference=subject_reference,
            latest_value=form.latest_value.data if not form.latest_expression.data else None,  # ty: ignore[unresolved-attribute]
            latest_expression=form.latest_expression.data if form.latest_expression.data else None,  # ty: ignore[unresolved-attribute]
            inclusive=form.latest_inclusive.data,  # ty: ignore[unresolved-attribute]
        )

    @classmethod
    def prepare_form_data(cls, add_context_data: AddContextToExpressionsModel) -> dict[str, Any]:
        data = super().prepare_form_data(add_context_data)

        if data.get("latest_value"):
            data["latest_value"] = datetime.datetime.strptime(data["latest_value"], "%Y-%m-%d").date()

        return data

    @property
    def required_functions(self) -> dict[str, Callable[[Any], Any] | type[Any]]:
        return {"date": datetime.date}


@register_managed_expression
class IsAfter(ManagedExpression):
    name: ClassVar[ManagedExpressionsEnum] = ManagedExpressionsEnum.IS_AFTER
    supported_condition_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.DATE}
    supported_validator_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.DATE}
    managed_expression_form_template: ClassVar[str | None] = (
        "deliver_grant_funding/collections/managed_expressions/is_after.html"
    )

    _key: ManagedExpressionsEnum = name

    earliest_value: datetime.date | None
    earliest_expression: ExpressionReference | None = None
    inclusive: bool = False

    @property
    def description(self) -> str:
        return f"Is {'on or ' if self.inclusive else ''}after"

    @property
    def message(self) -> InterpolationStatement:
        if self.earliest_value:
            formatted_earliest_value = (
                format_date_short(self.earliest_value)
                if not self.subject_reference.presentation_options.approximate_date
                else format_date_approximate(self.earliest_value)
            )
        return InterpolationStatement(
            f"The answer must be {'on or ' if self.inclusive else ''}after "
            + (self.earliest_expression.wrapped if self.earliest_expression else formatted_earliest_value)
        )

    @property
    def statement(self) -> EvaluationStatement:
        date_expression = (
            self.earliest_expression.unwrapped
            if self.earliest_expression
            else f"date({self.earliest_value.year}, {self.earliest_value.month}, {self.earliest_value.day})"  # ty: ignore[unresolved-attribute]
        )
        return EvaluationStatement(
            f"{self.subject_reference.unwrapped} >{'=' if self.inclusive else ''} {date_expression}"
        )

    @property
    def expression_referenced_question_ids(self) -> list[UUID]:
        if self.earliest_expression and (question_id := self.earliest_expression.question_id):
            return [question_id]
        return []

    @property
    def reference_aware_fields(self) -> set[str]:
        return {"earliest_expression"}

    @staticmethod
    def get_form_fields(
        subject_reference: ExpressionReference, expression: TOptional[Expression] = None
    ) -> dict[str, Field]:
        return {
            "earliest_value": DateField(
                "Earliest date",
                default=datetime.datetime.strptime(cast(str, earliest_value), "%Y-%m-%d").date()
                if expression and (earliest_value := expression.context.get("earliest_value"))
                else None,
                widget=GovDateInput()
                if not subject_reference.presentation_options.approximate_date
                else MHCLGApproximateDateInput(),
                validators=[Optional()],
                format=["%d %m %Y", "%d %b %Y", "%d %B %Y"]
                if not subject_reference.presentation_options.approximate_date
                else ["%m %Y", "%b %Y", "%B %Y"],  # multiple formats to help user input
            ),
            "earliest_expression": StringField(
                "Earliest date",
                default=ExpressionReference(str(earliest_expression)).wrapped
                if expression and (earliest_expression := expression.context.get("earliest_expression"))
                else "",
                widget=GovTextArea(),
            ),
            "earliest_inclusive": BooleanField(
                "An answer of exactly the earliest date is allowed",
                default=cast(bool, expression.context["inclusive"]) if expression else None,
                widget=GovCheckboxInput(),
            ),
            "add_context": StringField(
                "Reference data",
                widget=GovSubmitInput(),
            ),
            "remove_context": StringField(
                "Remove data",
                widget=GovSubmitInput(),
            ),
        }

    @staticmethod
    def update_validators(form: _ManagedExpressionForm) -> None:
        form.earliest_value.validators = (  # ty: ignore[unresolved-attribute]
            [DataRequired("Enter the date which this answer must come before")]
            if not form.earliest_expression.data  # ty: ignore[unresolved-attribute]
            else [Optional()]
        )
        form.earliest_expression.validators = [ReadOnly()]  # ty: ignore[unresolved-attribute]

    @staticmethod
    def build_from_form(form: _ManagedExpressionForm, subject_reference: ExpressionReference) -> IsAfter:
        return IsAfter(
            subject_reference=subject_reference,
            earliest_value=form.earliest_value.data if not form.earliest_expression.data else None,  # ty: ignore[unresolved-attribute]
            earliest_expression=form.earliest_expression.data if form.earliest_expression.data else None,  # ty: ignore[unresolved-attribute]
            inclusive=form.earliest_inclusive.data,  # ty: ignore[unresolved-attribute]
        )

    @classmethod
    def prepare_form_data(cls, add_context_data: AddContextToExpressionsModel) -> dict[str, Any]:
        data = super().prepare_form_data(add_context_data)

        if data.get("earliest_value"):
            data["earliest_value"] = datetime.datetime.strptime(data["earliest_value"], "%Y-%m-%d").date()

        return data

    @property
    def required_functions(self) -> dict[str, Callable[[Any], Any] | type[Any]]:
        return {"date": datetime.date}


@register_managed_expression
class BetweenDates(ManagedExpression):
    name: ClassVar[ManagedExpressionsEnum] = ManagedExpressionsEnum.BETWEEN_DATES
    supported_condition_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.DATE}
    supported_validator_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.DATE}
    managed_expression_form_template: ClassVar[str | None] = (
        "deliver_grant_funding/collections/managed_expressions/between_dates.html"
    )

    _key: ManagedExpressionsEnum = name

    earliest_value: datetime.date | None
    earliest_expression: ExpressionReference | None = None
    earliest_inclusive: bool = False
    latest_value: datetime.date | None
    latest_expression: ExpressionReference | None = None
    latest_inclusive: bool = False

    @property
    def description(self) -> str:
        return "Is between"

    @property
    def message(self) -> InterpolationStatement:
        if self.earliest_value:
            formatted_earliest_value = (
                format_date_short(self.earliest_value)
                if not self.subject_reference.presentation_options.approximate_date
                else format_date_approximate(self.earliest_value)
            )
        if self.latest_value:
            formatted_latest_value = (
                format_date_short(self.latest_value)
                if not self.subject_reference.presentation_options.approximate_date
                else format_date_approximate(self.latest_value)
            )
        return InterpolationStatement(
            "The answer must be between "
            + (self.earliest_expression.wrapped if self.earliest_expression else formatted_earliest_value)
            + f"{' (inclusive)' if self.earliest_inclusive else ' (exclusive)'} and "
            + (self.latest_expression.wrapped if self.latest_expression else formatted_latest_value)
            + f"{' (inclusive)' if self.latest_inclusive else ' (exclusive)'}"
        )

    @property
    def statement(self) -> EvaluationStatement:
        earliest_date_expression = (
            self.earliest_expression.unwrapped
            if self.earliest_expression
            else f"date({self.earliest_value.year}, {self.earliest_value.month}, {self.earliest_value.day})"  # ty: ignore[unresolved-attribute]
        )
        latest_date_expression = (
            self.latest_expression.unwrapped
            if self.latest_expression
            else f"date({self.latest_value.year}, {self.latest_value.month}, {self.latest_value.day})"  # ty: ignore[unresolved-attribute]
        )
        return EvaluationStatement(
            f"{earliest_date_expression} "
            + f"<{'=' if self.earliest_inclusive else ''} "
            + f"{self.subject_reference.unwrapped} "
            + f"<{'=' if self.latest_inclusive else ''} "
            + latest_date_expression
        )

    @property
    def expression_referenced_question_ids(self) -> list[UUID]:
        referenced_ids = []

        if self.earliest_expression and (question_id := self.earliest_expression.question_id):
            referenced_ids.append(question_id)

        if self.latest_expression and (question_id := self.latest_expression.question_id):
            referenced_ids.append(question_id)

        return referenced_ids

    @property
    def reference_aware_fields(self) -> set[str]:
        return {"earliest_expression", "latest_expression"}

    @staticmethod
    def get_form_fields(
        subject_reference: ExpressionReference, expression: TOptional[Expression] = None
    ) -> dict[str, Field]:
        return {
            "between_bottom_of_range": DateField(
                "Earliest date",
                default=datetime.datetime.strptime(cast(str, earliest_value), "%Y-%m-%d").date()
                if expression and (earliest_value := expression.context.get("earliest_value"))
                else None,
                widget=GovDateInput()
                if not subject_reference.presentation_options.approximate_date
                else MHCLGApproximateDateInput(),
                validators=[Optional()],
                format=["%d %m %Y", "%d %b %Y", "%d %B %Y"]
                if not subject_reference.presentation_options.approximate_date
                else ["%m %Y", "%b %Y", "%B %Y"],  # multiple formats to help user input
            ),
            "between_bottom_of_range_expression": StringField(
                "Earliest date",
                default=ExpressionReference(str(earliest_expression)).wrapped
                if expression and (earliest_expression := expression.context.get("earliest_expression"))
                else "",
                widget=GovTextArea(),
            ),
            "between_bottom_inclusive": BooleanField(
                "An answer of exactly the earliest date is allowed",
                default=cast(bool, expression.context["earliest_inclusive"]) if expression else None,
                widget=GovCheckboxInput(),
            ),
            "between_top_of_range": DateField(
                "Latest date",
                default=datetime.datetime.strptime(cast(str, latest_value), "%Y-%m-%d").date()
                if expression and (latest_value := expression.context.get("latest_value"))
                else None,
                widget=GovDateInput()
                if not subject_reference.presentation_options.approximate_date
                else MHCLGApproximateDateInput(),
                validators=[Optional()],
                format=["%d %m %Y", "%d %b %Y", "%d %B %Y"]
                if not subject_reference.presentation_options.approximate_date
                else ["%m %Y", "%b %Y", "%B %Y"],  # multiple formats to help user input
            ),
            "between_top_of_range_expression": StringField(
                "Latest date",
                default=ExpressionReference(str(latest_expression)).wrapped
                if expression and (latest_expression := expression.context.get("latest_expression"))
                else "",
                widget=GovTextArea(),
            ),
            "between_top_inclusive": BooleanField(
                "An answer of exactly the latest date is allowed",
                default=cast(bool, expression.context["latest_inclusive"]) if expression else None,
                widget=GovCheckboxInput(),
            ),
            "add_context": StringField(
                "Reference data",
                widget=GovSubmitInput(),
            ),
            "remove_context": StringField(
                "Remove data",
                widget=GovSubmitInput(),
            ),
        }

    @staticmethod
    def update_validators(form: _ManagedExpressionForm) -> None:
        form.between_bottom_of_range.validators = (  # ty: ignore[unresolved-attribute]
            [
                InputRequired("Enter the earliest date allowed for this question"),
                BottomOfRangeIsLower("The earliest date must be before the latest date"),
            ]
            if not form.between_bottom_of_range_expression.data and not form.between_top_of_range_expression.data  # ty: ignore[unresolved-attribute]
            else [InputRequired("Enter the earliest date allowed for this question")]
            if not form.between_bottom_of_range_expression.data  # ty: ignore[unresolved-attribute]
            else [Optional()]
        )
        form.between_top_of_range.validators = (  # ty: ignore[unresolved-attribute]
            [
                InputRequired("Enter the latest date allowed for this question"),
                BottomOfRangeIsLower("The latest date must be after the earliest date"),
            ]
            if not form.between_bottom_of_range_expression.data and not form.between_top_of_range_expression.data  # ty: ignore[unresolved-attribute]
            else [InputRequired("Enter the latest date allowed for this question")]
            if not form.between_top_of_range_expression.data  # ty: ignore[unresolved-attribute]
            else [Optional()]
        )

    @staticmethod
    def build_from_form(form: _ManagedExpressionForm, subject_reference: ExpressionReference) -> BetweenDates:
        return BetweenDates(
            subject_reference=subject_reference,
            earliest_value=form.between_bottom_of_range.data  # ty: ignore[unresolved-attribute]
            if not form.between_bottom_of_range_expression.data  # ty: ignore[unresolved-attribute]
            else None,
            earliest_expression=form.between_bottom_of_range_expression.data  # ty: ignore[unresolved-attribute]
            if form.between_bottom_of_range_expression.data  # ty: ignore[unresolved-attribute]
            else None,
            earliest_inclusive=form.between_bottom_inclusive.data,  # ty: ignore[unresolved-attribute]
            latest_value=form.between_top_of_range.data,  # ty: ignore[unresolved-attribute]
            latest_expression=form.between_top_of_range_expression.data  # ty: ignore[unresolved-attribute]
            if form.between_top_of_range_expression.data  # ty: ignore[unresolved-attribute]
            else None,
            latest_inclusive=form.between_top_inclusive.data,  # ty: ignore[unresolved-attribute]
        )

    @classmethod
    def prepare_form_data(cls, add_context_data: AddContextToExpressionsModel) -> dict[str, Any]:
        data = super().prepare_form_data(add_context_data)

        if data.get("between_bottom_of_range"):
            data["between_bottom_of_range"] = datetime.datetime.strptime(
                data["between_bottom_of_range"], "%Y-%m-%d"
            ).date()

        if data.get("between_top_of_range"):
            data["between_top_of_range"] = datetime.datetime.strptime(data["between_top_of_range"], "%Y-%m-%d").date()

        return data

    @property
    def required_functions(self) -> dict[str, Callable[[Any], Any] | type[Any]]:
        return {"date": datetime.date}


@register_managed_expression
class UKPostcode(ManagedExpression):
    name: ClassVar[ManagedExpressionsEnum] = ManagedExpressionsEnum.UK_POSTCODE
    supported_condition_data_types: ClassVar[set[QuestionDataType]] = {}  # ty: ignore[invalid-assignment]
    supported_validator_data_types: ClassVar[set[QuestionDataType]] = {QuestionDataType.TEXT_SINGLE_LINE}
    managed_expression_form_template: ClassVar[str | None] = None

    _key: ManagedExpressionsEnum = name

    @property
    def description(self) -> str:
        return "Must be a UK postcode"

    @property
    def message(self) -> InterpolationStatement:
        return InterpolationStatement("The answer must be a UK postcode")

    @property
    def statement(self) -> EvaluationStatement:
        return EvaluationStatement(f"uk_postcode_match({self.subject_reference.unwrapped})")

    @property
    def required_functions(self) -> dict[str, Callable[[Any], Any] | type[Any]]:
        def uk_postcode_match(value: str) -> bool:
            if not value:
                return False
            value = value.strip()
            pattern = r"^([A-Z]{1,2}(?:\d[A-Z]|\d{1,2}))\s*(\d[A-Z]{2})$"
            return bool(re.match(pattern, value, re.IGNORECASE))

        return {"uk_postcode_match": uk_postcode_match}

    @staticmethod
    def get_form_fields(
        subject_reference: ExpressionReference, expression: TOptional[Expression] = None
    ) -> dict[str, Field]:
        return {}

    @staticmethod
    def update_validators(form: _ManagedExpressionForm) -> None:
        pass

    @staticmethod
    def build_from_form(form: _ManagedExpressionForm, subject_reference: ExpressionReference) -> UKPostcode:
        return UKPostcode(subject_reference=subject_reference)


def get_managed_expression(expression: Expression) -> ManagedExpression:
    if not expression.managed_name:
        raise ValueError(f"Expression {expression.id} is not a managed expression.")

    ExpressionType = TypeAdapter(lookup_managed_expression(expression.managed_name))

    # TODO: for AnyOf, do we want to pull the list of items from the DB rather than denormalising into the `context`
    #       blob? We need to have hardlink references between expressions and the radio items they rely on first (this
    #       would be done in FSPT-673).
    return ExpressionType.validate_python(expression.context)
