#!/usr/bin/env python3
"""Offline HTML wrapper for the LLM wiki (REQ-109).

Reads every canonical wiki page through the page contract (prd_tools wiki
list/read) and emits ONE self-contained HTML file: a page list, a rendered
view, and two controls top-right — Copy Markdown (clipboard) and Download .md
(saves the exact source to the browser's download folder). No remote, no
server, no CDN: open the file in any browser.

Usage:
  python scripts/wiki_html_export.py [--repo-root .] [--output PATH]

Default output: .prd_plugin/local/wiki-html/index.html (per-clone runtime,
already gitignored downstream).
"""
from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))

import prd_tools

DEFAULT_REL = Path(".prd_plugin") / "local" / "wiki-html" / "index.html"

_TEMPLATE = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>__TITLE__</title>
<style>
  :root { color-scheme: light dark; }
  * { box-sizing: border-box; }
  body { margin: 0; font: 15px/1.55 system-ui, sans-serif; display: flex; height: 100vh; }
  nav { width: 300px; overflow-y: auto; border-right: 1px solid #8884; padding: 12px; flex-shrink: 0; }
  nav h1 { font-size: 14px; text-transform: uppercase; letter-spacing: .05em; opacity: .7; }
  nav a { display: block; padding: 4px 8px; border-radius: 6px; text-decoration: none; color: inherit; }
  nav a:hover { background: #8882; }
  nav a.active { background: #4a90d922; font-weight: 600; }
  main { flex: 1; overflow-y: auto; position: relative; }
  article { max-width: 860px; margin: 0 auto; padding: 24px 32px 64px; }
  .toolbar { position: sticky; top: 0; display: flex; justify-content: flex-end; gap: 8px;
             padding: 10px 16px; backdrop-filter: blur(6px); background: color-mix(in srgb, Canvas 82%, transparent); z-index: 5; }
  .toolbar button, .toolbar a.btn { font: 13px system-ui, sans-serif; padding: 6px 14px; border-radius: 8px;
             border: 1px solid #8886; background: Canvas; color: inherit; cursor: pointer; text-decoration: none; }
  .toolbar button:hover, .toolbar a.btn:hover { background: #8882; }
  pre { background: #8881; padding: 12px; border-radius: 8px; overflow-x: auto; }
  code { background: #8881; padding: 1px 5px; border-radius: 4px; font-size: .92em; }
  pre code { background: none; padding: 0; }
  table { border-collapse: collapse; width: 100%; margin: 12px 0; }
  th, td { border: 1px solid #8885; padding: 6px 10px; text-align: left; vertical-align: top; }
  th { background: #8881; }
  blockquote { border-left: 3px solid #8886; margin: 8px 0; padding: 2px 14px; opacity: .85; }
  .meta { font-size: 12.5px; opacity: .65; margin-bottom: 8px; }
  .flash { position: fixed; top: 14px; right: 18px; background: #2e7d32; color: #fff;
           padding: 8px 16px; border-radius: 8px; opacity: 0; transition: opacity .25s; z-index: 10; }
  .flash.show { opacity: 1; }
</style>
</head>
<body>
<nav><h1>__TITLE__</h1>__HUB_LINK__<div id="page-list"></div></nav>
<main>
  <div class="toolbar">
    <button id="copy-button" title="Copy the whole page as Markdown">Copy Markdown</button>
    <a id="download-button" class="btn" title="Download this page as a .md file" download>Download .md</a>
  </div>
  <article id="content"></article>
</main>
<div class="flash" id="flash">Copied</div>
<script type="application/json" id="wiki-pages">__PAYLOAD__</script>
<script>
(function () {
  "use strict";
  var data = JSON.parse(document.getElementById("wiki-pages").textContent);
  var pages = data.pages;
  var byPath = {};
  pages.forEach(function (p) { byPath[p.path] = p; });
  var current = null;

  function esc(s) {
    return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  }
  function inline(s) {
    s = esc(s);
    s = s.replace(/`([^`]+)`/g, function (_, c) { return "<code>" + c + "</code>"; });
    s = s.replace(/\\*\\*([^*]+)\\*\\*/g, "<strong>$1</strong>");
    s = s.replace(/(^|[^*])\\*([^*\\s][^*]*)\\*/g, "$1<em>$2</em>");
    s = s.replace(/\\[([^\\]]+)\\]\\(([^)\\s]+)\\)/g, function (_, text, href) {
      if (/^[a-z]+:/.test(href)) return "<a href=\\"" + href + "\\">" + text + "</a>";
      // internal wiki link: resolve relative to the current page's directory
      var base = current ? current.path.split("/").slice(0, -1) : ["wiki"];
      var parts = base.concat(href.split("/"));
      var stack = [];
      parts.forEach(function (part) {
        if (part === "..") stack.pop();
        else if (part !== "." && part) stack.push(part);
      });
      var target = stack.join("/");
      if (byPath[target]) {
        return "<a href=\\"#" + encodeURIComponent(target) + "\\">" + text + "</a>";
      }
      return "<span title=\\"" + esc(href) + "\\">" + text + "</span>";
    });
    return s;
  }
  function render(md) {
    var lines = md.split(/\\r?\\n/), html = [], i = 0;
    while (i < lines.length) {
      var line = lines[i];
      if (/^```/.test(line)) {
        var buf = []; i++;
        while (i < lines.length && !/^```/.test(lines[i])) { buf.push(lines[i]); i++; }
        i++; html.push("<pre><code>" + esc(buf.join("\\n")) + "</code></pre>"); continue;
      }
      var h = line.match(/^(#{1,6})\\s+(.*)$/);
      if (h) { html.push("<h" + h[1].length + ">" + inline(h[2]) + "</h" + h[1].length + ">"); i++; continue; }
      if (/^\\|/.test(line)) {
        var rows = [];
        while (i < lines.length && /^\\|/.test(lines[i])) { rows.push(lines[i]); i++; }
        var table = "<table>";
        rows.forEach(function (row, index) {
          if (/^\\|[\\s:-]+\\|/.test(row.replace(/[^|\\s:-]/g, "x")) && /^[|\\s:-]+$/.test(row)) return;
          var cells = row.replace(/^\\|/, "").replace(/\\|\\s*$/, "").split("|");
          var tag = index === 0 ? "th" : "td";
          table += "<tr>" + cells.map(function (c) { return "<" + tag + ">" + inline(c.trim()) + "</" + tag + ">"; }).join("") + "</tr>";
        });
        html.push(table + "</table>"); continue;
      }
      if (/^\\s*[-*]\\s+/.test(line)) {
        var items = [];
        while (i < lines.length && /^\\s*[-*]\\s+/.test(lines[i])) { items.push(lines[i].replace(/^\\s*[-*]\\s+/, "")); i++; }
        html.push("<ul>" + items.map(function (it) { return "<li>" + inline(it) + "</li>"; }).join("") + "</ul>"); continue;
      }
      if (/^>\\s?/.test(line)) {
        var quote = [];
        while (i < lines.length && /^>\\s?/.test(lines[i])) { quote.push(lines[i].replace(/^>\\s?/, "")); i++; }
        html.push("<blockquote>" + quote.map(inline).join("<br>") + "</blockquote>"); continue;
      }
      if (line.trim() === "") { i++; continue; }
      var para = [];
      while (i < lines.length && lines[i].trim() !== "" && !/^(#|```|\\||>\\s?|\\s*[-*]\\s+)/.test(lines[i])) { para.push(lines[i]); i++; }
      html.push("<p>" + para.map(inline).join(" ") + "</p>");
    }
    return html.join("\\n");
  }

  function show(path) {
    current = byPath[path] || pages[0];
    var meta = [];
    if (current.updated) meta.push("Updated " + current.updated);
    if (current.commit) meta.push("Commit " + current.commit);
    document.getElementById("content").innerHTML =
      (meta.length ? "<div class=\\"meta\\">" + meta.join(" · ") + "</div>" : "") + render(current.markdown);
    var dl = document.getElementById("download-button");
    dl.setAttribute("download", current.filename);
    dl.href = URL.createObjectURL(new Blob([current.markdown], { type: "text/markdown" }));
    Array.prototype.forEach.call(document.querySelectorAll("nav a"), function (a) {
      a.classList.toggle("active", a.getAttribute("data-path") === current.path);
    });
    document.title = (current.title || current.filename) + " — " + data.repo_id;
  }

  var list = document.getElementById("page-list");
  pages.forEach(function (p) {
    var a = document.createElement("a");
    a.textContent = p.title || p.filename;
    a.href = "#" + encodeURIComponent(p.path);
    a.setAttribute("data-path", p.path);
    list.appendChild(a);
  });

  function fromHash() {
    var target = decodeURIComponent((location.hash || "").slice(1));
    var home = byPath["wiki/index.md"] ? "wiki/index.md" : pages[0].path;
    show(byPath[target] ? target : home);
  }
  window.addEventListener("hashchange", fromHash);

  document.getElementById("copy-button").addEventListener("click", function () {
    var md = current.markdown;
    function done() {
      var flash = document.getElementById("flash");
      flash.classList.add("show");
      setTimeout(function () { flash.classList.remove("show"); }, 1200);
    }
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(md).then(done, fallback);
    } else { fallback(); }
    function fallback() {
      var area = document.createElement("textarea");
      area.value = md; document.body.appendChild(area); area.select();
      document.execCommand("copy"); document.body.removeChild(area); done();
    }
  });

  fromHash();
})();
</script>
</body>
</html>
"""


def build_html(root, hub_link=None):
    """The complete standalone viewer for one repo's wiki."""
    listing = prd_tools.run_tool("wiki", str(root), {"action": "list"})
    pages = []
    for page in listing["pages"]:
        pages.append(prd_tools.run_tool(
            "wiki", str(root), {"action": "read", "path": page["path"]}))
    for page in pages:
        page.pop("text", None)
    payload = json.dumps(
        {"repo_id": listing["repo_id"], "contract_version": listing["contract_version"],
         "pages": pages},
        ensure_ascii=False)
    # A literal </script> inside page markdown would terminate the JSON block
    # early in the browser; escape the slash (harmless inside JSON strings).
    payload = payload.replace("</", "<\\/")
    title = f"{listing['repo_id']} wiki"
    hub_html = (f'<a href="{hub_link}" style="display:block;padding:4px 8px;'
                f'opacity:.75">&#8962; All wikis (all-wikis.html)</a>'
                if hub_link else "")
    return (_TEMPLATE
            .replace("__TITLE__", title)
            .replace("__HUB_LINK__", hub_html)
            .replace("__PAYLOAD__", payload))


def export(root, output=None, hub_link=None):
    root = Path(root)
    target = Path(output) if output else root / DEFAULT_REL
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(build_html(root, hub_link=hub_link),
                      encoding="utf-8", newline="\n")
    return str(target)


def discover_repos(workspace_root):
    """Immediate children of the workspace whose wiki follows the LLM-wiki
    convention (wiki/index.md). PRD Plugin installation is NOT required —
    core repos like Fork keep convention wikis without the plugin (REQ-111)."""
    repos = []
    for child in sorted(Path(workspace_root).iterdir()):
        if child.is_dir() and (child / "wiki" / "index.md").is_file():
            repos.append(child)
    return repos


_LOG_HEADING = re.compile(r"^## \[(\d{4}-\d{2}-\d{2})\]\s*(.*)$")


def parse_wiki_log(root):
    """Entries from a repo's wiki/log.md: {date, heading, body, is_lint}.
    Every workspace log shares the '## [YYYY-MM-DD] mode | text' convention
    (REQ-112 grounded survey); repos without a log yield []."""
    log = Path(root) / "wiki" / "log.md"
    if not log.is_file():
        return []
    entries = []
    for line in log.read_text(encoding="utf-8-sig").splitlines():
        match = _LOG_HEADING.match(line)
        if match:
            heading = match.group(2).strip()
            entries.append({"date": match.group(1), "heading": heading,
                            "body": [],
                            "is_lint": heading.startswith("lint |")})
        elif entries and line.strip() and not line.startswith("# "):
            entries[-1]["body"].append(line.rstrip())
    return entries


def _html_escape(text):
    return (text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;"))


_SUPER_TEMPLATE = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>All wikis</title>
<style>
  :root { color-scheme: light dark; }
  body { margin: 0 auto; max-width: 820px; font: 15px/1.55 system-ui, sans-serif; padding: 32px 24px; }
  h1 { font-size: 22px; }
  .tabs { display: flex; gap: 8px; margin: 18px 0; }
  .tabs button { font: 14px system-ui, sans-serif; padding: 7px 16px; border-radius: 8px;
                 border: 1px solid #8886; background: none; color: inherit; cursor: pointer; }
  .tabs button.active { background: #4a90d922; border-color: #4a90d9; font-weight: 600; }
  .repo { display: block; border: 1px solid #8885; border-radius: 10px; padding: 14px 18px;
          margin: 10px 0; text-decoration: none; color: inherit; }
  .repo:hover { background: #8881; }
  .repo b { font-size: 16px; }
  .repo span { display: block; font-size: 13px; opacity: .7; margin-top: 2px; }
  .day > h2 { font-size: 16px; border-bottom: 1px solid #8884; padding-bottom: 4px; }
  .entry { margin: 10px 0 14px; }
  .entry .badge { display: inline-block; font-size: 12px; padding: 1px 9px; border-radius: 99px;
                  background: #4a90d922; border: 1px solid #4a90d955; margin-right: 8px;
                  text-decoration: none; color: inherit; }
  .entry .head { font-weight: 550; }
  .entry .body { font-size: 13px; opacity: .75; margin: 3px 0 0 6px; white-space: pre-wrap; }
  .entry.lint { opacity: .55; }
  body:not(.show-lint) .entry.lint { display: none; }
  .lint-row { font-size: 13px; opacity: .8; margin: 4px 0 14px; }
</style>
</head>
<body>
<h1>All wikis</h1>
<div class="tabs">
  <button id="tab-wikis" class="active">Wikis</button>
  <button id="tab-changelog">Changelog</button>
</div>
<section id="view-wikis">
<p>Every repo in this workspace with a convention knowledge base.
Click a repo to open its wiki index (copy/download buttons top right).</p>
__CARDS__
</section>
<section id="view-changelog" hidden>
<p>Every wiki's log, combined — newest day first. Entries link to the repo's
own wiki log.</p>
<label class="lint-row"><input type="checkbox" id="lint-toggle"> show lint runs</label>
__CHANGELOG__
</section>
<script>
(function () {
  "use strict";
  var tabs = { wikis: document.getElementById("tab-wikis"),
               changelog: document.getElementById("tab-changelog") };
  function select(name) {
    document.getElementById("view-wikis").hidden = name !== "wikis";
    document.getElementById("view-changelog").hidden = name !== "changelog";
    tabs.wikis.classList.toggle("active", name === "wikis");
    tabs.changelog.classList.toggle("active", name === "changelog");
    if (history.replaceState) history.replaceState(null, "", "#" + name);
  }
  tabs.wikis.addEventListener("click", function () { select("wikis"); });
  tabs.changelog.addEventListener("click", function () { select("changelog"); });
  if (location.hash === "#changelog") select("changelog");
  document.getElementById("lint-toggle").addEventListener("change", function () {
    document.body.classList.toggle("show-lint", this.checked);
  });
})();
</script>
</body>
</html>
"""


def _changelog_html(log_entries):
    """Grouped-by-date (newest first) HTML for the combined changelog.
    log_entries: [(repo_name, viewer_uri, entry), ...]"""
    by_date = {}
    for repo_name, viewer_uri, entry in log_entries:
        by_date.setdefault(entry["date"], []).append((repo_name, viewer_uri, entry))
    sections = []
    for date in sorted(by_date, reverse=True):
        rows = []
        for repo_name, viewer_uri, entry in by_date[date]:
            body = _html_escape("\n".join(entry["body"]))
            rows.append(
                f'<div class="entry{" lint" if entry["is_lint"] else ""}">'
                f'<a class="badge" href="{viewer_uri}#wiki%2Flog.md">{_html_escape(repo_name)}</a>'
                f'<span class="head">{_html_escape(entry["heading"])}</span>'
                + (f'<div class="body">{body}</div>' if body else "")
                + "</div>")
        sections.append(f'<div class="day"><h2>{date}</h2>{"".join(rows)}</div>')
    return "\n".join(sections)


def export_all(workspace_root, host_repo, output=None):
    """Generate every repo's viewer plus the workspace super index. The super
    index lands in the host repo's local runtime dir; each generated viewer
    links back to it."""
    host = Path(host_repo)
    target = Path(output) if output else (
        host / ".prd_plugin" / "local" / "wiki-html" / "all-wikis.html")
    target.parent.mkdir(parents=True, exist_ok=True)
    hub_uri = target.resolve().as_uri()

    cards = []
    log_entries = []
    for repo in discover_repos(workspace_root):
        if (repo / ".prd_plugin").is_dir():
            viewer = Path(export(repo, hub_link=hub_uri))
        else:
            # Guest repo (no PRD Plugin, e.g. Fork): never plant plugin dirs
            # in its tree — host its viewer beside the super index.
            viewer = Path(export(repo, output=target.parent / f"{repo.name}.html",
                                 hub_link=hub_uri))
        viewer_uri = viewer.resolve().as_uri()
        count = len(prd_tools.run_tool("wiki", str(repo), {"action": "list"})["pages"])
        cards.append(
            f'<a class="repo" href="{viewer_uri}">'
            f"<b>{repo.name}</b><span>{count} pages</span></a>")
        for entry in parse_wiki_log(repo):
            log_entries.append((repo.name, viewer_uri, entry))
    target.write_text(
        _SUPER_TEMPLATE
        .replace("__CARDS__", "\n".join(cards))
        .replace("__CHANGELOG__", _changelog_html(log_entries)),
        encoding="utf-8", newline="\n")
    return str(target)


def main(argv=None):
    parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    parser.add_argument("--repo-root", default=".")
    parser.add_argument("--output", default=None)
    parser.add_argument("--all", action="store_true",
                        help="Generate every workspace repo's viewer plus the "
                             "all-wikis super index (REQ-110).")
    parser.add_argument("--workspace-root", default=None,
                        help="Workspace to scan with --all (default: the "
                             "repo root's parent directory).")
    args = parser.parse_args(argv)
    if args.all:
        workspace = args.workspace_root or str(Path(args.repo_root).resolve().parent)
        target = export_all(workspace, args.repo_root, args.output)
        print(f"workspace super index written to {target}")
        print("Open it in a browser: click a repo to open its wiki index.")
        return 0
    target = export(args.repo_root, args.output)
    print(f"wiki viewer written to {target}")
    print("Open it in a browser: page list on the left, Copy Markdown / "
          "Download .md top right.")
    return 0


if __name__ == "__main__":
    sys.exit(main())
