"""Markdown assembly: combine all pipeline outputs into final document."""

from __future__ import annotations

from pathlib import Path
from typing import Optional, Union

from ..pipeline.analysis import SlideAnalysis
from ..pipeline.text import SlideText
from ..tracking.versions import VersionInfo, ChangeSet
from .diagram_notes import DiagramNoteRef


def assemble(
    title: str,
    frontmatter: str,
    source_display_path: str,
    version_info: VersionInfo,
    slide_texts: dict[int, Union[str, SlideText]],
    slide_analyses: dict[int, SlideAnalysis],
    slide_count: int,
    version_history: list[dict],
    *,
    slide_classifications: dict[int, str] | None = None,
    diagram_note_refs: dict[int, DiagramNoteRef] | None = None,
) -> str:
    """Assemble the complete markdown document.

    Args:
        title: Deck title.
        frontmatter: Pre-generated YAML frontmatter string.
        source_display_path: Source path for display in header.
        version_info: Current version metadata.
        slide_texts: Verbatim text per slide.
        slide_analyses: LLM analysis per slide.
        slide_count: Total number of slides (from images, the authoritative count).
        version_history: Full version history list.
        slide_classifications: Optional page classification map (e.g. diagram, mixed, text).
        diagram_note_refs: Optional diagram note references for transclusion.

    Returns:
        Complete markdown document as string.
    """
    sections = []

    # Frontmatter
    sections.append(frontmatter)
    sections.append("")

    # Title and header
    sections.append(f"# {title}")
    sections.append("")
    sections.append(f"**Source:** `{source_display_path}`  ")
    sections.append(
        f"**Version:** {version_info.version} | "
        f"**Converted:** {version_info.timestamp[:10]}  "
    )
    status_icon = "✓" if not version_info.changes.has_changes or version_info.version == 1 else "△"
    sections.append(f"**Status:** {status_icon} Current")
    sections.append("")

    # Recent changes (if not first version)
    if version_info.version > 1 and version_info.changes.has_changes:
        sections.append(_format_changes(version_info))
        sections.append("")

    sections.append("---")
    sections.append("")

    # Per-slide sections
    classifications = slide_classifications or {}
    note_refs = diagram_note_refs or {}
    for slide_num in range(1, slide_count + 1):
        slide_section = _format_slide(
            slide_num=slide_num,
            text=slide_texts.get(slide_num),
            analysis=slide_analyses.get(slide_num),
            is_modified=slide_num in version_info.changes.modified,
            is_added=slide_num in version_info.changes.added,
            classification=classifications.get(slide_num),
            diagram_note_ref=note_refs.get(slide_num),
        )
        sections.append(slide_section)

    # Version history table
    if version_history:
        sections.append(_format_version_history(version_history))

    return "\n".join(sections)


def _format_changes(version_info: VersionInfo) -> str:
    """Format the 'Recent Changes' section."""
    changes = version_info.changes
    lines = ["## Recent Changes", ""]

    modified = ", ".join(str(s) for s in changes.modified) if changes.modified else "—"
    added = ", ".join(str(s) for s in changes.added) if changes.added else "—"
    removed = ", ".join(str(s) for s in changes.removed) if changes.removed else "—"

    lines.append("| Slides Modified | Slides Added | Slides Removed |")
    lines.append("|-----------------|--------------|----------------|")
    lines.append(f"| {modified} | {added} | {removed} |")
    lines.append("")

    if version_info.note:
        lines.append(f"**Note:** {version_info.note}")

    return "\n".join(lines)


def _format_slide(
    slide_num: int,
    text: Optional[Union[str, SlideText]],
    analysis: Optional[SlideAnalysis],
    is_modified: bool = False,
    is_added: bool = False,
    classification: str | None = None,
    diagram_note_ref: DiagramNoteRef | None = None,
) -> str:
    """Format a single slide section."""
    lines = []

    # Header with modification marker
    header = f"## Slide {slide_num}"
    if is_modified:
        header += " *(modified)*"
    elif is_added:
        header += " *(new)*"
    lines.append(header)
    lines.append("")

    # Image
    lines.append(f"![Slide {slide_num}](slides/slide-{slide_num:03d}.png)")
    lines.append("")

    # Resolve text content from SlideText or plain string
    text_content = text.full_text if isinstance(text, SlideText) else text

    # Verbatim text
    if text_content:
        lines.append("### Text (Verbatim)")
        lines.append("")
        # Blockquote format for visual distinction
        for line in text_content.split("\n"):
            lines.append(f"> {line}" if line.strip() else ">")
        lines.append("")

    # Diagram transclusion for pure diagram pages
    is_pure_diagram = classification == "diagram"
    is_mixed = classification == "mixed"
    is_unsupported_diagram = classification == "unsupported_diagram"

    if (is_pure_diagram or is_unsupported_diagram) and diagram_note_ref:
        # Pure/unsupported diagram: suppress generic Analysis, add transclusion
        _append_diagram_transclusion(lines, diagram_note_ref)
    elif (is_pure_diagram or is_unsupported_diagram) and not diagram_note_ref:
        # B1 fix: unsupported diagram without note ref — show consulting analysis
        _append_consulting_analysis(lines, analysis)
    elif is_mixed:
        # Mixed: keep consulting analysis, then append transclusion
        _append_consulting_analysis(lines, analysis)
        if diagram_note_ref:
            _append_diagram_transclusion(lines, diagram_note_ref)
    else:
        # Normal non-diagram slide
        _append_consulting_analysis(lines, analysis)

    lines.append("---")
    lines.append("")

    return "\n".join(lines)


