import datetime
from abc import abstractmethod
from typing import Any, Literal, cast

import msgspec
from sqlalchemy import Column, Enum, ForeignKey, Select, Table, select
from sqlalchemy.orm import Mapped, mapped_column, relationship, selectinload

from csirtmu.core.sqlalchemy.types import StringArray
from csirtmu.self_service_backend import dto_structs
from csirtmu.self_service_backend.enums import Language, NetboxQueryType
from csirtmu.strawberry_dmt.sqla_base import AutoBase, AutoPolymorphicBase

LanguageSqlEnum = Enum(Language)


class Text(AutoBase):
    translations: Mapped[list["Translation"]] = relationship(
        "Translation",
        foreign_keys="Translation.text_id",
        uselist=True,
        back_populates="text",
    )

    def to_str(self, offset: int = 2) -> str:
        indent = offset * " "
        return f"{indent}translations:" + (
            ("\n" + "\n".join([f"{indent}  - {t.to_str(offset + 4)}" for t in self.translations]))
            if self.translations
            else " []"
        )


class Translation(AutoBase):
    text_id: Mapped[int] = mapped_column(ForeignKey("text.id"), nullable=False)
    text: Mapped[Text] = relationship("Text", foreign_keys=[text_id])
    contents: Mapped[str] = mapped_column(nullable=False)
    language = mapped_column(LanguageSqlEnum, nullable=False, default=Language.CZ)

    def to_str(self, offset: int = 6) -> str:
        return f'text: "{self.contents.replace("\n", "\\n")}"\n{offset * " "}language: {self.language.value}'


class Step(AutoPolymorphicBase("type")):
    type: Mapped[str] = mapped_column(nullable=False)

    prev_id: Mapped[int | None] = mapped_column(ForeignKey("step.id"), nullable=True)
    prev_step: Mapped["Step | None"] = relationship("Step", foreign_keys=[prev_id])

    title_id: Mapped[int] = mapped_column(ForeignKey("text.id"), nullable=False)
    title: Mapped["Text"] = relationship("Text", foreign_keys=[title_id])

    description_id: Mapped[int | None] = mapped_column(ForeignKey("text.id"), nullable=True)
    description: Mapped["Text | None"] = relationship("Text", foreign_keys=[description_id])

    @classmethod
    @abstractmethod
    def build_query(cls) -> Select[Any]: ...

    @abstractmethod
    def to_struct(self) -> dto_structs.Step: ...

    def to_dict(self) -> dict[str, Any]:
        return msgspec.to_builtins(self.to_struct())

    def __str__(self) -> str:
        result = f"id: {self.id}\ntitle:\n{self.title.to_str()}"
        result += f"\nprev_id: {self.prev_id or 'null'}"
        if self.description_id is not None and self.description is not None:
            result += f"\ndescription:\n{self.description.to_str()}"
        return result


class TextStep(Step):
    text_id: Mapped[int] = mapped_column(ForeignKey("text.id"), nullable=False)
    text: Mapped["Text"] = relationship("Text", foreign_keys=[text_id])

    @classmethod
    def build_query(cls) -> Select[tuple["TextStep"]]:
        return select(TextStep).options(
            selectinload(TextStep.title).selectinload(Text.translations),
            selectinload(TextStep.text).selectinload(Text.translations),
            selectinload(TextStep.description).selectinload(Text.translations),
        )

    def to_struct(self) -> dto_structs.Step:
        return dto_structs.TextStep(
            id=self.id,
            title=self.title.translations[0].contents,
            description=self.description.translations[0].contents
            if (self.description_id is not None and self.description is not None)
            else None,
            text=self.text.translations[0].contents,
        )

    def __str__(self) -> str:
        return super().__str__() + f'\nkind: "TextStep"\ntext:\n{self.text.to_str()}'


class LinkStep(Step):
    link: Mapped[str] = mapped_column(nullable=False)

    @classmethod
    def build_query(cls) -> Select[tuple["LinkStep"]]:
        return select(LinkStep).options(
            selectinload(LinkStep.title).selectinload(Text.translations),
            selectinload(LinkStep.description).selectinload(Text.translations),
        )

    def __str__(self) -> str:
        return super().__str__() + f'\nkind: "LinkStep"\nlink: {self.link}'

    def to_struct(self) -> dto_structs.Step:
        return dto_structs.LinkStep(
            id=self.id,
            title=self.title.translations[0].contents,
            description=self.description.translations[0].contents
            if (self.description_id is not None and self.description is not None)
            else None,
            link=self.link,
        )


association_table_one_required = Table(
    "association_table",
    AutoBase.metadata,
    Column["OneRequired"]("parent_id", ForeignKey("one_required.id"), nullable=False, primary_key=True),
    Column["FormField"]("child_id", ForeignKey("form_field.id"), nullable=False, primary_key=True),
)

