"""Excel template emitter from the Document IR.

Uses SSOT (Single Source of Truth) pattern: XML structure from xml-structures/,
Python modifies attribute values via lxml DOM manipulation with cached templates.
Excel does not support font embedding, so font-related hooks are no-ops.

Cell styles generated:
- Normal (body font, body size)
- Title, Heading 1-4 (heading font, scaled sizes)
- Total (body font, bold, top border)
- Note, Explanatory Text (body font, smaller/italic)
- Accent 1-6, 20%/40%/60% tints (themed fills)
- Comma, Currency, Percent (number formats)
- Hyperlink, Followed Hyperlink
- Good, Bad, Neutral (semantic colors)
- Input, Output, Calculation, Check Cell, Warning Text

Table styles: 6 accent variants with header row, banded rows, total row.
Locale-aware: page setup (A4/Letter), margins, number formats, header/footer.
"""

from __future__ import annotations

import html
import math
from pathlib import Path
from typing import Dict, Literal

from tokenmoulds.design.units import emu_to_inches, emu_to_pt
from tokenmoulds.emitters.excel_constants import (
    _LETTER_LOCALES,
    _PAPER_A4,
    _PAPER_LETTER,
    _THEME_ACCENT1,
    SSML_NS,
    _fmt_size,
)
from tokenmoulds.emitters.excel_stylesheet import (
    _col_index_to_letters,
    _StyleSheetBuilder,
)
from tokenmoulds.emitters.ooxml_base import OOXMLBaseEmitter
from tokenmoulds.emitters.xml_helpers import (
    clone_template_element,
    remove_template_elements,
    replace_placeholders,
    serialize_tree,
)
from tokenmoulds.ir import DocumentIR


def _default_row_height_pt(doc: DocumentIR) -> float:
    """Derive default row height from baseline grid or body font size.

    Excel row height = enough to fit body text + padding, snapped to the
    baseline grid when available.  Falls back to 1.4× body size.
    """
    body_size = doc.body_style.font_size_pt
    if doc.page_geometry and doc.page_geometry.baseline_grid_emu > 0:
        grid_pt = emu_to_pt(doc.page_geometry.baseline_grid_emu)
        lines = math.ceil(body_size * 1.2 / grid_pt)
        return lines * grid_pt
    # No grid — use 1.4× line height (Excel default ratio)
    return round(body_size * 1.4, 1)


def _page_margins_inches(doc: DocumentIR, locale: str) -> Dict[str, str]:
    """Get page margins in inches from IR or locale defaults."""
    if doc.page_geometry:
        m = doc.page_geometry.margins
        return {
            "left": f"{emu_to_inches(m.left):.2f}",
            "right": f"{emu_to_inches(m.right):.2f}",
            "top": f"{emu_to_inches(m.top):.2f}",
            "bottom": f"{emu_to_inches(m.bottom):.2f}",
        }
    # Locale defaults
    if locale in _LETTER_LOCALES:
        return {"left": "0.70", "right": "0.70", "top": "0.75", "bottom": "0.75"}
    # A4 metric defaults (narrower margins)
    return {"left": "0.79", "right": "0.79", "top": "0.79", "bottom": "0.79"}


