# Copyright (c) 2026 Luigi Paone <ppc.luigi.paone@gmail.com>
# SPDX-License-Identifier: BSD-3-Clause

"""Typst-specific fragment builders for report generation.

This module provides fragment builders that generate typst content,
mirroring the LaTeX fragment builders in latexReportMakers.py.
"""

from pathlib import Path
from typing import Any, Dict, List, Union, cast

from pycivil.EXAGeometry.shapes import ShapeArea, ShapeRect, ShapesEnum
from pycivil.EXAStructural.codes import KNOWN_CODES, Code, Codes
from pycivil.EXAStructural.loads import ForcesOnSection
from pycivil.EXAStructural.materials import Concrete, ConcreteSteel
from pycivil.EXAStructural.templateRCRect import RCTemplRectEC2
from pycivil.EXAUtils.EXAExceptions import EXAExceptions
from pycivil.EXAUtils.report import EnumFBSection, TypstFragment, TypstFragmentsBuilder


def _get_section_header(level: EnumFBSection, title: str) -> str:
    """Return typst section header based on level."""
    if level == EnumFBSection.SEC_CHAPTER:
        return f" {title}"
    elif level == EnumFBSection.SEC_SECTION:
        return f"= {title}"
    elif level == EnumFBSection.SEC_SUBSECTION:
        return f"== {title}"
    elif level == EnumFBSection.SEC_SUBSUBSECTION:
        return f"=== {title}"
    else:
        raise EXAExceptions("0001", "level number unknown", level)


class ForcesOnSectionListTypstFB(TypstFragmentsBuilder):
    """Typst fragment builder for forces on section lists."""

    def __init__(
        self, typstTemplatePath: Path, forces: List[ForcesOnSection] | None = None
    ):
        super().__init__()
        if forces is None:
            forces = []
        self.__forces = forces
        self.__opt_section_enabled = True
        self.__opt_section_title = "Forces on section lists with NTC2018"
        self.__opt_section_level = EnumFBSection.SEC_SUBSECTION
        self.__typstTemplatePath = typstTemplatePath

    def forces(self) -> List[ForcesOnSection]:
        return self.__forces

    def setFragmentOptions(self, options: Dict[str, Any]) -> bool:
        if "section_enabled" in options:
            self.__opt_section_enabled = options["section_enabled"]

        if "section_title" in options:
            self.__opt_section_title = options["section_title"]

        if "section_level" in options:
            self.__opt_section_level = options["section_level"]

        return True

    def buildFragment(self) -> TypstFragment:
        f = TypstFragment(self.__typstTemplatePath)
        if self.__opt_section_enabled:
            f.add(_get_section_header(self.__opt_section_level, self.__opt_section_title))

        forces: List[Dict[str, str]] = []
        placeHolders = {"forces": forces}
        for fs in self.__forces:

            fd: Dict[str, str] = {"id": f"{fs.id:.0f}"}

            comp = fs.Fx
            if isinstance(comp, (float, int)):
                fd["Fx"] = f"{comp / 1000:.1f}"
            else:
                fd["Fx"] = ""

            comp = fs.Fy
            if isinstance(comp, (float, int)):
                fd["Fy"] = f"{comp / 1000:.1f}"
            else:
                fd["Fy"] = ""

            comp = fs.Fz
            if isinstance(comp, (float, int)):
                fd["Fz"] = f"{comp / 1000:.1f}"
            else:
                fd["Fz"] = ""

            comp = fs.Mx
            if isinstance(comp, (float, int)):
                fd["Mx"] = f"{comp / 1000000:.1f}"
            else:
                fd["Mx"] = ""

            comp = fs.My
            if isinstance(comp, (float, int)):
                fd["My"] = f"{comp / 1000000:.1f}"
            else:
                fd["My"] = ""

            comp = fs.Mz
            if isinstance(comp, (float, int)):
                fd["Mz"] = f"{comp / 1000000:.1f}"
            else:
                fd["Mz"] = ""

            if fs.descr is not None:
                fd["descr"] = f"{fs.descr:s}"
            else:
                fd["descr"] = ""

            fd["limitState"] = f"{fs.getLimitStateTr():s}"
            fd["frequency"] = f"{fs.getFrequencyTr():s}"
            forces.append(fd)

        f.add(
            templateName="template-ita-loads-NTC2018.typ",
            templatePlaceholders=placeHolders,
        )

        self._setFragment(f)
        return f