def _append_consulting_analysis(
    lines: list[str],
    analysis: Optional[SlideAnalysis],
) -> None:
    """Append the standard consulting-slide analysis block."""
    if analysis and analysis.slide_type != "pending":
        lines.append("### Analysis")
        lines.append("")
        lines.append(f"**Slide Type:** {analysis.slide_type}  ")
        lines.append(f"**Framework:** {analysis.framework}  ")
        if analysis.visual_description:
            lines.append(f"**Visual Description:** {analysis.visual_description}  ")
        if analysis.key_data:
            lines.append(f"**Key Data:** {analysis.key_data}  ")
        if analysis.main_insight:
            lines.append(f"**Main Insight:** {analysis.main_insight}")
        lines.append("")

        # Evidence block
        if analysis.evidence:
            lines.append("**Evidence:**")
            for ev in analysis.evidence:
                claim = ev.get("claim", "")
                confidence = ev.get("confidence", "medium")
                quote = ev.get("quote", "")
                element_type = ev.get("element_type", "body")
                pass_num = ev.get("pass", 1)
                pass_label = f", pass {pass_num}" if pass_num > 1 else ""
                verified_label = "" if ev.get("validated", False) else " [unverified]"
                lines.append(
                    f'- **{claim} ({confidence}{pass_label}):** '
                    f'"{quote}" *({element_type})*{verified_label}'
                )
            lines.append("")
    elif analysis and analysis.slide_type == "pending":
        lines.append("### Analysis")
        lines.append("")
        lines.append(f"*[{analysis.visual_description}]*")
        lines.append("")


def _append_diagram_transclusion(
    lines: list[str],
    note_ref: DiagramNoteRef,
) -> None:
    """Append Obsidian transclusion block for a diagram note."""
    if note_ref.has_diagram_section:
        lines.append(f"![[{note_ref.basename}#Diagram]]")
        lines.append("")
    if note_ref.has_components_section:
        lines.append(f"![[{note_ref.basename}#Components]]")
        lines.append("")
    lines.append(f"*Full details: [[{note_ref.basename}]]*")
    lines.append("")


def _format_version_history(history: list[dict], max_display: int = 10) -> str:
    """Format the version history table.

    Args:
        history: Full version history list.
        max_display: Maximum number of versions to show in the table.
            Full history is preserved in version_history.json.

    Note: Change detection uses positional identity (slide number = identity).
    Insertions or deletions mid-deck cause subsequent slides to shift, producing
    inflated "modified" counts. See spec D3 for details and Tier 2+ plans.
    """
    lines = [
        "## Version History",
        "",
    ]

    # Defensive guard: max_display <= 0 treated as "show all" for robustness.
    if max_display <= 0:
        max_display = len(history)

    total = len(history)
    if total > max_display:
        lines.append(f"*Showing last {max_display} of {total} versions.*")
        lines.append("")
        display_history = history[-max_display:]
    else:
        display_history = history

    lines.extend([
        "| Version | Date | Changes | Note |",
        "|---------|------|---------|------|",
    ])

    for v in reversed(display_history):
        changes_parts = []
        ch = v.get("changes", {})
        if ch.get("added"):
            count = len(ch["added"])
            changes_parts.append(
                f"Initial ({count} slides)" if v.get("version") == 1
                else f"{count} added"
            )
        if ch.get("modified"):
            changes_parts.append(f"{len(ch['modified'])} modified")
        if ch.get("removed"):
            changes_parts.append(f"{len(ch['removed'])} removed")

        changes_str = ", ".join(changes_parts) if changes_parts else "No changes"
        note = v.get("note") or "—"
        date = v.get("timestamp", "unknown")[:10]
        version_label = v.get("version", "?")

        lines.append(f"| v{version_label} | {date} | {changes_str} | {note} |")

    lines.append("")
    return "\n".join(lines)
