#!/usr/bin/env python3
"""Convert a Markdown file to a professionally styled Word document (.docx)."""

import argparse
import os
import re
import shutil
import subprocess
import sys
import tempfile


# --- Colour palette ---
DARK = (0x1A, 0x1A, 0x2E)       # Near-black navy (H1, title)
ACCENT = (0x1B, 0x4D, 0x89)     # Professional blue (H2, H3, links)
BODY = (0x33, 0x33, 0x33)       # Dark grey (body text)
HEADER_BG = "1B4D89"
LIGHT_BG = "F2F6FA"


def check_dependencies():
    """Check that pandoc and python-docx are available. Returns (pandoc_version, docx_version) or exits."""
    pandoc_path = shutil.which("pandoc")
    if not pandoc_path:
        print("Error: pandoc not found. Install with: sudo apt install pandoc", file=sys.stderr)
        sys.exit(1)

    result = subprocess.run([pandoc_path, "--version"], capture_output=True, text=True)
    pandoc_version = result.stdout.split("\n")[0].replace("pandoc ", "")

    try:
        import docx
        docx_version = docx.__version__
    except ImportError:
        print("Error: python-docx not installed. Install with: pip install python-docx", file=sys.stderr)
        sys.exit(1)

    return pandoc_version, docx_version


def extract_title(md_path):
    """Extract the first # heading from a markdown file."""
    with open(md_path, "r") as f:
        for line in f:
            match = re.match(r"^#\s+(.+)$", line.strip())
            if match:
                return match.group(1)
    return os.path.splitext(os.path.basename(md_path))[0]


def create_styled_reference(output_path):
    """Generate a pandoc reference.docx with professional styling applied."""
    from docx import Document
    from docx.shared import Pt, Cm, RGBColor
    from docx.enum.text import WD_ALIGN_PARAGRAPH

    # Extract pandoc's default reference doc
    subprocess.run(
        ["pandoc", "-o", output_path, "--print-default-data-file", "reference.docx"],
        capture_output=True,
        check=True,
    )

    doc = Document(output_path)

    for section in doc.sections:
        section.top_margin = Cm(2.5)
        section.bottom_margin = Cm(2.5)
        section.left_margin = Cm(2.5)
        section.right_margin = Cm(2.5)

    style_defs = {
        "Title": {"size": Pt(28), "bold": True, "color": DARK, "space_after": Pt(6), "alignment": WD_ALIGN_PARAGRAPH.LEFT},
        "Subtitle": {"size": Pt(14), "bold": False, "color": ACCENT, "space_after": Pt(24)},
        "Heading 1": {"size": Pt(22), "bold": True, "color": DARK, "space_before": Pt(36), "space_after": Pt(12)},
        "Heading 2": {"size": Pt(16), "bold": True, "color": ACCENT, "space_before": Pt(24), "space_after": Pt(8)},
        "Heading 3": {"size": Pt(13), "bold": True, "color": ACCENT, "space_before": Pt(18), "space_after": Pt(6)},
        "Normal": {"size": Pt(11), "bold": False, "color": BODY, "space_after": Pt(8), "line_spacing": Pt(16)},
        "Body Text": {"size": Pt(11), "bold": False, "color": BODY, "space_after": Pt(8), "line_spacing": Pt(16)},
        "First Paragraph": {"size": Pt(11), "bold": False, "color": BODY, "space_after": Pt(8), "line_spacing": Pt(16)},
        "Block Text": {"size": Pt(10), "bold": False, "color": BODY},
        "List Paragraph": {"size": Pt(11), "color": BODY},
        "Compact": {"size": Pt(11), "color": BODY},
    }

    for style_name, props in style_defs.items():
        try:
            style = doc.styles[style_name]
        except KeyError:
            continue

        style.font.name = "Calibri"
        if "size" in props:
            style.font.size = props["size"]
        if "bold" in props:
            style.font.bold = props["bold"]
        if "color" in props:
            style.font.color.rgb = RGBColor(*props["color"])

        pf = style.paragraph_format
        for attr in ("space_before", "space_after", "line_spacing"):
            if attr in props:
                setattr(pf, attr, props[attr])
        if "alignment" in props:
            pf.alignment = props["alignment"]

    try:
        hl = doc.styles["Hyperlink"]
        hl.font.color.rgb = RGBColor(*ACCENT)
        hl.font.underline = True
    except KeyError:
        pass

    doc.save(output_path)


