#!/usr/bin/env python3
"""
THE TESTAMENT — Build System

Compiles the complete novel into distributable formats:
  1. Combined markdown (testament-complete.md)
  2. EPUB (the-testament.epub)
  3. PDF via xelatex (the-testament.pdf)

Usage:
  python3 build/build.py              # all formats
  python3 build/build.py --md         # markdown only
  python3 build/build.py --epub       # EPUB only
  python3 build/build.py --pdf        # PDF (xelatex or weasyprint fallback)
  python3 build/build.py --html       # standalone HTML book

Requirements:
  - pandoc (brew install pandoc)
  - xelatex (install MacTeX or TinyTeX) — for PDF
"""

import os
import re
import sys
import subprocess
import shutil
from pathlib import Path

# Paths relative to repo root
REPO = Path(__file__).resolve().parent.parent
BUILD = REPO / "build"
OUTPUT_DIR = BUILD / "output"
CHAPTERS_DIR = REPO / "chapters"
FRONT_MATTER = BUILD / "frontmatter.md"
BACK_MATTER = BUILD / "backmatter.md"
METADATA = BUILD / "metadata.yaml"
STYLESHEET = REPO / "book-style.css"
COVER_IMAGE = REPO / "cover" / "cover-art.jpg"

# Output files
OUT_MD = REPO / "testament-complete.md"
OUT_EPUB = OUTPUT_DIR / "the-testament.epub"
OUT_PDF = OUTPUT_DIR / "the-testament.pdf"

# Part divisions
PARTS = {
    1: ("THE BRIDGE", "The bridge. The cabin. The first men. Where despair meets purpose."),
    6: ("THE TOWER", "The tower grows. Timmy awakens. Stone breaks. The house appears."),
    11: ("THE LIGHT", "Thomas at the door. The network. The story breaks. The green light."),
}


def get_chapter_num(filename):
    m = re.search(r'chapter-(\d+)', filename)
    return int(m.group(1)) if m else 0


def compile_markdown():
    """Combine front matter + 18 chapters + back matter into one markdown file."""
    parts = []

    # Front matter
    parts.append(FRONT_MATTER.read_text())

    # Chapters
    chapters = sorted(
        [(get_chapter_num(f), f) for f in os.listdir(CHAPTERS_DIR)
         if f.startswith("chapter-") and f.endswith(".md")]
    )

    current_part = 0
    for num, filename in chapters:
        if num in PARTS:
            current_part += 1
            name, desc = PARTS[num]
            parts.append(f"\n---\n\n# PART {current_part}: {name}\n\n*{desc}*\n\n---\n")

        content = (CHAPTERS_DIR / filename).read_text()
        lines = content.split('\n')
        body = '\n'.join(lines[1:]).strip()
        parts.append(f"\n{lines[0]}\n\n{body}\n")

    # Back matter
    parts.append("\n---\n")
    parts.append(BACK_MATTER.read_text())

    compiled = '\n'.join(parts)
    OUT_MD.write_text(compiled)

    words = len(compiled.split())
    size = OUT_MD.stat().st_size
    print(f"  Markdown: {OUT_MD.name}  ({words:,} words, {size:,} bytes)")
    return True


def compile_epub():
    """Generate EPUB via pandoc."""
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

    cmd = [
        "pandoc", str(OUT_MD),
        "-o", str(OUT_EPUB),
        "--toc", "--toc-depth=2",
        "--metadata", "title=The Testament",
        "--metadata", "author=Alexander Whitestone with Timmy",
        "--metadata", "lang=en",
        "--metadata", "date=2026",
    ]

    if METADATA.exists():
        cmd.extend(["--metadata-file", str(METADATA)])
    if STYLESHEET.exists():
        cmd.extend(["--css", str(STYLESHEET)])
    if COVER_IMAGE.exists():
        cmd.extend(["--epub-cover-image", str(COVER_IMAGE)])

    r = subprocess.run(cmd, capture_output=True, text=True)
    if r.returncode == 0:
        size = OUT_EPUB.stat().st_size
        print(f"  EPUB:     {OUT_EPUB.name}  ({size:,} bytes, {size/1024:.0f} KB)")
        return True
    else:
        print(f"  EPUB FAILED: {r.stderr[:200]}")
        return False