association_table_dynamic_section = Table(
    "association_table_dynamic_section",
    AutoBase.metadata,
    Column["DynamicSection"]("parent_id", ForeignKey("dynamic_section.id"), nullable=False, primary_key=True),
    Column["FormField"]("child_id", ForeignKey("form_field.id"), nullable=False, primary_key=True),
)


class FormField(AutoPolymorphicBase("field_type")):
    field_type: Mapped[str] = mapped_column(nullable=False)
    form_id: Mapped[int | None] = mapped_column(ForeignKey("form_step.id"), nullable=True)
    form: Mapped["FormStep | None"] = relationship("FormStep", foreign_keys=[form_id], back_populates="fields")

    label_id: Mapped[int] = mapped_column(ForeignKey("text.id"), nullable=False)
    label: Mapped[Text] = relationship("Text", foreign_keys=[label_id])
    position: Mapped[int] = mapped_column(nullable=False)

    @abstractmethod
    def to_struct(self) -> dto_structs.FormField: ...

    def to_dict(self) -> dict[str, Any]:
        return msgspec.to_builtins(self.to_struct())

    def build_str_from_attr_names(self, attr_names: list[str], offset: int) -> str:
        data = ""
        for kwarg in attr_names:
            if (value := getattr(self, kwarg)) is not None:
                data += f"\n{offset * ' '}{kwarg}: {value}"
        return data

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        if use_dash:
            return f"\n{indent}- position: {self.position}\n{indent}  label:\n{self.label.to_str(offset + 4)}"
        return f"\n{indent}  position: {self.position}\n{indent}  label:\n{self.label.to_str(offset + 4)}"


class TextInput(FormField):
    value: Mapped[str | None] = mapped_column(nullable=True, default=None)
    max_chars: Mapped[int | None] = mapped_column(nullable=True, default=None)
    type: Mapped[Literal["default", "password"]] = mapped_column(nullable=False, default="default")
    help: Mapped[str | None] = mapped_column(nullable=True, default=None)
    autocomplete: Mapped[str | None] = mapped_column(nullable=True, default=None)
    placeholder_id: Mapped[int | None] = mapped_column(ForeignKey("text.id"), nullable=True)
    placeholder: Mapped[Text | None] = relationship("Text", foreign_keys=[placeholder_id])
    label_visibility: Mapped[Literal["visible", "hidden", "collapsed"]] = mapped_column(
        nullable=False, default="visible"
    )
    icon: Mapped[str | None] = mapped_column(nullable=True, default=None)

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.TextInput(
            position=self.position,
            label=self.label.translations[0].contents,
            value=self.value,
            max_chars=self.max_chars,
            type=self.type,
            help=self.help,
            autocomplete=self.autocomplete,
            placeholder=self.placeholder.translations[0].contents
            if (self.placeholder_id is not None and self.placeholder is not None)
            else None,
            label_visibility=self.label_visibility,
            icon=self.icon,
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset=offset, use_dash=use_dash) + f'\n{indent}  kind: "TextInput"'
        data += self.build_str_from_attr_names(["value", "max_chars", "help", "autocomplete", "icon"], offset + 2)
        if self.type != "default":
            data += f"\n{indent}  type: {self.type}"
        if self.placeholder_id is not None and self.placeholder is not None:
            data += f"\n{indent}  placeholder:\n{self.placeholder.to_str(offset + 4)}"
        if self.label_visibility != "visible":
            data += f"\n{indent}  label_visibility: {self.label_visibility}"
        return data


class Selectbox(FormField):
    options: Mapped[list[str]] = mapped_column(StringArray, nullable=False)
    index: Mapped[int | None] = mapped_column(nullable=True, default=None)
    help: Mapped[str | None] = mapped_column(nullable=True, default=None)
    placeholder_id: Mapped[int | None] = mapped_column(ForeignKey("text.id"), nullable=True)
    placeholder: Mapped[Text | None] = relationship("Text", foreign_keys=[placeholder_id])
    label_visibility: Mapped[Literal["visible", "hidden", "collapsed"]] = mapped_column(
        nullable=False, default="visible"
    )
    accept_new_options: Mapped[bool] = mapped_column(nullable=False, default=False)

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.Selectbox(
            position=self.position,
            label=self.label.translations[0].contents,
            options=self.options,
            index=self.index,
            help=self.help,
            placeholder=self.placeholder.translations[0].contents
            if (self.placeholder_id is not None and self.placeholder is not None)
            else None,
            label_visibility=self.label_visibility,
            accept_new_options=self.accept_new_options,
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset, use_dash) + f'\n{indent}  kind: "Selectbox"'
        data += f"\n{indent}  options:\n{indent}    - " + f"\n{indent}    - ".join([f'"{opt}"' for opt in self.options])
        data += self.build_str_from_attr_names(["index", "help"], offset + 2)
        if self.placeholder_id is not None and self.placeholder is not None:
            data += f"\n{indent}  placeholder:\n{self.placeholder.to_str(offset + 4)}"
        if self.label_visibility != "visible":
            data += f"\n{indent}  label_visibility: {self.label_visibility}"
        if self.accept_new_options:
            data += f"\n{indent}  accept_new_options: {self.accept_new_options}"
        return data


