#!/usr/bin/env python3
"""Build a neutral business-proposal deck from structured JSON data.

Fills ``templates/mockup-html/proposal-base.html`` with course-proposal content.
All sections are optional — absent sections are omitted cleanly (no empty
headers, no broken placeholders). A sidebar nav is generated listing only the
sections that were actually rendered.

CLI:
    python scripts/proposal_builder.py --in data.json --out proposal.html
    python scripts/proposal_builder.py --in data.json --course-id 261470

Design constraints (from docs/plan-proposal-mode.md):
1. Warm-neutral chrome only — palette tokens --cream:#F7F2EA, --sienna:#9A5B3F, etc.
   Never #8C1D40 (maroon) or #FFC627 (gold) as chrome/background.
2. Neutral business-proposal tone — no faculty-review deferential phrases.
3. No AI-attribution badge — the proposal is presenter-authored.
"""

from __future__ import annotations

import argparse
import html
import json
import sys
from pathlib import Path
from typing import Any

PLUGIN_ROOT = Path(__file__).resolve().parents[1]
SCRIPTS_DIR = Path(__file__).resolve().parent
if str(SCRIPTS_DIR) not in sys.path:
    sys.path.insert(0, str(SCRIPTS_DIR))

TEMPLATE_PATH = PLUGIN_ROOT / "templates" / "mockup-html" / "proposal-base.html"


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _e(text: Any) -> str:
    """HTML-escape a value and coerce to str. Guards against XSS in user text."""
    return html.escape(str(text), quote=True)


# ---------------------------------------------------------------------------
# Section renderers — one pure function per section
# Each returns (anchor_id, nav_label, html_fragment) or None when the section
# should be omitted.
# ---------------------------------------------------------------------------

def render_cover(course: dict) -> tuple[str, str, str] | None:
    """Render the cover/header section from ``data["course"]``."""
    if not course:
        return None
    title = _e(course.get("title", ""))
    code = _e(course.get("code", ""))
    term = _e(course.get("term", ""))
    version = _e(course.get("version", ""))
    date = _e(course.get("date", ""))
    about = _e(course.get("about", ""))

    sub_parts = [p for p in [code] if p]
    sub_html = f'<div class="sub">{" — ".join(sub_parts)}</div>' if sub_parts else ""

    meta_items = []
    if term:
        meta_items.append(f"<div><b>Term</b>{term}</div>")
    if version:
        meta_items.append(f"<div><b>Version</b>{version}</div>")
    if date:
        meta_items.append(f"<div><b>Date</b>{date}</div>")
    meta_row = (
        f'<div class="meta-row">{"".join(meta_items)}</div>' if meta_items else ""
    )

    about_html = (
        f'<div class="about-note"><strong>About this proposal.</strong> {about}</div>'
        if about
        else ""
    )

    fragment = f"""
    <header class="cover" id="cover">
      <div class="eyebrow">Online Course Conversion · Proposal</div>
      <h1>{title}</h1>
      {sub_html}
      {meta_row}
      {about_html}
    </header>"""

    return ("cover", title or "Overview", fragment)


def render_parameters(parameters: list[dict]) -> tuple[str, str, str] | None:
    """Render the parameters / locked-constraints section from ``data["parameters"]``."""
    if not parameters:
        return None

    rows_html = ""
    for i, param in enumerate(parameters):
        label = _e(param.get("label", ""))
        value = _e(param.get("value", ""))
        style = ' style="border-bottom:0"' if i == len(parameters) - 1 else ""
        rows_html += f"""
        <div class="lockrow"{style}>
          <span class="label">{label}</span>
          <span>{value}</span>
        </div>"""

    fragment = f"""
    <section id="parameters">
      <div class="sec-label">Parameters</div>
      <h2>Proposal parameters</h2>
      <div class="card">{rows_html}
      </div>
    </section>"""

    return ("parameters", "Parameters", fragment)


def render_at_a_glance(at_a_glance: dict) -> tuple[str, str, str] | None:
    """Render the at-a-glance before/after from ``data["at_a_glance"]``."""
    if not at_a_glance:
        return None

    now_items = at_a_glance.get("now", [])
    next_items = at_a_glance.get("next", [])

    if not now_items and not next_items:
        return None

    def _list(items: list[Any]) -> str:
        return "".join(f"<li>{_e(item)}</li>" for item in items)

    now_html = f"""
      <div class="now">
        <h4>What exists now</h4>
        <ul>{_list(now_items)}</ul>
      </div>""" if now_items else ""

    next_html = f"""
      <div class="next">
        <h4>What we propose</h4>
        <ul>{_list(next_items)}</ul>
      </div>""" if next_items else ""

    fragment = f"""
    <section id="at-a-glance">
      <div class="sec-label">Section 1</div>
      <h2>At a glance</h2>
      <div class="ba">
        {now_html}
        {next_html}
      </div>
    </section>"""

    return ("at-a-glance", "1 · At a glance", fragment)