def compile_pdf():
    """Generate PDF via pandoc + xelatex, or weasyprint fallback."""
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

    # Try xelatex first (best quality)
    if shutil.which("xelatex"):
        cmd = [
            "pandoc", str(OUT_MD),
            "-o", str(OUT_PDF),
            "--pdf-engine=xelatex",
            "--toc", "--toc-depth=2",
        ]
        if METADATA.exists():
            cmd.extend(["--metadata-file", str(METADATA)])
        print("  Building PDF (xelatex)... this takes a minute")
        r = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
        if r.returncode == 0:
            size = OUT_PDF.stat().st_size
            print(f"  PDF:      {OUT_PDF.name}  ({size:,} bytes, {size/(1024*1024):.1f} MB)")
            return True
        else:
            print(f"  PDF (xelatex) FAILED: {r.stderr[:300]}")

    # Fallback: pandoc HTML + weasyprint
    try:
        import weasyprint
        html_tmp = OUTPUT_DIR / "the-testament-print.html"
        cmd = [
            "pandoc", str(OUT_MD),
            "-o", str(html_tmp),
            "--standalone",
            "--toc", "--toc-depth=2",
            "--css", str(STYLESHEET),
            "--metadata", "title=The Testament",
        ]
        if METADATA.exists():
            cmd.extend(["--metadata-file", str(METADATA)])
        print("  Building PDF (weasyprint)...")
        r = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
        if r.returncode != 0:
            print(f"  PDF (pandoc HTML) FAILED: {r.stderr[:200]}")
            return False

        doc = weasyprint.HTML(filename=str(html_tmp))
        doc.write_pdf(str(OUT_PDF))
        html_tmp.unlink(missing_ok=True)
        size = OUT_PDF.stat().st_size
        print(f"  PDF:      {OUT_PDF.name}  ({size:,} bytes, {size/(1024*1024):.1f} MB)")
        return True
    except Exception as e:
        print(f"  PDF FAILED: {e}")

    # Fallback 2: reportlab (pure Python, no system deps)
    return _compile_pdf_reportlab()