class Multiselect(FormField):
    options: Mapped[list[str]] = mapped_column(StringArray, nullable=False)
    default: Mapped[list[str] | None] = mapped_column(StringArray, nullable=True, default=None)
    help: Mapped[str | None] = mapped_column(nullable=True, default=None)
    max_selections: Mapped[int | None] = mapped_column(nullable=True, default=None)
    placeholder_id: Mapped[int | None] = mapped_column(ForeignKey("text.id"), nullable=True, default=None)
    placeholder: Mapped[Text | None] = relationship(
        "Text",
        foreign_keys=[placeholder_id],
    )
    label_visibility: Mapped[Literal["visible", "hidden", "collapsed"]] = mapped_column(
        nullable=False, default="visible"
    )
    accept_new_options: Mapped[bool] = mapped_column(nullable=False, default=False)

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.Multiselect(
            position=self.position,
            label=self.label.translations[0].contents,
            options=self.options,
            default=self.default,
            max_selections=self.max_selections,
            placeholder=self.placeholder.translations[0].contents
            if (self.placeholder_id is not None and self.placeholder is not None)
            else None,
            label_visibility=self.label_visibility,
            accept_new_options=self.accept_new_options,
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset, use_dash) + f'\n{indent}  kind: "Multiselect"'
        data += f"\n{indent}  options:\n{indent}    - " + f"\n{indent}    - ".join([f'"{opt}"' for opt in self.options])
        data += self.build_str_from_attr_names(["max_selections", "help"], offset + 2)
        if self.placeholder is not None:
            data += f"\n{indent}  placeholder:\n{self.placeholder.to_str(offset + 4)}"
        if self.default:
            data += f"\n{indent}  default:\n{indent}    - " + f"\n{indent}    - ".join(
                [f'"{opt}"' for opt in self.default]
            )
        if self.label_visibility != "visible":
            data += f"\n{indent}  label_visibility: {self.label_visibility}"
        if self.accept_new_options:
            data += f"\n{indent}  accept_new_options: {self.accept_new_options}"
        return data


class TextArea(FormField):
    value: Mapped[str | None] = mapped_column(nullable=True, default=None)
    height: Mapped[int | None] = mapped_column(nullable=True, default=None)
    max_chars: Mapped[int | None] = mapped_column(nullable=True, default=None)
    help: Mapped[str | None] = mapped_column(nullable=True, default=None)
    placeholder_id: Mapped[int | None] = mapped_column(ForeignKey("text.id"), nullable=True)
    placeholder: Mapped[Text | None] = relationship("Text", foreign_keys=[placeholder_id])
    label_visibility: Mapped[Literal["visible", "hidden", "collapsed"]] = mapped_column(
        nullable=False, default="visible"
    )

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.TextArea(
            position=self.position,
            label=self.label.translations[0].contents,
            value=self.value,
            height=self.height,
            max_chars=self.max_chars,
            help=self.help,
            placeholder=self.placeholder.translations[0].contents
            if (self.placeholder_id is not None and self.placeholder is not None)
            else None,
            label_visibility=self.label_visibility,
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset, use_dash) + f'\n{indent}  kind: "TextArea"'
        data += self.build_str_from_attr_names(["value", "height", "max_chars", "help"], offset + 2)
        if self.placeholder is not None:
            data += f"\n{indent}  placeholder:\n{self.placeholder.to_str(offset + 4)}"
        if self.label_visibility != "visible":
            data += f"\n{indent}  label_visibility: {self.label_visibility}"
        return data


class NumberInput(FormField):
    format: Mapped[str | None] = mapped_column(nullable=True, default=None)
    help: Mapped[str | None] = mapped_column(nullable=True, default=None)
    label_visibility: Mapped[Literal["visible", "hidden", "collapsed"]] = mapped_column(
        nullable=False, default="visible"
    )
    icon: Mapped[str | None] = mapped_column(nullable=True, default=None)

    @abstractmethod
    def to_struct(self) -> dto_structs.FormField: ...


class NumberInputInt(NumberInput):
    value: Mapped[int | None] = mapped_column(nullable=True, default=None)
    min_value: Mapped[int | None] = mapped_column(nullable=True, default=None)
    max_value: Mapped[int | None] = mapped_column(nullable=True, default=None)
    step: Mapped[int | None] = mapped_column(nullable=True, default=None)
    placeholder: Mapped[int | None] = mapped_column(nullable=True, default=None)

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.NumberInputInt(
            position=self.position,
            label=self.label.translations[0].contents,
            value=self.value,
            min_value=self.min_value,
            max_value=self.max_value,
            step=self.step,
            format=self.format,
            help=self.help,
            placeholder=self.placeholder,
            label_visibility=self.label_visibility,
            icon=self.icon,
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset, use_dash) + f'\n{indent}  kind: "NumberInputInt"'
        data += self.build_str_from_attr_names(
            [
                "value",
                "min_value",
                "max_value",
                "step",
                "format",
                "help",
                "icon",
                "placeholder",
            ],
            offset + 2,
        )
        if self.label_visibility != "visible":
            data += f"\n{indent}  label_visibility: {self.label_visibility}"
        return data