def render_outcomes(outcomes: list[str]) -> tuple[str, str, str] | None:
    """Render the course outcomes from ``data["outcomes"]``."""
    if not outcomes:
        return None

    items_html = "".join(f"<li>{_e(o)}</li>" for o in outcomes)

    fragment = f"""
    <section id="outcomes">
      <div class="sec-label">Section 2</div>
      <h2>Course outcomes</h2>
      <ol class="outcomes">
        {items_html}
      </ol>
    </section>"""

    return ("outcomes", "2 · Course outcomes", fragment)


def render_assessment(assessment: dict) -> tuple[str, str, str] | None:
    """Render the assessment architecture from ``data["assessment"]``."""
    if not assessment:
        return None

    summary = _e(assessment.get("summary", ""))
    note = _e(assessment.get("note", ""))
    weighting = assessment.get("weighting", [])

    summary_html = f"<p>{summary}</p>" if summary else ""
    note_html = (
        f'<p style="font-size:13px;color:var(--muted)">{note}</p>' if note else ""
    )

    table_html = ""
    if weighting:
        rows = "".join(
            f"<tr><td>{_e(row.get('item', ''))}</td>"
            f'<td class="pct">{_e(row.get("pct", ""))}%</td></tr>'
            for row in weighting
        )
        table_html = f"""
        <table style="max-width:440px">
          <thead><tr><th>Assessment item</th><th style="text-align:right">Weight</th></tr></thead>
          <tbody>{rows}</tbody>
        </table>"""

    fragment = f"""
    <section id="assessment">
      <div class="sec-label">Section 3</div>
      <h2>Assessment architecture</h2>
      {summary_html}
      {table_html}
      {note_html}
    </section>"""

    return ("assessment", "3 · Assessment", fragment)


def render_module_map(module_map: dict) -> tuple[str, str, str] | None:
    """Render the module map table from ``data["module_map"]``."""
    if not module_map:
        return None

    columns = module_map.get("columns", [])
    rows = module_map.get("rows", [])

    if not columns and not rows:
        return None

    thead = "".join(f"<th>{_e(c)}</th>" for c in columns)
    tbody = ""
    for row in rows:
        cells = "".join(f"<td>{_e(cell)}</td>" for cell in row)
        tbody += f"<tr>{cells}</tr>"

    fragment = f"""
    <section id="module-map">
      <div class="sec-label">Section 4</div>
      <h2>Module map</h2>
      <table>
        <thead><tr>{thead}</tr></thead>
        <tbody>{tbody}</tbody>
      </table>
    </section>"""

    return ("module-map", "4 · Module map", fragment)


def render_decision_points(decision_points: list[dict]) -> tuple[str, str, str] | None:
    """Render decision points from ``data["decision_points"]``."""
    if not decision_points:
        return None

    cards_html = ""
    for dp in decision_points:
        title = _e(dp.get("title", ""))
        detail = _e(dp.get("detail", ""))
        recommendation = _e(dp.get("recommendation", ""))
        rec_html = (
            f'<div class="rec">Recommendation: {recommendation}</div>'
            if recommendation
            else ""
        )
        detail_html = f'<div class="detail">{detail}</div>' if detail else ""
        cards_html += f"""
        <div class="decide-card">
          <h4>{title}</h4>
          {detail_html}
          {rec_html}
        </div>"""

    fragment = f"""
    <section id="decision-points">
      <div class="sec-label">Section 5</div>
      <h2>Decision points</h2>
      {cards_html}
    </section>"""

    return ("decision-points", "5 · Decision points", fragment)


def render_timeline(timeline: list[dict]) -> tuple[str, str, str] | None:
    """Render the build timeline from ``data["timeline"]``."""
    if not timeline:
        return None

    items_html = ""
    for entry in timeline:
        when = _e(entry.get("when", ""))
        what = _e(entry.get("what", ""))
        items_html += f"<li><b>{when}</b> — {what}</li>"

    fragment = f"""
    <section id="timeline">
      <div class="sec-label">Section 6</div>
      <h2>Build timeline</h2>
      <ul class="tl">
        {items_html}
      </ul>
    </section>"""

    return ("timeline", "6 · Timeline", fragment)


# ---------------------------------------------------------------------------
# Core builder
# ---------------------------------------------------------------------------

# Ordered list of (data_key, renderer_fn) — governs render order
_SECTION_RENDERERS: list[tuple[str, Any]] = [
    ("parameters", render_parameters),
    ("at_a_glance", render_at_a_glance),
    ("outcomes", render_outcomes),
    ("assessment", render_assessment),
    ("module_map", render_module_map),
    ("decision_points", render_decision_points),
    ("timeline", render_timeline),
]