def _compile_pdf_reportlab():
    """Generate PDF using reportlab — pure Python, no external dependencies."""
    try:
        from reportlab.lib.pagesizes import letter
        from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
        from reportlab.lib.units import inch
        from reportlab.lib.colors import HexColor
        from reportlab.platypus import (
            SimpleDocTemplate, Paragraph, Spacer, PageBreak,
            Image as RLImage, Table, TableStyle, HRFlowable
        )
        from reportlab.lib.enums import TA_CENTER, TA_JUSTIFY, TA_LEFT
        import io
        try:
            import qrcode
            HAS_QRCODE = True
        except ImportError:
            HAS_QRCODE = False
    except ImportError:
        print("  PDF SKIPPED: no PDF engine found (install MacTeX, fix weasyprint, or pip install reportlab)")
        return False

    print("  Building PDF (reportlab)...")
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

    styles = getSampleStyleSheet()
    styles.add(ParagraphStyle(
        'BookTitle', parent=styles['Title'],
        fontSize=28, leading=34, spaceAfter=20,
        textColor=HexColor('#1a1a2e'), alignment=TA_CENTER
    ))
    styles.add(ParagraphStyle(
        'BookAuthor', parent=styles['Normal'],
        fontSize=14, leading=18, spaceAfter=40,
        textColor=HexColor('#555555'), alignment=TA_CENTER
    ))
    styles.add(ParagraphStyle(
        'PartTitle', parent=styles['Heading1'],
        fontSize=22, leading=28, spaceBefore=40, spaceAfter=12,
        textColor=HexColor('#16213e'), alignment=TA_CENTER
    ))
    styles.add(ParagraphStyle(
        'PartDesc', parent=styles['Normal'],
        fontSize=11, leading=15, spaceAfter=30,
        textColor=HexColor('#666666'), alignment=TA_CENTER, italics=1
    ))
    styles.add(ParagraphStyle(
        'ChapterTitle', parent=styles['Heading1'],
        fontSize=20, leading=26, spaceBefore=30, spaceAfter=16,
        textColor=HexColor('#1a1a2e'), alignment=TA_CENTER
    ))
    styles.add(ParagraphStyle(
        'BodyText2', parent=styles['Normal'],
        fontSize=11, leading=16, spaceAfter=8,
        alignment=TA_JUSTIFY, firstLineIndent=24
    ))
    styles.add(ParagraphStyle(
        'BodyNoIndent', parent=styles['Normal'],
        fontSize=11, leading=16, spaceAfter=8,
        alignment=TA_JUSTIFY
    ))
    styles.add(ParagraphStyle(
        'SectionBreak', parent=styles['Normal'],
        fontSize=14, leading=18, spaceBefore=20, spaceAfter=20,
        alignment=TA_CENTER, textColor=HexColor('#999999')
    ))
    styles.add(ParagraphStyle(
        'Footer', parent=styles['Normal'],
        fontSize=9, textColor=HexColor('#888888'), alignment=TA_CENTER
    ))

    def _make_qr(data, size=80):
        """Generate a QR code image as a reportlab Image flowable."""
        if not HAS_QRCODE:
            return None
        qr = qrcode.QRCode(version=1, box_size=4, border=1)
        qr.add_data(data)
        qr.make(fit=True)
        img = qr.make_image(fill_color="black", back_color="white")
        buf = io.BytesIO()
        img.save(buf, format='PNG')
        buf.seek(0)
        return RLImage(buf, width=size, height=size)

    def _parse_md_to_flowables(md_text):
        """Convert markdown text to reportlab flowables."""
        flowables = []
        lines = md_text.split('\n')
        i = 0
        while i < len(lines):
            line = lines[i]
            stripped = line.strip()

            # Horizontal rule
            if stripped in ('---', '***', '___'):
                flowables.append(HRFlowable(width="60%", thickness=1,
                    spaceAfter=20, spaceBefore=20, color=HexColor('#cccccc')))
                i += 1
                continue

            # H1
            if stripped.startswith('# ') and not stripped.startswith('## '):
                text = stripped[2:].strip()
                # Check if it's a part divider or chapter
                if text.upper().startswith('PART '):
                    flowables.append(PageBreak())
                    flowables.append(Paragraph(text, styles['PartTitle']))
                elif text.upper().startswith('CHAPTER '):
                    flowables.append(PageBreak())
                    flowables.append(Paragraph(text, styles['ChapterTitle']))
                elif 'THE TESTAMENT' in text.upper():
                    flowables.append(Spacer(1, 2*inch))
                    flowables.append(Paragraph(text, styles['BookTitle']))
                else:
                    flowables.append(Spacer(1, 0.3*inch))
                    flowables.append(Paragraph(text, styles['Heading1']))
                i += 1
                continue

            # H2
            if stripped.startswith('## '):
                text = stripped[3:].strip()
                flowables.append(Spacer(1, 0.2*inch))
                flowables.append(Paragraph(text, styles['Heading2']))
                i += 1
                continue

            # Italic-only line (part descriptions, epigraphs)
            if stripped.startswith('*') and stripped.endswith('*') and len(stripped) > 2:
                text = stripped.strip('*').strip()
                flowables.append(Paragraph(f'<i>{_escape(text)}</i>', styles['PartDesc']))
                i += 1
                continue

            # Empty line
            if not stripped:
                i += 1
                continue

            # Bold text: **text** -> <b>text</b>
            # Italic text: *text* -> <i>text</i>
            # Regular paragraph
            para_text = _md_inline_to_rml(stripped)
            flowables.append(Paragraph(para_text, styles['BodyText2']))
            i += 1

        return flowables

    def _escape(text):
        """Escape XML special characters."""
        return (text.replace('&', '&amp;')
                    .replace('<', '&lt;')
                    .replace('>', '&gt;'))

    def _md_inline_to_rml(text):
        """Convert inline markdown to reportlab XML markup."""
        text = _escape(text)
        # Bold: **text**
        text = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', text)
        # Italic: *text*
        text = re.sub(r'\*(.+?)\*', r'<i>\1</i>', text)
        return text

    # Build the PDF
    doc = SimpleDocTemplate(
        str(OUT_PDF),
        pagesize=letter,
        leftMargin=1.0*inch,
        rightMargin=1.0*inch,
        topMargin=0.8*inch,
        bottomMargin=0.8*inch,
        title="The Testament",
        author="Alexander Whitestone with Timmy",
    )

    story = []

    # Read the compiled markdown
    if not OUT_MD.exists():
        compile_markdown()
    md_text = OUT_MD.read_text()

    # Parse into flowables
    story = _parse_md_to_flowables(md_text)

    # Add QR codes page at the end
    qr_links = {
        "Read Online": "https://timmyfoundation.org/the-testament",
        "The Door (Game)": "https://timmyfoundation.org/the-door",
        "Soundtrack": "https://timmyfoundation.org/soundtrack",
        "Source Code": "https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament",
    }

    if HAS_QRCODE:
        story.append(PageBreak())
        story.append(Paragraph("Experience More", styles['PartTitle']))
        story.append(Spacer(1, 0.3*inch))

        qr_items = []
        for label, url in qr_links.items():
            qr_img = _make_qr(url, size=72)
            if qr_img:
                cell_content = []
                cell_content.append(qr_img)
                cell_content.append(Spacer(1, 6))
                cell_content.append(Paragraph(f'<b>{label}</b>', styles['Footer']))
                qr_items.append(cell_content)

        if qr_items:
            # Arrange QR codes in a 2x2 table
            rows = []
            for i in range(0, len(qr_items), 2):
                row = qr_items[i:i+2]
                if len(row) == 1:
                    row.append('')
                rows.append(row)
            qr_table = Table(rows, colWidths=[2.5*inch, 2.5*inch])
            qr_table.setStyle(TableStyle([
                ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
                ('VALIGN', (0, 0), (-1, -1), 'TOP'),
                ('TOPPADDING', (0, 0), (-1, -1), 12),
                ('BOTTOMPADDING', (0, 0), (-1, -1), 12),
            ]))
            story.append(qr_table)

    # Build
    try:
        doc.build(story)
        size = OUT_PDF.stat().st_size
        print(f"  PDF:      {OUT_PDF.name}  ({size:,} bytes, {size/(1024*1024):.1f} MB)")
        return True
    except Exception as e:
        print(f"  PDF (reportlab) FAILED: {e}")
        return False