class NumberInputFloat(NumberInput):
    value: Mapped[float | None] = mapped_column(nullable=True, default=None)
    min_value: Mapped[float | None] = mapped_column(nullable=True, default=None)
    max_value: Mapped[float | None] = mapped_column(nullable=True, default=None)
    step: Mapped[float | None] = mapped_column(nullable=True, default=None)
    placeholder: Mapped[float | None] = mapped_column(nullable=True, default=None)

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.NumberInputFloat(
            position=self.position,
            label=self.label.translations[0].contents,
            value=self.value,
            min_value=self.min_value,
            max_value=self.max_value,
            step=self.step,
            format=self.format,
            help=self.help,
            placeholder=self.placeholder,
            label_visibility=self.label_visibility,
            icon=self.icon,
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset, use_dash) + f'\n{indent}  kind: "NumberInputFloat"'
        data += self.build_str_from_attr_names(
            [
                "value",
                "min_value",
                "max_value",
                "step",
                "format",
                "help",
                "icon",
                "placeholder",
            ],
            offset + 2,
        )
        if self.label_visibility != "visible":
            data += f"\n{indent}  label_visibility: {self.label_visibility}"
        return data


class Checkbox(FormField):
    value: Mapped[bool] = mapped_column(nullable=False, default=False)
    help: Mapped[str | None] = mapped_column(nullable=True, default=None)
    label_visibility: Mapped[Literal["visible", "hidden", "collapsed"]] = mapped_column(
        nullable=False, default="visible"
    )

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.Checkbox(
            position=self.position,
            label=self.label.translations[0].contents,
            value=self.value,
            help=self.help,
            label_visibility=self.label_visibility,
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset, use_dash) + f'\n{indent}  kind: "Checkbox"'
        if self.value:
            data += f"\n{indent}  value: true"
        data += self.build_str_from_attr_names(["help"], offset + 2)
        if self.label_visibility != "visible":
            data += f"\n{indent}  label_visibility: {self.label_visibility}"
        return data


class Radio(FormField):
    options: Mapped[list[str]] = mapped_column(StringArray, nullable=False)
    index: Mapped[int | None] = mapped_column(nullable=True, default=0)
    help: Mapped[str | None] = mapped_column(nullable=True, default=None)
    horizontal: Mapped[bool] = mapped_column(nullable=False, default=False)
    captions: Mapped[list[str] | None] = mapped_column(StringArray, nullable=True, default=None)
    label_visibility: Mapped[Literal["visible", "hidden", "collapsed"]] = mapped_column(
        nullable=False, default="visible"
    )

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.Radio(
            position=self.position,
            label=self.label.translations[0].contents,
            options=self.options,
            index=self.index,
            help=self.help,
            horizontal=self.horizontal,
            captions=self.captions,
            label_visibility=self.label_visibility,
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset, use_dash) + f'\n{indent}  kind: "Radio"'
        data += self.build_str_from_attr_names(["help", "index"], offset + 2)
        data += f"\n{indent}  options:\n{indent}    - " + f"\n{indent}    - ".join([f'"{opt}"' for opt in self.options])
        if self.horizontal:
            data += f"\n{indent}  horizontal: true"
        if self.captions is not None:
            data += f"\n{indent}  captions:\n{indent}    - " + f"\n{indent}    - ".join(
                [f'"{opt}"' for opt in self.captions]
            )
        if self.label_visibility != "visible":
            data += f"\n{indent}  label_visibility: {self.label_visibility}"
        return data


class DateInput(FormField):
    value: Mapped[datetime.date | None] = mapped_column(nullable=True, default=None)
    min_value: Mapped[datetime.date | None] = mapped_column(nullable=True, default=None)
    max_value: Mapped[datetime.date | None] = mapped_column(nullable=True, default=None)
    format: Mapped[str | None] = mapped_column(nullable=True, default=None)
    help: Mapped[str | None] = mapped_column(nullable=True, default=None)
    label_visibility: Mapped[Literal["visible", "hidden", "collapsed"]] = mapped_column(
        nullable=False, default="visible"
    )

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.DateInput(
            position=self.position,
            label=self.label.translations[0].contents,
            value=self.value,
            min_value=self.min_value,
            max_value=self.max_value,
            format=self.format,
            help=self.help,
            label_visibility=self.label_visibility,
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset, use_dash) + f'\n{indent}  kind: "DateInput"'
        data += self.build_str_from_attr_names(["value", "min_value", "max_value", "format", "help"], offset + 2)
        if self.label_visibility != "visible":
            data += f"\n{indent}  label_visibility: {self.label_visibility}"
        return data