def build_proposal(data: dict) -> str:
    """Fill proposal-base.html with sections present in *data*.

    Args:
        data: Structured proposal data following the schema in
              ``docs/plan-proposal-mode.md``.  All top-level keys are
              optional — absent sections are omitted cleanly.

    Returns:
        Self-contained HTML string (no external assets).

    Raises:
        FileNotFoundError: if the base template is missing.
    """
    template = TEMPLATE_PATH.read_text(encoding="utf-8")

    course = data.get("course") or {}

    # Render cover separately (not in _SECTION_RENDERERS — it uses the
    # <header> element and must always come first if present)
    cover_result = render_cover(course)
    cover_html = cover_result[2] if cover_result else ""

    # Render all optional sections
    rendered_sections: list[tuple[str, str, str]] = []  # (anchor_id, nav_label, html)
    section_html_map: dict[str, str] = {}

    for key, renderer in _SECTION_RENDERERS:
        value = data.get(key)
        if value is None:
            continue
        result = renderer(value)
        if result is not None:
            anchor_id, nav_label, fragment = result
            rendered_sections.append((anchor_id, nav_label, fragment))
            section_html_map[f"{anchor_id.upper().replace('-', '_')}_SECTION"] = fragment

    # Build sidebar nav — cover link always first if cover is present
    nav_links = ""
    if cover_result:
        nav_links += f'<a href="#cover">{_e(cover_result[1])}</a>\n    '
    for anchor_id, nav_label, _ in rendered_sections:
        nav_links += f'<a href="#{_e(anchor_id)}">{_e(nav_label)}</a>\n    '

    # Page title and nav course title
    page_title = _e(
        f'{course.get("title", "")} — {course.get("code", "")}'.strip(" —")
        or "Course Proposal"
    )
    nav_course_title = _e(
        f'{course.get("code", "")} {course.get("title", "")}'.strip()
        or "Course Proposal"
    )

    # Footer: date + version only — no AI attribution
    footer_parts = []
    if course.get("date"):
        footer_parts.append(_e(course["date"]))
    if course.get("version"):
        footer_parts.append(_e(course["version"]))
    footer_text = " · ".join(footer_parts) if footer_parts else ""

    # Fill template placeholders
    result_html = template
    result_html = result_html.replace("{{PAGE_TITLE}}", page_title)
    result_html = result_html.replace("{{NAV_COURSE_TITLE}}", nav_course_title)
    result_html = result_html.replace("{{SIDEBAR_NAV}}", nav_links)
    result_html = result_html.replace("{{COVER_SECTION}}", cover_html)
    result_html = result_html.replace("{{FOOTER_TEXT}}", footer_text)

    # Fill each section slot — omit (replace with empty string) if absent
    all_slot_keys = [
        "PARAMETERS_SECTION",
        "AT_A_GLANCE_SECTION",
        "OUTCOMES_SECTION",
        "ASSESSMENT_SECTION",
        "MODULE_MAP_SECTION",
        "DECISION_POINTS_SECTION",
        "TIMELINE_SECTION",
    ]
    for slot_key in all_slot_keys:
        placeholder = f"{{{{{slot_key}}}}}"
        result_html = result_html.replace(
            placeholder, section_html_map.get(slot_key, "")
        )

    return result_html


# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------

def main() -> None:
    parser = argparse.ArgumentParser(
        description="Build a neutral business-proposal HTML deck from a JSON data file."
    )
    parser.add_argument("--in", dest="input_path", required=True, help="Path to JSON data file")
    parser.add_argument("--out", dest="output_path", help="Output HTML path (default: proposal.html)")
    parser.add_argument(
        "--course-id",
        help="Canvas course ID — if provided and --out is omitted, "
             "writes to courses/{id}/mockup/proposal.html",
    )
    args = parser.parse_args()

    input_path = Path(args.input_path)
    if not input_path.exists():
        parser.error(f"Input file not found: {input_path}")

    with input_path.open(encoding="utf-8") as fh:
        data = json.load(fh)

    html_output = build_proposal(data)

    # Determine output path
    if args.output_path:
        out_path = Path(args.output_path)
    elif args.course_id:
        out_dir = PLUGIN_ROOT / "courses" / args.course_id / "mockup"
        out_dir.mkdir(parents=True, exist_ok=True)
        course = data.get("course") or {}
        course_title = course.get("title", "proposal").replace(" ", "-").replace("/", "-")
        out_path = out_dir / f"{course_title}-proposal.html"
    else:
        out_path = Path("proposal.html")

    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(html_output, encoding="utf-8")
    print(f"Proposal written to: {out_path}")


if __name__ == "__main__":
    main()