class ExcelEmitter(OOXMLBaseEmitter):
    """Build Excel workbooks from the Document IR.

    Generates themed cell styles visible in Excel's Home > Cell Styles gallery:
    - Title, Heading 1-4 (heading font, scaled from type scale)
    - 6 accent colors × 4 variants (solid, 20%, 40%, 60%)
    - Good/Bad/Neutral semantic styles
    - Input/Output/Calculation/Check Cell/Warning
    - Comma, Currency, Percent number formats
    - Hyperlink, Followed Hyperlink
    - Note, Explanatory Text, Total
    """

    PACKAGE_STRUCTURE: Dict[str, str] = {
        "[Content_Types].xml": "templates/content_types.xml",
        "_rels/.rels": "templates/package_rels.xml",
        "xl/workbook.xml": "templates/workbook.xml",
        "xl/styles.xml": "templates/styles.xml",
        "xl/worksheets/sheet1.xml": "templates/worksheet.xml",
        "xl/theme/theme1.xml": "templates/theme.xml",
        "xl/_rels/workbook.xml.rels": "templates/workbook_rels.xml",
        "docProps/core.xml": "templates/docprops_core.xml",
        "docProps/app.xml": "templates/docprops_app.xml",
    }

    def __init__(
        self,
        document: DocumentIR,
        template_root: Path | str | None = None,
        use_pure_text_colors: bool = True,
    ) -> None:
        super().__init__(
            document=document,
            template_root=template_root,
            embed_fonts=False,
            fonts_dir=None,
            use_pure_text_colors=use_pure_text_colors,
        )

    def _get_format_key(self) -> str:
        return "excel"

    def _get_base_template_path(self) -> Path:
        return Path()

    def _get_package_structure(self) -> Dict[str, str]:
        return self.PACKAGE_STRUCTURE

    def _get_document_title(self) -> str:
        return f"{self.document.org_id} Workbook"

    def _get_font_embedding_format(self) -> Literal["odttf", "eot", "none"]:
        return "none"

    def _get_font_entries_path(self) -> str:
        return ""

    def _should_customize_entry(self, entry_path: str) -> bool:
        return entry_path in (
            "xl/theme/theme1.xml",
            "xl/styles.xml",
            "xl/worksheets/sheet1.xml",
            "xl/workbook.xml",
            "docProps/core.xml",
            "docProps/app.xml",
        )

    def _customize_entry(self, entry_path: str, data: bytes) -> bytes | None:
        if entry_path == "xl/theme/theme1.xml":
            return self._customize_theme_ssot(data)
        elif entry_path == "xl/styles.xml":
            return self._customize_styles(data)
        elif entry_path == "xl/worksheets/sheet1.xml":
            return self._customize_worksheet_ssot(data)
        elif entry_path == "xl/workbook.xml":
            return self._customize_workbook_ssot(data)
        elif entry_path == "docProps/core.xml":
            return self._customize_core_props_ssot(data)
        elif entry_path == "docProps/app.xml":
            return self._customize_app_props_ssot(data)
        return data

    # build_package() inherited from OOXMLBaseEmitter

    # =========================================================================
    # STYLE GENERATION
    # =========================================================================

    def _customize_styles(self, data: bytes) -> bytes:
        """Generate styles.xml with full cell style library."""
        return _StyleSheetBuilder(self.document).build()

    # =========================================================================
    # SSOT CUSTOMIZATIONS
    # =========================================================================

    def _customize_workbook_ssot(self, data: bytes) -> bytes:
        context = {"SHEET_NAME": self.document.org_id}
        root = self.templates.read_tree("templates/workbook.xml")
        replace_placeholders(root, context)
        return serialize_tree(root, xml_declaration=False)

    def _customize_worksheet_ssot(self, data: bytes) -> bytes:
        body = self.document.body_style
        root = self.templates.read_tree("templates/worksheet.xml")
        locale = (self.document.locale or "US").upper()

        # -- Tab color: theme accent1 --
        tab_color = root.find(f".//{{{SSML_NS}}}tabColor")
        if tab_color is not None:
            tab_color.set("theme", _THEME_ACCENT1)
            tab_color.set("tint", "0")
            # Remove hardcoded rgb if present — theme reference takes precedence
            if "rgb" in tab_color.attrib:
                del tab_color.attrib["rgb"]

        # -- Default row height from baseline grid --
        fmt_pr = root.find(f"{{{SSML_NS}}}sheetFormatPr")
        if fmt_pr is not None:
            row_height = _default_row_height_pt(self.document)
            fmt_pr.set("defaultRowHeight", _fmt_size(row_height))
            fmt_pr.set("customHeight", "1")

        # -- Page setup: paper size from locale --
        page_setup = root.find(f"{{{SSML_NS}}}pageSetup")
        if page_setup is not None:
            paper_size = _PAPER_LETTER if locale in _LETTER_LOCALES else _PAPER_A4
            page_setup.set("paperSize", paper_size)

        # -- Page margins from IR page geometry or locale defaults --
        page_margins = root.find(f"{{{SSML_NS}}}pageMargins")
        if page_margins is not None:
            margins = _page_margins_inches(self.document, locale)
            page_margins.set("left", margins["left"])
            page_margins.set("right", margins["right"])
            page_margins.set("top", margins["top"])
            page_margins.set("bottom", margins["bottom"])

        # -- Header/footer with brand font --
        odd_header = root.find(f".//{{{SSML_NS}}}oddHeader")
        if odd_header is not None:
            font_name = body.font_family
            odd_header.text = f'&L&"{font_name},Regular"&K000000&A&C&R'

        # -- Columns --
        cols_el = root.find(f"{{{SSML_NS}}}cols")
        if cols_el is not None:
            col_widths = [(1, 1, 15), (2, 2, 25), (3, 3, 30)]
            for min_col, max_col, width in col_widths:
                col_clone = clone_template_element(root, "col")
                replace_placeholders(
                    col_clone,
                    {"MIN": str(min_col), "MAX": str(max_col), "WIDTH": str(width)},
                )
                cols_el.append(col_clone)

        # -- Sheet data --
        sheet_data = root.find(f"{{{SSML_NS}}}sheetData")
        if sheet_data is not None:
            rows_data = [
                (1, ["Metric", "Value", "Notes"]),
                (None, ["Org", self.document.org_id, self.document.locale]),
                (
                    None,
                    ["Accent 1", self.document.color_theme.accent1, "Primary palette"],
                ),
                (
                    None,
                    [
                        "Table Font",
                        f"{body.font_family} {body.font_size_pt:.1f}pt",
                        "Baseline-aligned rows",
                    ],
                ),
            ]

            for row_idx, (style_xf, values) in enumerate(rows_data, start=1):
                row_clone = clone_template_element(root, "row")
                replace_placeholders(row_clone, {"ROW_NUM": str(row_idx)})

                for child in list(row_clone):
                    if child.get("data-template"):
                        row_clone.remove(child)

                for col_idx, value in enumerate(values):
                    column = _col_index_to_letters(col_idx)
                    cell_ref = f"{column}{row_idx}"
                    cell_clone = clone_template_element(root, "cell")
                    replace_placeholders(
                        cell_clone,
                        {"CELL_REF": cell_ref, "VALUE": html.escape(str(value))},
                    )
                    if style_xf is not None:
                        cell_clone.set("s", str(style_xf))
                    row_clone.append(cell_clone)

                sheet_data.append(row_clone)

        remove_template_elements(root)
        return serialize_tree(root, xml_declaration=False)

    def _customize_theme_ssot(self, data: bytes) -> bytes:
        root = self.templates.read_tree("templates/theme.xml")
        theme = self.document.color_theme
        typo = self.document.typography

        self._set_color_value(root, "dk1", theme.dk1)
        self._set_color_value(root, "lt1", theme.lt1)
        self._set_color_value(root, "dk2", theme.dk2)
        self._set_color_value(root, "lt2", theme.lt2)
        self._set_color_value(root, "accent1", theme.accent1)
        self._set_color_value(root, "accent2", theme.accent2)
        self._set_color_value(root, "accent3", theme.accent3)
        self._set_color_value(root, "accent4", theme.accent4)
        self._set_color_value(root, "accent5", theme.accent5)
        self._set_color_value(root, "accent6", theme.accent6)
        self._set_color_value(root, "hlink", theme.hyperlink)
        self._set_color_value(root, "folHlink", theme.hyperlink_followed)

        self._set_font_typeface(root, "majorFont", "latin", typo.heading_font)
        self._set_font_typeface(root, "minorFont", "latin", typo.body_font)

        return self._serialize_tree(root)

    def _customize_core_props_ssot(self, data: bytes) -> bytes:
        timestamp = self._utc_timestamp()
        context = {
            "TITLE": self._get_document_title(),
            "CREATOR": "TokenMoulds",
            "MODIFIER": "TokenMoulds",
            "TIMESTAMP": timestamp,
        }
        return self._render_tree("templates/docprops_core.xml", context)

    def _customize_app_props_ssot(self, data: bytes) -> bytes:
        context = {
            "APPLICATION": "TokenMoulds",
            "COMPANY": self.document.org_id,
        }
        return self._render_tree("templates/docprops_app.xml", context)

    def _get_font_metadata_entries(self) -> list:
        return []