class TimeInput(FormField):
    value: Mapped[datetime.time | None] = mapped_column(nullable=True, default=None)
    help: Mapped[str | None] = mapped_column(nullable=True, default=None)
    step: Mapped[int | None] = mapped_column(nullable=True, default=None)
    label_visibility: Mapped[Literal["visible", "hidden", "collapsed"]] = mapped_column(
        nullable=False, default="visible"
    )

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.TimeInput(
            position=self.position,
            label=self.label.translations[0].contents,
            value=self.value,
            step=self.step,
            help=self.help,
            label_visibility=self.label_visibility,
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset, use_dash) + f'\n{indent}  kind: "TimeInput"'
        data += self.build_str_from_attr_names(["value", "step", "help"], offset + 2)
        if self.label_visibility != "visible":
            data += f"\n{indent}  label_visibility: {self.label_visibility}"
        return data


class FileUploader(FormField):
    type: Mapped[list[str] | None] = mapped_column(StringArray, nullable=True, default=None)
    accept_multiple_files: Mapped[bool] = mapped_column(nullable=False, default=False)
    help: Mapped[str | None] = mapped_column(nullable=True, default=None)
    label_visibility: Mapped[Literal["visible", "hidden", "collapsed"]] = mapped_column(
        nullable=False, default="visible"
    )

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.FileUpload(
            type=self.type,
            position=self.position,
            label=self.label.translations[0].contents,
            accept_multiple_files=self.accept_multiple_files,
            help=self.help,
            label_visibility=self.label_visibility,
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset, use_dash) + f'\n{indent}  kind: "FileUpload"'
        if self.type is not None:
            data += f"\n{indent}  type:\n{indent}    - " + f"\n{indent}    - ".join(self.type)
        if self.accept_multiple_files:
            data += f"\n{indent}  accept_multiple_files: true"
        if self.label_visibility != "visible":
            data += f"\n{indent}  label_visibility: {self.label_visibility}"
        return data


class TextField(FormField):
    text_id: Mapped[int] = mapped_column(ForeignKey("text.id"))
    text: Mapped[Text] = relationship("Text", foreign_keys=[text_id])

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.TextField(
            position=self.position,
            label="",
            text=self.text.translations[0].contents,
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset, use_dash) + f'\n{indent}  kind: "TextField"'
        data += f"\n{indent}  text:\n{self.text.to_str(offset + 4)}"
        return data


class CompositeField(FormField):
    @abstractmethod
    def to_struct(self) -> dto_structs.FormField: ...

    def cast_to_all_simple_fields(
        self, fields: list[dto_structs.FormField]
    ) -> list[
        dto_structs.TextInput
        | dto_structs.Selectbox
        | dto_structs.Multiselect
        | dto_structs.TextArea
        | dto_structs.NumberInputInt
        | dto_structs.NumberInputFloat
        | dto_structs.Checkbox
        | dto_structs.Radio
        | dto_structs.DateInput
        | dto_structs.TimeInput
        | dto_structs.FileUpload
        | dto_structs.TextField
    ]:
        return cast(
            "list[dto_structs.TextInput | dto_structs.Selectbox | dto_structs.Multiselect | dto_structs.TextArea | dto_structs.NumberInputInt | dto_structs.NumberInputFloat | dto_structs.Checkbox | dto_structs.Radio | dto_structs.DateInput | dto_structs.TimeInput | dto_structs.FileUpload | dto_structs.TextField]",
            fields,
        )


class OneRequired(CompositeField):
    fields: Mapped[list[FormField]] = relationship(
        "FormField",
        uselist=True,
        secondary=association_table_one_required,
        primaryjoin=lambda: OneRequired.id == association_table_one_required.c.parent_id,
        secondaryjoin=lambda: FormField.id == association_table_one_required.c.child_id,
    )

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.OneRequired(
            position=self.position,
            label=self.label.translations[0].contents,
            fields=[cast("dto_structs.Checkbox", field.to_struct()) for field in self.fields],
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset, use_dash) + f'\n{indent}  kind: "OneRequired"'
        if self.fields:
            data += f"\n{indent}  fields:\n{indent}    " + "".join(
                field.to_str(offset + 4) for field in sorted(self.fields, key=lambda x: x.position)
            )
        return data


selection_association_table = Table(
    "selection_association_table",
    AutoBase.metadata,
    Column[int]("selection_mapping_id", ForeignKey("selection_mapping.id")),
    Column["FormField"]("option_id", ForeignKey("form_field.id")),
)


class SelectionMapping(AutoBase):
    selection_id: Mapped[int] = mapped_column(ForeignKey("on_selection.id"))
    selection: Mapped["OnSelection"] = relationship("OnSelection", foreign_keys=[selection_id])

    option: Mapped[str] = mapped_column(nullable=False)
    fields: Mapped[list[FormField]] = relationship(uselist=True, secondary=selection_association_table)