class CodesTypstFB(Codes, TypstFragmentsBuilder):
    """Typst fragment builder for codes/bibliography."""

    def __init__(
        self, typstTemplatePath: Path, codes: Union[List[Code], None] = None
    ) -> None:

        if codes is None:
            codes = []

        Codes.__init__(self, codes)
        TypstFragmentsBuilder.__init__(self)
        self.__opt_nameList = "Normative utilizzate"
        self.__opt_section = True
        self.__opt_subsection = False
        self.__typstTemplatePath = typstTemplatePath

    def setOptionNameList(self, name: str = "Normative utilizzate") -> None:
        self.__opt_nameList = name

    def setOptionSection(self) -> None:
        self.__opt_section = True
        self.__opt_subsection = False

    def setOptionSubSection(self) -> None:
        self.__opt_section = False
        self.__opt_subsection = True

    def setFragmentOptions(self, options: Dict[str, Any]) -> bool:
        return True

    def buildFragment(self) -> TypstFragment:
        f = TypstFragment(self.__typstTemplatePath)

        if self.__opt_section:
            f.add(f"= {self.__opt_nameList}")
        elif self.__opt_subsection:
            f.add(f"== {self.__opt_nameList}")

        f.add("")  # Empty line before list

        for c in self.getCodes():
            try:
                dfc = KNOWN_CODES[c.codeStr()]
            except KeyError:
                print("skipped")
                continue
            f.add(f"- *[{dfc.code}]* _{dfc.description}_, {dfc.author}, {dfc.year}")

        self._setFragment(f)
        return f


class ConcreteTypstFB(TypstFragmentsBuilder):
    """Typst fragment builder for concrete material properties."""

    def __init__(self, typstTemplatePath: Path, concrete: Concrete):
        super().__init__()
        self.__concrete = concrete
        self.__opt_section_enabled = True
        self.__opt_section_title = "Concrete material"
        self.__opt_section_level = EnumFBSection.SEC_SUBSECTION
        self.__opt_glossary = False
        self.__typstTemplatePath = typstTemplatePath

    def setFragmentOptions(self, options: Dict[str, Any]) -> bool:
        if "section_enabled" in options:
            self.__opt_section_enabled = options["section_enabled"]

        if "section_title" in options:
            self.__opt_section_title = options["section_title"]

        if "section_level" in options:
            self.__opt_section_level = options["section_level"]

        if "glossary" in options:
            self.__opt_glossary = options["glossary"]

        return True

    def buildFragment(self) -> TypstFragment:
        f = TypstFragment(self.__typstTemplatePath)
        if self.__opt_section_enabled:
            f.add(_get_section_header(self.__opt_section_level, self.__opt_section_title))

        placeHolders = {
            "classe": f"{self.__concrete.catStr():s}",
            "norma": f"{self.__concrete.codeStr():s}",
            "environnment": "{:s}".format(self.__concrete.getEnvironmentTr("IT")),
            "fck": "{:.1f} \"MPa\"".format(self.__concrete.get_fck()),
            "fcm": "{:.1f} \"MPa\"".format(self.__concrete.get_fcm()),
            "fctm": "{:.1f} \"MPa\"".format(self.__concrete.get_fctm()),
            "Ecm": "{:.1f} \"MPa\"".format(self.__concrete.get_Ecm()),
            "gammac": f"{self.__concrete.get_gammac():.2f}",
            "sigmac_max_c": "{:.1f} \"MPa\"".format(self.__concrete.get_sigmac_max_c()),
            "sigmac_max_q": "{:.1f} \"MPa\"".format(self.__concrete.get_sigmac_max_q()),
            "ecu": f"{self.__concrete.get_ecu():.4f}",
            "ec2": f"{self.__concrete.get_ec2():.4f}",
            "lambda": f"{self.__concrete.get_lambda():.2f}",
            "eta": f"{self.__concrete.get_eta():.2f}",
            "alphacc": f"{self.__concrete.get_alphacc():.2f}",
            "alphacc_fire": f"{self.__concrete.get_alphacc_fire():.2f}",
            "byCode": self.__concrete.isSetByCode(),
            "glossary": self.__opt_glossary,
        }

        f.add(
            templateName="template-ita-material-concrete.typ",
            templatePlaceholders=placeHolders,
        )

        self._setFragment(f)
        return f


