"""Generate a LaTeX-formatted academic paper from a DocumentModel.

Produces a .tex file structured as an IEEE/ACM-style technical paper
with abstract, sections, code listings, tables, and references.
"""

from __future__ import annotations

from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING

from ..core.knowledge_graph import EntityType, KnowledgeGraph
from ..core.models import (
    BlockType,
    CodeBlock,
    ContentBlock,
    DocumentModel,
    GenerationResult,
    ImageBlock,
    ListBlock,
    OutputFormat,
    ParagraphBlock,
    Section,
    TableBlock,
)
from .base import BaseGenerator

if TYPE_CHECKING:
    from .diagram_extractor import ImageCache


class LatexGenerator(BaseGenerator):
    """DocumentModel → LaTeX academic paper (.tex)."""

    format = OutputFormat.LATEX

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def generate(self, doc: DocumentModel, output_dir: Path) -> GenerationResult:
        filename = self._safe_filename(doc.metadata.repo_name or "paper", "tex")
        output_path = self._ensure_dir(output_dir) / filename

        try:
            content = self._build_latex(doc)
            output_path.write_text(content, encoding="utf-8")
            return GenerationResult(
                format=OutputFormat.LATEX,
                output_path=output_path,
            )
        except Exception as exc:
            return GenerationResult(
                format=OutputFormat.LATEX,
                output_path=output_path,
                success=False,
                error=str(exc),
            )

    # ------------------------------------------------------------------
    # Internal – build the LaTeX document
    # ------------------------------------------------------------------

    def _build_latex(self, doc: DocumentModel) -> str:
        name = doc.metadata.repo_name or "Project"
        url = doc.metadata.repo_url or ""
        now = datetime.now().strftime("%B %Y")

        parts: list[str] = []

        # -- Preamble ----------------------------------------------------
        parts.append(r"\documentclass[conference]{IEEEtran}")
        parts.append(r"\usepackage[utf8]{inputenc}")
        parts.append(r"\usepackage[T1]{fontenc}")
        parts.append(r"\usepackage{hyperref}")
        parts.append(r"\usepackage{listings}")
        parts.append(r"\usepackage{xcolor}")
        parts.append(r"\usepackage{booktabs}")
        parts.append(r"\usepackage{graphicx}")
        parts.append(r"\usepackage{url}")
        parts.append("")
        parts.append(r"\definecolor{codebg}{RGB}{245,245,245}")
        parts.append(r"\lstset{")
        parts.append(r"  backgroundcolor=\color{codebg},")
        parts.append(r"  basicstyle=\ttfamily\small,")
        parts.append(r"  breaklines=true,")
        parts.append(r"  frame=single,")
        parts.append(r"  captionpos=b,")
        parts.append(r"  numbers=left,")
        parts.append(r"  numberstyle=\tiny\color{gray},")
        parts.append(r"}")
        parts.append("")

        # -- Title -------------------------------------------------------
        parts.append(r"\title{" + self._escape(name) + r": A Technical Overview}")
        parts.append(r"\author{Auto-generated by opendocs}")
        parts.append(r"\date{" + now + r"}")
        parts.append("")
        parts.append(r"\begin{document}")
        parts.append(r"\maketitle")
        parts.append("")

        # -- Abstract ----------------------------------------------------
        abstract = self._build_abstract(doc)
        parts.append(r"\begin{abstract}")
        parts.append(self._escape(abstract))
        parts.append(r"\end{abstract}")
        parts.append("")

        # -- Keywords ----------------------------------------------------
        keywords = self._build_keywords()
        if keywords:
            parts.append(r"\begin{IEEEkeywords}")
            parts.append(keywords)
            parts.append(r"\end{IEEEkeywords}")
            parts.append("")

        # -- Introduction ------------------------------------------------
        parts.append(r"\section{Introduction}")
        intro = self._build_intro(doc, name, url)
        parts.append(intro)
        parts.append("")

        # -- Main sections from README -----------------------------------
        for section in doc.sections:
            self._render_section(parts, section, depth=1)

        # -- Technology Overview -----------------------------------------
        tech_section = self._build_tech_section()
        if tech_section:
            parts.append(r"\section{Technology Stack}")
            parts.append(tech_section)
            parts.append("")

        # -- Architecture ------------------------------------------------
        if self.kg and self.kg.relations:
            parts.append(r"\section{Architecture}")
            parts.append(self._build_architecture_section())
            parts.append("")

        # -- Conclusion --------------------------------------------------
        parts.append(r"\section{Conclusion}")
        parts.append(self._build_conclusion(doc, name))
        parts.append("")

        # -- References --------------------------------------------------
        parts.append(r"\begin{thebibliography}{9}")
        if url:
            parts.append(
                r"\bibitem{repo} " + self._escape(name)
                + r" repository. \url{" + url + r"}"
            )
        parts.append(
            r"\bibitem{opendocs} opendocs --- README documentation generator. "
            r"\url{https://pypi.org/project/opendocs/}"
        )
        parts.append(r"\end{thebibliography}")
        parts.append("")

        parts.append(r"\end{document}")
        parts.append("")

        return "\n".join(parts)

    # ------------------------------------------------------------------
    # Section rendering
    # ------------------------------------------------------------------

    def _render_section(
        self, parts: list[str], section: Section, depth: int,
    ) -> None:
        """Recursively render a section as LaTeX."""
        if not section.title:
            return

        # LaTeX section hierarchy
        cmd_map = {1: "section", 2: "subsection", 3: "subsubsection"}
        cmd = cmd_map.get(depth, "paragraph")
        parts.append(f"\\{cmd}{{{self._escape(section.title)}}}")
        parts.append("")

        for block in section.blocks:
            self._render_block(parts, block)

        for sub in section.subsections:
            self._render_section(parts, sub, depth + 1)

    def _render_block(self, parts: list[str], block: ContentBlock) -> None:
        """Render a content block as LaTeX."""
        if isinstance(block, ParagraphBlock):
            parts.append(self._escape(block.text))
            parts.append("")

        elif isinstance(block, CodeBlock):
            lang = block.language or "text"
            parts.append(f"\\begin{{lstlisting}}[language={self._latex_lang(lang)}]")
            parts.append(block.code.rstrip())
            parts.append(r"\end{lstlisting}")
            parts.append("")

        elif isinstance(block, ListBlock):
            env = "enumerate" if block.ordered else "itemize"
            parts.append(f"\\begin{{{env}}}")
            for item in block.items:
                parts.append(f"  \\item {self._escape(item)}")
            parts.append(f"\\end{{{env}}}")
            parts.append("")

        elif isinstance(block, TableBlock):
            if block.headers:
                ncols = len(block.headers)
                col_spec = "|".join(["l"] * ncols)
                parts.append(r"\begin{table}[h]")
                parts.append(r"\centering")
                parts.append(f"\\begin{{tabular}}{{{col_spec}}}")
                parts.append(r"\toprule")
                parts.append(
                    " & ".join(
                        f"\\textbf{{{self._escape(h)}}}" for h in block.headers
                    )
                    + r" \\"
                )
                parts.append(r"\midrule")
                for row in block.rows:
                    parts.append(
                        " & ".join(self._escape(c) for c in row) + r" \\"
                    )
                parts.append(r"\bottomrule")
                parts.append(r"\end{tabular}")
                parts.append(r"\end{table}")
                parts.append("")

        elif isinstance(block, ImageBlock):
            if block.src:
                parts.append(r"\begin{figure}[h]")
                parts.append(r"\centering")
                parts.append(
                    r"\includegraphics[width=\linewidth]{"
                    + self._escape(block.src) + r"}"
                )
                if block.alt:
                    parts.append(f"\\caption{{{self._escape(block.alt)}}}")
                parts.append(r"\end{figure}")
                parts.append("")

    # ------------------------------------------------------------------
    # Content builders
    # ------------------------------------------------------------------

    def _build_abstract(self, doc: DocumentModel) -> str:
        """Build the paper abstract."""
        if self.kg and self.kg.executive_summary:
            return self.kg.executive_summary

        paras = [
            b.text for b in doc.all_blocks
            if isinstance(b, ParagraphBlock) and len(b.text) > 30
        ]
        if paras:
            abstract = paras[0]
            if len(paras) > 1:
                abstract += " " + paras[1]
            return abstract[:500]

        return (
            f"This paper presents {doc.metadata.repo_name or 'the project'}, "
            f"a software system documented in its README file. We analyze its "
            f"architecture, key features, and technology stack."
        )

    def _build_keywords(self) -> str:
        """Build keyword list from KG."""
        keywords: list[str] = []
        if self.kg:
            for e in self.kg.entities:
                if e.entity_type in (
                    EntityType.TECHNOLOGY, EntityType.FRAMEWORK,
                    EntityType.LANGUAGE, EntityType.PLATFORM,
                ):
                    keywords.append(e.name)
        keywords = list(dict.fromkeys(keywords))[:8]
        return ", ".join(keywords) if keywords else ""

    def _build_intro(self, doc: DocumentModel, name: str, url: str) -> str:
        """Build the introduction section."""
        parts: list[str] = []
        parts.append(
            f"{self._escape(name)} is a software project that addresses "
            f"key challenges in its domain."
        )

        paras = [
            b.text for b in doc.all_blocks
            if isinstance(b, ParagraphBlock) and len(b.text) > 40
        ]
        if paras:
            parts.append(self._escape(paras[0]))

        if url:
            parts.append(
                f"The source code is publicly available at \\url{{{url}}}."
            )

        return "\n\n".join(parts)

    def _build_tech_section(self) -> str:
        """Build a technology stack overview from KG."""
        if not self.kg:
            return ""

        items: list[str] = []
        for e in self.kg.entities:
            if e.entity_type in (
                EntityType.TECHNOLOGY, EntityType.FRAMEWORK,
                EntityType.LANGUAGE, EntityType.DATABASE,
                EntityType.CLOUD_SERVICE,
            ):
                items.append(
                    f"\\textbf{{{self._escape(e.name)}}} "
                    f"({e.entity_type.value.replace('_', ' ')})"
                )

        if not items:
            return ""

        parts = [
            "The project leverages the following technologies:",
            r"\begin{itemize}",
        ]
        for item in items[:15]:
            parts.append(f"  \\item {item}")
        parts.append(r"\end{itemize}")

        return "\n".join(parts)

    def _build_architecture_section(self) -> str:
        """Build architecture description from KG relations."""
        if not self.kg:
            return ""

        parts: list[str] = []
        parts.append(
            "The system architecture is characterized by the following "
            "component relationships:"
        )
        parts.append("")
        parts.append(r"\begin{itemize}")

        seen: set[str] = set()
        for r in self.kg.relations[:20]:
            src = self.kg.get_entity(r.source_id)
            tgt = self.kg.get_entity(r.target_id)
            if src and tgt:
                desc = (
                    f"{self._escape(src.name)} "
                    f"{r.relation_type.value.replace('_', ' ')}s "
                    f"{self._escape(tgt.name)}"
                )
                if desc not in seen:
                    seen.add(desc)
                    parts.append(f"  \\item {desc}")

        parts.append(r"\end{itemize}")
        return "\n".join(parts)

    def _build_conclusion(self, doc: DocumentModel, name: str) -> str:
        """Build a conclusion paragraph."""
        n_blocks = len(doc.all_blocks)
        n_sections = len(doc.sections)
        n_entities = len(self.kg.entities) if self.kg else 0

        return (
            f"This paper has presented an automated analysis of "
            f"{self._escape(name)}, extracting {n_entities} semantic entities "
            f"across {n_sections} sections and {n_blocks} content blocks. "
            f"The combination of structural parsing and knowledge graph "
            f"extraction provides a comprehensive overview of the project's "
            f"architecture, capabilities, and technology stack."
        )

    # ------------------------------------------------------------------
    # LaTeX escaping
    # ------------------------------------------------------------------

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

    @staticmethod
    def _latex_lang(lang: str) -> str:
        """Map common language names to LaTeX listings language names."""
        lang_map = {
            "python": "Python",
            "py": "Python",
            "javascript": "JavaScript",
            "js": "JavaScript",
            "typescript": "JavaScript",
            "ts": "JavaScript",
            "java": "Java",
            "c": "C",
            "cpp": "C++",
            "c++": "C++",
            "csharp": "C",
            "cs": "C",
            "go": "Go",
            "rust": "C",  # closest built-in
            "ruby": "Ruby",
            "bash": "bash",
            "sh": "bash",
            "shell": "bash",
            "sql": "SQL",
            "html": "HTML",
            "css": "HTML",
            "xml": "XML",
            "json": "JavaScript",
            "yaml": "bash",
            "yml": "bash",
            "r": "R",
            "matlab": "Matlab",
        }
        return lang_map.get(lang.lower(), "")