class OnSelection(CompositeField):
    selection_field_id: Mapped[int] = mapped_column(ForeignKey("form_field.id"), nullable=False)
    selection_field: Mapped[FormField] = relationship(FormField, foreign_keys=[selection_field_id])
    mapping: Mapped[list[SelectionMapping]] = relationship(SelectionMapping, uselist=True, back_populates="selection")

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.OnSelection(
            position=self.position,
            label=self.label.translations[0].contents,
            selection_field=cast(
                "dto_structs.Radio | dto_structs.Selectbox",
                self.selection_field.to_struct(),
            ),
            mapping={
                item.option: self.cast_to_all_simple_fields([field.to_struct() for field in item.fields])
                for item in self.mapping
            },
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset, use_dash) + f'\n{indent}  kind: "OnSelection"'
        data += f"\n{indent}  selection_field:" + self.selection_field.to_str(offset + 2, False)
        data += f"\n{indent}  mapping:\n{indent}    " + f"\n{indent}    ".join(
            [
                f'"{item.option}":'
                + "".join(field.to_str(offset + 6) for field in sorted(item.fields, key=lambda i: i.position))
                for item in self.mapping
            ]
        )
        return data


class DynamicSection(CompositeField):
    fields: Mapped[list[FormField]] = relationship(
        "FormField",
        uselist=True,
        secondary=association_table_dynamic_section,
        primaryjoin=lambda: DynamicSection.id == association_table_dynamic_section.c.parent_id,
        secondaryjoin=lambda: FormField.id == association_table_dynamic_section.c.child_id,
    )
    default_amount: Mapped[int] = mapped_column(nullable=False, default=0)

    def to_struct(self) -> dto_structs.FormField:
        return dto_structs.DynamicSection(
            position=self.position,
            label=self.label.translations[0].contents,
            fields=self.cast_to_all_simple_fields([field.to_struct() for field in self.fields]),
            default_amount=self.default_amount,
        )

    def to_str(self, offset: int = 2, use_dash: bool = True) -> str:
        indent = offset * " "
        data = super().to_str(offset, use_dash) + f'\n{indent}  kind: "OneRequired"'
        if self.fields:
            data += f"\n{indent}  fields:\n{indent}    " + "\n".join(
                field.to_str(offset + 4) for field in sorted(self.fields, key=lambda x: x.position)
            )
            data += f"\n{indent}  default_amount:{self.default_amount}"
        return data