class SteelConcreteTypstFB(TypstFragmentsBuilder):
    """Typst fragment builder for rebar steel material properties."""

    def __init__(self, typstTemplatePath: Path, steel: ConcreteSteel):
        super().__init__()
        self.__steel = steel
        self.__opt_section_enabled = True
        self.__opt_section_title = "Steel for rebar material"
        self.__opt_section_level = EnumFBSection.SEC_SUBSECTION
        self.__typstTemplatePath = typstTemplatePath

    def setFragmentOptions(self, options: Dict[str, Any]) -> bool:
        if "section_enabled" in options:
            self.__opt_section_enabled = options["section_enabled"]

        if "section_title" in options:
            self.__opt_section_title = options["section_title"]

        if "section_level" in options:
            self.__opt_section_level = options["section_level"]

        return True

    def buildFragment(self) -> TypstFragment:
        f = TypstFragment(self.__typstTemplatePath)
        if self.__opt_section_enabled:
            f.add(_get_section_header(self.__opt_section_level, self.__opt_section_title))

        placeHolders = {
            "classe": f"{self.__steel.catStr():s}",
            "norma": f"{self.__steel.codeStr():s}",
            "sensitivity": "{:s}".format(self.__steel.getEnvironmentTr("IT")),
            "fsy": "{:.0f} \"MPa\"".format(self.__steel.get_fsy()),
            "Es": "{:.0f} \"MPa\"".format(self.__steel.get_Es()),
            "gammas": f"{self.__steel.get_gammas():.2f}",
            "sigmas_max_c": "{:.1f} \"MPa\"".format(self.__steel.get_sigmas_max_c()),
            "esu": f"{self.__steel.get_esu():.4f}",
            "esy": f"{self.__steel.get_esy():.4f}",
            "byCode": self.__steel.isSetByCode(),
        }

        f.add(
            templateName="template-ita-material-rebar.typ",
            templatePlaceholders=placeHolders,
        )

        self._setFragment(f)
        return f


class ConcreteSectionTypstFB(TypstFragmentsBuilder):
    """Typst fragment builder for RC section geometry."""

    def __init__(self, typstTemplatePath: Path, rcs: RCTemplRectEC2):
        super().__init__()
        self.__rcs = rcs
        self.__opt_section_enabled = True
        self.__opt_section_title = "Reinforced concrete section"
        self.__opt_section_level = EnumFBSection.SEC_SUBSECTION
        self.__opt_glossary = False
        self.__typstTemplatePath = typstTemplatePath

    def getOptSectionEnabled(self) -> bool:
        return self.__opt_section_enabled

    def getOptSectionTitle(self) -> str:
        return self.__opt_section_title

    def getOptSectionLevel(self) -> EnumFBSection:
        return self.__opt_section_level

    def getOptGlossary(self) -> bool:
        return self.__opt_glossary

    def setFragmentOptions(self, options: Dict[str, Any]) -> bool:
        if "section_enabled" in options:
            self.__opt_section_enabled = options["section_enabled"]

        if "section_title" in options:
            self.__opt_section_title = options["section_title"]

        if "section_level" in options:
            self.__opt_section_level = options["section_level"]

        if "glossary" in options:
            self.__opt_glossary = options["glossary"]

        return True

    def buildFragment(self) -> TypstFragment:
        f = TypstFragment(self.__typstTemplatePath)
        if self.__opt_section_enabled:
            f.add(_get_section_header(self.__opt_section_level, self.__opt_section_title))

        placeHolders: Dict[str, Any] = {}
        #
        # RECTANGULAR SHAPE
        #
        if (
            self.__rcs.getStructConcretelItem().getShape().getType()
            == ShapesEnum.SHAPE_RECT
        ):
            placeHolders["shape"] = "RECT"
            rectShape = cast(ShapeRect, self.__rcs.getStructConcretelItem().getShape())
            placeHolders["width"] = rectShape.w()
            placeHolders["height"] = rectShape.h()

            placeHolders["xTL"] = rectShape.getShapePoint("TL").x
            placeHolders["xTR"] = rectShape.getShapePoint("TR").x
            placeHolders["xBL"] = rectShape.getShapePoint("BL").x
            placeHolders["xBR"] = rectShape.getShapePoint("BR").x
            placeHolders["yTL"] = rectShape.getShapePoint("TL").y
            placeHolders["yTR"] = rectShape.getShapePoint("TR").y
            placeHolders["yBL"] = rectShape.getShapePoint("BL").y
            placeHolders["yBR"] = rectShape.getShapePoint("BR").y
        else:
            raise EXAExceptions("0001", "Section Unknown")

        steels: List[Dict[str, str]] = []

        for idx, i in enumerate(self.__rcs.getStructSteelItems()):
            steel: Dict[str, str] = {}
            areaShape = cast(ShapeArea, i.getShape())
            steel["id"] = str(i.getId()) if i.getId() != -1 else ""
            steel["xPos"] = "{:.1f}".format(areaShape.getShapePoint("O").x)
            steel["yPos"] = "{:.1f}".format(areaShape.getShapePoint("O").y)
            steel["diam"] = f"{self.__rcs.getSteelDiamAt(idx):.0f}"
            steel["area"] = f"{self.__rcs.getSteelAreaAt(idx):.1f}"
            steels.append(steel)

        placeHolders["rebars"] = steels

        # Stirrup data
        placeHolders["stirrupArea"] = f"{self.__rcs.getStirruptArea():.1f}"
        placeHolders["stirrupStep"] = f"{self.__rcs.getStirruptDistance():.1f}"
        placeHolders["stirrupAngle"] = f"{self.__rcs.getStirruptAngle():.1f}"

        f.add(
            templateName="template-ita-rc-section-rec.typ",
            templatePlaceholders=placeHolders,
        )
        self._setFragment(f)
        return f