def postprocess_docx(docx_path):
    """Apply table styling, page breaks, and font normalization."""
    from docx import Document
    from docx.shared import Pt, RGBColor
    from docx.enum.table import WD_TABLE_ALIGNMENT
    from docx.oxml.ns import nsdecls, qn
    from docx.oxml import parse_xml

    doc = Document(docx_path)

    # Style tables
    for table in doc.tables:
        table.alignment = WD_TABLE_ALIGNMENT.CENTER

        if not table.rows:
            continue

        # Header row
        for cell in table.rows[0].cells:
            shading = parse_xml(f'<w:shd {nsdecls("w")} w:fill="{HEADER_BG}"/>')
            cell._tc.get_or_add_tcPr().append(shading)
            for paragraph in cell.paragraphs:
                for run in paragraph.runs:
                    run.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
                    run.font.bold = True
                    run.font.size = Pt(10)
                    run.font.name = "Calibri"

        # Data rows with alternating shading
        for i, row in enumerate(table.rows[1:], 1):
            for cell in row.cells:
                if i % 2 == 0:
                    shading = parse_xml(f'<w:shd {nsdecls("w")} w:fill="{LIGHT_BG}"/>')
                    cell._tc.get_or_add_tcPr().append(shading)
                for paragraph in cell.paragraphs:
                    for run in paragraph.runs:
                        run.font.size = Pt(10)
                        run.font.name = "Calibri"

        # Table borders
        tbl_pr = table._tbl.tblPr
        if tbl_pr is None:
            tbl_pr = parse_xml(f'<w:tblPr {nsdecls("w")}/>')
            table._tbl.insert(0, tbl_pr)

        existing = tbl_pr.find(qn("w:tblBorders"))
        if existing is not None:
            tbl_pr.remove(existing)

        borders = parse_xml(
            f'<w:tblBorders {nsdecls("w")}>'
            '  <w:top w:val="single" w:sz="4" w:space="0" w:color="CCCCCC"/>'
            '  <w:left w:val="single" w:sz="4" w:space="0" w:color="CCCCCC"/>'
            '  <w:bottom w:val="single" w:sz="4" w:space="0" w:color="CCCCCC"/>'
            '  <w:right w:val="single" w:sz="4" w:space="0" w:color="CCCCCC"/>'
            '  <w:insideH w:val="single" w:sz="4" w:space="0" w:color="CCCCCC"/>'
            '  <w:insideV w:val="single" w:sz="4" w:space="0" w:color="CCCCCC"/>'
            "</w:tblBorders>"
        )
        tbl_pr.append(borders)

    # Page break before "References" heading
    for para in doc.paragraphs:
        if para.text.strip() == "References" and para.style.name.startswith("Heading"):
            pPr = para._p.get_or_add_pPr()
            pPr.append(parse_xml(f'<w:pageBreakBefore {nsdecls("w")}/>'))
            break

    # Ensure all runs have Calibri
    for para in doc.paragraphs:
        for run in para.runs:
            if run.font.name is None:
                run.font.name = "Calibri"

    doc.save(docx_path)


def main():
    parser = argparse.ArgumentParser(
        description="Convert a Markdown file to a professionally styled Word document (.docx).",
        prog="md-to-docx",
    )
    parser.add_argument("input", help="Path to the Markdown file to convert")
    parser.add_argument("-o", "--output", help="Output file path (default: <input>.docx)")
    parser.add_argument("-t", "--title", help="Override document title")
    parser.add_argument("--toc", action="store_true", help="Include a table of contents")
    parser.add_argument("--toc-depth", type=int, default=2, choices=[1, 2, 3], help="TOC depth (default: 2)")
    parser.add_argument("-n", "--dryrun", action="store_true", help="Preview without creating files")
    args = parser.parse_args()

    # Validate input
    if not os.path.isfile(args.input):
        print(f"Error: file not found: {args.input}", file=sys.stderr)
        sys.exit(1)

    input_path = os.path.abspath(args.input)
    if args.output:
        output_path = os.path.abspath(args.output)
    else:
        output_path = os.path.splitext(input_path)[0] + ".docx"

    title = args.title or extract_title(input_path)
    pandoc_version, docx_version = check_dependencies()

    if args.dryrun:
        print(f"[dryrun] Input:       {input_path}")
        print(f"[dryrun] Output:      {output_path}")
        print(f"[dryrun] Title:       {title}")
        toc_str = f"yes (depth: {args.toc_depth})" if args.toc else "no"
        print(f"[dryrun] TOC:         {toc_str}")
        print(f"[dryrun] Dependencies OK: pandoc {pandoc_version}, python-docx {docx_version}")
        print("[dryrun] Would generate styled .docx file")
        return

    # Create styled reference doc in a temp directory
    tmpdir = tempfile.mkdtemp(prefix="md-to-docx-")
    ref_path = os.path.join(tmpdir, "reference.docx")

    try:
        create_styled_reference(ref_path)

        # Build pandoc command
        cmd = [
            "pandoc", input_path,
            "-o", output_path,
            f"--reference-doc={ref_path}",
            "--metadata", f"title={title}",
            "-f", "markdown+pipe_tables+inline_notes",
            "--columns=72",
        ]
        if args.toc:
            cmd.extend(["--toc", f"--toc-depth={args.toc_depth}"])

        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode != 0:
            print(f"Error: pandoc failed:\n{result.stderr}", file=sys.stderr)
            sys.exit(1)

        postprocess_docx(output_path)
        print(output_path)

    finally:
        shutil.rmtree(tmpdir, ignore_errors=True)


if __name__ == "__main__":
    main()