class FormStep(Step):
    fields: Mapped[list[FormField]] = relationship(
        FormField,
        foreign_keys=[FormField.form_id],
        uselist=True,
        back_populates="form",
    )
    clear_on_submit: Mapped[bool] = mapped_column(nullable=False, default=False)
    border: Mapped[bool] = mapped_column(nullable=False, default=True)

    def cast_to_all_fields(
        self, fields: list[dto_structs.FormField]
    ) -> list[
        dto_structs.TextInput
        | dto_structs.Selectbox
        | dto_structs.Multiselect
        | dto_structs.TextArea
        | dto_structs.NumberInputInt
        | dto_structs.NumberInputFloat
        | dto_structs.Checkbox
        | dto_structs.Radio
        | dto_structs.DateInput
        | dto_structs.TimeInput
        | dto_structs.FileUpload
        | dto_structs.TextField
        | dto_structs.OnSelection
        | dto_structs.OneRequired
        | dto_structs.DynamicSection
    ]:
        return cast(
            "list[dto_structs.TextInput | dto_structs.Selectbox | dto_structs.Multiselect | dto_structs.TextArea | dto_structs.NumberInputInt | dto_structs.NumberInputFloat | dto_structs.Checkbox | dto_structs.Radio | dto_structs.DateInput | dto_structs.TimeInput | dto_structs.FileUpload | dto_structs.TextField | dto_structs.OnSelection | dto_structs.OneRequired | dto_structs.DynamicSection]",
            fields,
        )

    def to_struct(self) -> dto_structs.FormStep:
        return dto_structs.FormStep(
            id=self.id,
            title=self.title.translations[0].contents,
            description=self.description.translations[0].contents if self.description else None,
            fields=self.cast_to_all_fields([field.to_struct() for field in self.fields]),
            clear_on_submit=self.clear_on_submit,
            border=self.border,
        )

    def __str__(self) -> str:
        data = '\nkind: "FormStep"'
        if self.clear_on_submit:
            data += f"\nclear_on_submit: {self.clear_on_submit}"
        if not self.border:
            data += f"\nborder: {self.border}"
        if len(self.fields) > 0:
            data += "\nfields:" + "".join([f.to_str() for f in sorted(self.fields, key=lambda x: x.position)])
        else:
            data += "\nfields: []"
        return super().__str__() + data

    @classmethod
    def build_query(cls) -> Select[tuple["FormStep"]]:
        return select(FormStep).options(
            # Select title
            selectinload(FormStep.title).selectinload(Text.translations),
            # Select description
            selectinload(FormStep.description).selectinload(Text.translations),
            # Select fields
            selectinload(FormStep.fields),
            # Select label on all fields
            selectinload(FormStep.fields.of_type(FormField))
            .selectinload(FormField.label)
            .selectinload(Text.translations),
            # Select placeholder on TextInput
            selectinload(FormStep.fields.of_type(TextInput))
            .selectinload(TextInput.placeholder)
            .selectinload(Text.translations),
            # Select placeholder on Selectbox
            selectinload(FormStep.fields.of_type(Selectbox))
            .selectinload(Selectbox.placeholder)
            .selectinload(Text.translations),
            # Select placeholder on Multiselect
            selectinload(FormStep.fields.of_type(Multiselect))
            .selectinload(Multiselect.placeholder)
            .selectinload(Text.translations),
            # Select placeholder on TextArea
            selectinload(FormStep.fields.of_type(TextArea))
            .selectinload(TextArea.placeholder)
            .selectinload(Text.translations),
            # Select NumberInputInt in FormStep fields
            selectinload(FormStep.fields.of_type(NumberInputInt)),
            # Select NumberInputFloat in FormStep fields
            selectinload(FormStep.fields.of_type(NumberInputFloat)),
            # Select Checkbox in FormStep fields
            selectinload(FormStep.fields.of_type(Checkbox)),
            # Select Radio in FormStep fields
            selectinload(FormStep.fields.of_type(Radio)),
            # Select DateInput in FormStep fields
            selectinload(FormStep.fields.of_type(DateInput)),
            # Select TimeInput in FormStep fields
            selectinload(FormStep.fields.of_type(TimeInput)),
            # Select FileUploader in FormStep fields
            selectinload(FormStep.fields.of_type(FileUploader)),
            # Select text for TextFields
            selectinload(FormStep.fields.of_type(TextField))
            .selectinload(TextField.text)
            .selectinload(Text.translations),
            # Select label for fields on OneRequired
            selectinload(FormStep.fields.of_type(OneRequired))
            .selectinload(OneRequired.fields)
            .selectinload(FormField.label)
            .selectinload(Text.translations),
            # Select Checkbox in fields on OneRequired
            selectinload(FormStep.fields.of_type(OneRequired)).selectinload(OneRequired.fields.of_type(Checkbox)),
            # Select label for selection field on OnSelection
            selectinload(FormStep.fields.of_type(OnSelection))
            .selectinload(OnSelection.selection_field)
            .selectinload(FormField.label)
            .selectinload(Text.translations),
            # Select label for fields in field mappings on OnSelection
            selectinload(FormStep.fields.of_type(OnSelection))
            .selectinload(OnSelection.mapping)
            .selectinload(SelectionMapping.fields)
            .selectinload(FormField.label)
            .selectinload(Text.translations),
            # Select text on TextFields in field mappings on OnSelection
            selectinload(FormStep.fields.of_type(OnSelection))
            .selectinload(OnSelection.mapping)
            .selectinload(SelectionMapping.fields.of_type(TextField))
            .selectinload(TextField.text)
            .selectinload(Text.translations),
            # Select placeholder on TextInput in field mappings on OnSelection
            selectinload(FormStep.fields.of_type(OnSelection))
            .selectinload(OnSelection.mapping)
            .selectinload(SelectionMapping.fields.of_type(TextInput))
            .selectinload(TextInput.placeholder)
            .selectinload(Text.translations),
            # Select Checkbox in field mappings on OnSelection
            selectinload(FormStep.fields.of_type(OnSelection))
            .selectinload(OnSelection.mapping)
            .selectinload(SelectionMapping.fields.of_type(Checkbox)),
            # Select Selectbox in field mappings on OnSelection
            selectinload(FormStep.fields.of_type(OnSelection))
            .selectinload(OnSelection.mapping)
            .selectinload(SelectionMapping.fields.of_type(Selectbox))
            .selectinload(Selectbox.placeholder)
            .selectinload(Text.translations),
            # Select Multiselect in field mappings on OnSelection
            selectinload(FormStep.fields.of_type(OnSelection))
            .selectinload(OnSelection.mapping)
            .selectinload(SelectionMapping.fields.of_type(Multiselect))
            .selectinload(Multiselect.placeholder)
            .selectinload(Text.translations),
            # Select TextArea in field mappings on OnSelection
            selectinload(FormStep.fields.of_type(OnSelection))
            .selectinload(OnSelection.mapping)
            .selectinload(SelectionMapping.fields.of_type(TextArea))
            .selectinload(TextArea.placeholder)
            .selectinload(Text.translations),
            # Select NumberInputInt in field mappings on OnSelection
            selectinload(FormStep.fields.of_type(OnSelection))
            .selectinload(OnSelection.mapping)
            .selectinload(SelectionMapping.fields.of_type(NumberInputInt)),
            # Select NumberInputFloat in field mappings on OnSelection
            selectinload(FormStep.fields.of_type(OnSelection))
            .selectinload(OnSelection.mapping)
            .selectinload(SelectionMapping.fields.of_type(NumberInputFloat)),
            # Select DateInput in field mappings on OnSelection
            selectinload(FormStep.fields.of_type(OnSelection))
            .selectinload(OnSelection.mapping)
            .selectinload(SelectionMapping.fields.of_type(DateInput)),
            # Select TimeInput in field mappings on OnSelection
            selectinload(FormStep.fields.of_type(OnSelection))
            .selectinload(OnSelection.mapping)
            .selectinload(SelectionMapping.fields.of_type(TimeInput)),
            # Select FileUploader in field mappings on OnSelection
            selectinload(FormStep.fields.of_type(OnSelection))
            .selectinload(OnSelection.mapping)
            .selectinload(SelectionMapping.fields.of_type(FileUploader)),
            # Select Radio in field mappings on OnSelection
            selectinload(FormStep.fields.of_type(OnSelection))
            .selectinload(OnSelection.mapping)
            .selectinload(SelectionMapping.fields.of_type(Radio)),
            # Select labels in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields)
            .selectinload(FormField.label)
            .selectinload(Text.translations),
            # Select labels in TextField in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(TextField))
            .selectinload(TextField.label)
            .selectinload(Text.translations),
            # Select placeholders in TextInput in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(TextInput))
            .selectinload(TextInput.placeholder)
            .selectinload(Text.translations),
            # Select labels in TextInput in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(TextInput))
            .selectinload(TextInput.label)
            .selectinload(Text.translations),
            # Select labels in Checkbox in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(Checkbox))
            .selectinload(Checkbox.label)
            .selectinload(Text.translations),
            # Select placeholders in Selectbox in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(Selectbox))
            .selectinload(Selectbox.placeholder)
            .selectinload(Text.translations),
            # Select labels in Selectbox in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(Selectbox))
            .selectinload(Selectbox.label)
            .selectinload(Text.translations),
            # Select placeholders in Multiselect in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(Multiselect))
            .selectinload(Multiselect.placeholder)
            .selectinload(Text.translations),
            # Select labels in Multiselect in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(Multiselect))
            .selectinload(Multiselect.label)
            .selectinload(Text.translations),
            # Select placeholders in TextArea in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(TextArea))
            .selectinload(TextArea.placeholder)
            .selectinload(Text.translations),
            # Select labels in TextArea in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(TextArea))
            .selectinload(TextArea.label)
            .selectinload(Text.translations),
            # Select NumberInputInt in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(NumberInputInt))
            .selectinload(NumberInputInt.label)
            .selectinload(Text.translations),
            # Select NumberInputFloat in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(NumberInputFloat))
            .selectinload(NumberInputFloat.label)
            .selectinload(Text.translations),
            # Select DateInput in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(DateInput))
            .selectinload(DateInput.label)
            .selectinload(Text.translations),
            # Select TimeInput in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(TimeInput))
            .selectinload(TimeInput.label)
            .selectinload(Text.translations),
            # Select FileUploader in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(FileUploader))
            .selectinload(FileUploader.label)
            .selectinload(Text.translations),
            # Select Radio in fields on DynamicSection
            selectinload(FormStep.fields.of_type(DynamicSection))
            .selectinload(DynamicSection.fields.of_type(Radio))
            .selectinload(Radio.label)
            .selectinload(Text.translations),
        )