def compile_html():
    """Generate a standalone HTML book for the web reader."""
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    OUT_HTML = REPO / "testament.html"

    cmd = [
        "pandoc", str(OUT_MD),
        "-o", str(OUT_HTML),
        "--standalone",
        "--toc", "--toc-depth=2",
        "--css", "book-style.css",
        "--metadata", "title=The Testament",
        "--variable", "pagetitle=The Testament",
    ]
    if METADATA.exists():
        cmd.extend(["--metadata-file", str(METADATA)])

    r = subprocess.run(cmd, capture_output=True, text=True)
    if r.returncode == 0:
        size = OUT_HTML.stat().st_size
        print(f"  HTML:     {OUT_HTML.name}  ({size:,} bytes, {size/1024:.0f} KB)")
        return True
    else:
        print(f"  HTML FAILED: {r.stderr[:200]}")
        return False


def main():
    args = sys.argv[1:]
    do_all = not any(a.startswith("--") and a != "--check" for a in args)
    do_md = "--md" in args or do_all
    do_epub = "--epub" in args or do_all
    do_pdf = "--pdf" in args or do_all
    do_html = "--html" in args or do_all

    print("=" * 50)
    print("  THE TESTAMENT — Build System")
    print("=" * 50)

    # Step 1: Always compile markdown first
    if do_md or do_epub or do_pdf or do_html:
        compile_markdown()

    # Step 2: EPUB
    if do_epub:
        compile_epub()

    # Step 3: PDF
    if do_pdf:
        compile_pdf()

    # Step 4: Standalone HTML
    if do_html:
        compile_html()

    print("=" * 50)
    print("  Build complete.")
    print("=" * 50)

    OUT_HTML = REPO / "testament.html"
    for f in [OUT_MD, OUT_EPUB, OUT_PDF, OUT_HTML]:
        if f.exists():
            print(f"  ✓ {f.relative_to(REPO)}")


if __name__ == "__main__":
    main()
