"""LaTeX report exporter.

Generates LaTeX documents with booktabs tables, suitable for
compilation with pdflatex.
"""

from __future__ import annotations

from pathlib import Path
from typing import Any


class LaTeXExporter:
    """Export report data as LaTeX document.

    Generates professional-quality LaTeX with booktabs tables,
    section hierarchy, and figure references.
    """

    def export(
        self,
        context: dict[str, Any],
        output: str | Path,
        standalone: bool = True,
    ) -> Path:
        """Export report context as LaTeX document.

        Parameters
        ----------
        context : dict
            Template context from a Transformer.
        output : str or Path
            Output .tex file path.
        standalone : bool
            If True, generates a complete document. If False,
            generates only the body content.

        Returns
        -------
        Path
            Path to the saved file.
        """
        path = Path(output)
        path.parent.mkdir(parents=True, exist_ok=True)

        latex = self._render_latex(context, standalone)
        path.write_text(latex, encoding="utf-8")

        return path

    def export_string(self, context: dict[str, Any], standalone: bool = True) -> str:
        """Return LaTeX as a string without saving."""
        return self._render_latex(context, standalone)

    def _render_latex(self, context: dict[str, Any], standalone: bool) -> str:
        """Render complete LaTeX document from context."""
        sections: list[str] = []

        model_info = context.get("model_info", {})
        sections.append(self._render_model_info(model_info))

        parameters = context.get("parameters", [])
        if parameters:
            sections.append(self._render_parameters_table(parameters))

        info_criteria = context.get("info_criteria", {})
        if any(v is not None for v in info_criteria.values()):
            sections.append(self._render_info_criteria(info_criteria))

        diagnostics = context.get("diagnostics", {})
        if diagnostics:
            sections.append(self._render_diagnostics(diagnostics))

        body = "\n\n".join(sections)

        metadata = context.get("metadata", {})
        footer = self._render_footer(metadata)

        if standalone:
            return self._wrap_document(body + "\n\n" + footer, model_info)
        return body + "\n\n" + footer

    def _wrap_document(self, body: str, model_info: dict[str, Any]) -> str:
        """Wrap body in complete LaTeX document."""
        title = _tex_escape(model_info.get("model_name", "State Space Model Report"))
        return f"""\\documentclass[11pt,a4paper]{{article}}
\\usepackage[utf8]{{inputenc}}
\\usepackage[T1]{{fontenc}}
\\usepackage{{booktabs}}
\\usepackage{{geometry}}
\\usepackage{{graphicx}}
\\usepackage{{hyperref}}
\\usepackage{{amsmath}}

\\geometry{{margin=2.5cm}}

\\title{{{title} -- Report}}
\\author{{Generated by kalmanbox}}
\\date{{\\today}}

\\begin{{document}}
\\maketitle

{body}

\\end{{document}}
"""

    def _render_model_info(self, info: dict[str, Any]) -> str:
        """Render model info section."""
        lines = [
            "\\section{Model Summary}",
            "\\begin{tabular}{ll}",
            "\\toprule",
        ]
        if info.get("model_name"):
            lines.append(f"Model & {_tex_escape(info['model_name'])} \\\\")
        if info.get("n_obs"):
            lines.append(f"Observations & {info['n_obs']} \\\\")
        if info.get("n_states"):
            lines.append(f"States & {info['n_states']} \\\\")
        if info.get("n_params"):
            lines.append(f"Parameters & {info['n_params']} \\\\")
        if info.get("optimizer"):
            lines.append(f"Optimizer & {_tex_escape(info['optimizer'])} \\\\")
        converged = info.get("converged", True)
        lines.append(f"Converged & {'Yes' if converged else 'No'} \\\\")
        lines.extend(["\\bottomrule", "\\end{tabular}"])
        return "\n".join(lines)

    def _render_parameters_table(self, params: list[dict[str, Any]]) -> str:
        """Render parameter estimation table."""
        lines = [
            "\\section{Parameter Estimates}",
            "\\begin{tabular}{lrrrrr}",
            "\\toprule",
            "Parameter & Estimate & Std. Error & t-stat & p-value & \\\\",
            "\\midrule",
        ]
        for p in params:
            name = _tex_escape(p["name"])
            val = f"{p['value']:.4f}"
            se = f"{p.get('se', float('nan')):.4f}" if "se" in p else "--"
            tstat = f"{p.get('t_stat', float('nan')):.4f}" if "t_stat" in p else "--"
            pval = f"{p.get('p_value', float('nan')):.4f}" if "p_value" in p else "--"
            sig = ""
            if "p_value" in p:
                pv = p["p_value"]
                if pv < 0.001:
                    sig = "***"
                elif pv < 0.01:
                    sig = "**"
                elif pv < 0.05:
                    sig = "*"
            lines.append(f"{name} & {val} & {se} & {tstat} & {pval} & {sig} \\\\")
        lines.extend(["\\bottomrule", "\\end{tabular}"])
        lines.append("\\footnotesize{Significance: *** p<0.001, ** p<0.01, * p<0.05}")
        return "\n".join(lines)

    def _render_info_criteria(self, ic: dict[str, float | None]) -> str:
        """Render information criteria."""
        lines = [
            "\\subsection{Information Criteria}",
            "\\begin{tabular}{lr}",
            "\\toprule",
        ]
        for name, val in ic.items():
            display_name = name.upper() if name != "loglike" else "Log-Likelihood"
            if val is not None:
                lines.append(f"{display_name} & {val:.4f} \\\\")
            else:
                lines.append(f"{display_name} & -- \\\\")
        lines.extend(["\\bottomrule", "\\end{tabular}"])
        return "\n".join(lines)

    def _render_diagnostics(self, diag: dict[str, Any]) -> str:
        """Render diagnostic tests section."""
        lines = ["\\section{Diagnostics}"]
        if not diag:
            lines.append("No diagnostic tests available.")
            return "\n".join(lines)
        for test_name, test_result in diag.items():
            lines.append(f"\\subsection{{{_tex_escape(test_name.replace('_', ' ').title())}}}")
            lines.append(f"{_tex_escape(str(test_result))}")
        return "\n".join(lines)

    def _render_footer(self, metadata: dict[str, str]) -> str:
        """Render footer with metadata."""
        version = metadata.get("kalmanbox_version", "unknown")
        generated = metadata.get("generated_at", "")
        report_type = metadata.get("report_type", "SSM")
        return (
            f"\\vfill\n"
            f"\\noindent\\rule{{\\textwidth}}{{0.4pt}}\n"
            f"\\footnotesize{{Report type: {report_type} | "
            f"Generated: {generated} | kalmanbox v{version}}}"
        )


def _tex_escape(text: str) -> str:
    """Escape special LaTeX characters."""
    special = {
        "&": "\\&",
        "%": "\\%",
        "$": "\\$",
        "#": "\\#",
        "_": "\\_",
        "{": "\\{",
        "}": "\\}",
        "~": "\\textasciitilde{}",
        "^": "\\textasciicircum{}",
    }
    for char, replacement in special.items():
        text = text.replace(char, replacement)
    return text