NetboxQueryTypeSqlEnum = Enum(NetboxQueryType)


class NetboxSearchStep(Step):
    query_type = mapped_column(NetboxQueryTypeSqlEnum, nullable=False)

    @classmethod
    def build_query(cls) -> Select[tuple["NetboxSearchStep"]]:
        return select(NetboxSearchStep).options(
            selectinload(NetboxSearchStep.title).selectinload(Text.translations),
            selectinload(NetboxSearchStep.description).selectinload(Text.translations),
        )

    def to_struct(self) -> dto_structs.Step:
        return dto_structs.NetboxSearchStep(
            id=self.id,
            title=self.title.translations[0].contents,
            description=self.description.translations[0].contents if self.description else None,
            query_type=self.query_type.value,
        )

    def __str__(self) -> str:
        return f'{super().__str__()}\n  kind: "NetboxSearchStep"\n  query_type: {self.query_type.value}'


class OutlineSearchStep(Step):
    @classmethod
    def build_query(cls) -> Select[tuple["OutlineSearchStep"]]:
        return select(OutlineSearchStep).options(
            selectinload(OutlineSearchStep.title).selectinload(Text.translations),
            selectinload(OutlineSearchStep.description).selectinload(Text.translations),
        )

    def to_struct(self) -> dto_structs.Step:
        return dto_structs.OutlineSearchStep(
            id=self.id,
            title=self.title.translations[0].contents,
            description=self.description.translations[0].contents if self.description else None,
        )
