# retoor <retoor@molodetz.nl>

import base64
import json
import re
from functools import lru_cache
from pathlib import Path

from devplacepy.config import STATIC_DIR
from devplacepy import docs_api


@lru_cache(maxsize=None)
def _vendor(name: str) -> str:
    return (Path(STATIC_DIR) / "vendor" / name).read_text(encoding="utf-8")


def _prose_markdown(slug: str, ctx: dict) -> str:
    from devplacepy.templating import templates

    try:
        rendered = templates.env.get_template(f"docs/{slug}.html").render(**ctx)
    except Exception:
        return ""
    rendered = re.sub(r"^\s*<div[^>]*>", "", rendered.strip())
    rendered = re.sub(r"</div>\s*$", "", rendered)
    return rendered.strip()


def _params_table(params: list) -> str:
    if not params:
        return ""
    rows = [
        "| Name | In | Type | Required | Description |",
        "|------|----|------|----------|-------------|",
    ]
    for p in params:
        desc = (p.get("description", "") or "").replace("|", "\\|")
        if p["type"] == "enum" and p.get("options"):
            allowed = ", ".join(str(option) for option in p["options"]).replace(
                "|", "\\|"
            )
            desc = f"{desc} Allowed: {allowed}.".strip()
        rows.append(
            f"| `{p['name']}` | {p['location']} | {p['type']} | "
            f"{'yes' if p['required'] else 'no'} | {desc} |"
        )
    return "\n".join(rows)


def _endpoint_markdown(ep: dict) -> str:
    lines = [
        f"### `{ep['method']} {ep['path']}` - {ep['title']}",
        "",
        ep["summary"],
        "",
        f"*Minimal role:* {ep.get('min_role', ep['auth'])}",
    ]
    if ep.get("params"):
        lines += ["", "**Parameters**", "", _params_table(ep["params"])]
    for note in ep.get("notes", []) or []:
        lines += ["", f"> {note}"]
    if ep.get("sample_response") is not None:
        lines += [
            "",
            "**Sample response**",
            "",
            "```json",
            json.dumps(ep["sample_response"], indent=2),
            "```",
        ]
    return "\n".join(lines)


def _group_markdown(group: dict) -> str:
    parts = [(group.get("intro", "") or "").strip(), ""]
    for ep in group.get("endpoints", []) or []:
        parts.append(_endpoint_markdown(ep))
        parts.append("")
    return "\n".join(parts).strip()


def build_markdown(
    is_admin: bool, base: str, username: str = "", api_key: str = ""
) -> str:
    from devplacepy.routers.docs import DOCS_PAGES
    from devplacepy.services.manager import service_manager

    user_ctx = {"role": "Admin"} if is_admin else None
    replacements = {
        "{{ base }}": base,
        "{{ username }}": username or "YOUR_USERNAME",
        "{{ api_key }}": api_key or "YOUR_API_KEY",
    }
    pages = [
        p
        for p in DOCS_PAGES
        if (not p.get("admin") or is_admin) and p["kind"] != "live"
    ]
    slugs = {p["slug"] for p in pages}

    out = [
        "# DevPlace Documentation",
        "",
        f"Complete developer documentation for `{base}`.",
        "",
        "## Contents",
        "",
    ]
    for page in pages:
        out.append(f"- [{page['title']}](#doc-{page['slug']})")
    out += ["", "---", ""]

    for page in pages:
        if page["kind"] == "prose":
            body = _prose_markdown(page["slug"], {"base": base, "user": user_ctx})
        elif page.get("dynamic"):
            group = docs_api.build_services_group(service_manager.describe_all(), base)
            body = _group_markdown(docs_api._substitute(group, replacements))
        else:
            group = docs_api.render_group(page["slug"], base, username, api_key) or {}
            body = _group_markdown(group)
        out += [f'<a id="doc-{page["slug"]}"></a>', body, "", "---", ""]

    text = "\n".join(out).strip() + "\n"
    return re.sub(
        r"\(/docs/([a-z0-9-]+)\.html(?:#[^)]*)?\)",
        lambda m: f"(#doc-{m.group(1)})" if m.group(1) in slugs else m.group(0),
        text,
    )


_LAYOUT_CSS = """
:root { color-scheme: light dark; }
* { box-sizing: border-box; }
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  line-height: 1.65; color: #1f2328; background: #fff; }
.doc-header { position: sticky; top: 0; z-index: 5; background: #0d1117; color: #fff;
  padding: 0.9rem 1.5rem; font-weight: 700; letter-spacing: 0.02em; box-shadow: 0 1px 0 rgba(0,0,0,0.15); }
.doc-header span { opacity: 0.6; font-weight: 400; }
main.doc { max-width: 860px; margin: 0 auto; padding: 2rem 1.5rem 4rem; }
main.doc h1 { font-size: 1.9rem; margin: 2.4rem 0 1rem; padding-bottom: 0.3rem; border-bottom: 2px solid #e1e4e8; }
main.doc h1:first-child { margin-top: 0; }
main.doc h2 { font-size: 1.4rem; margin: 2rem 0 0.8rem; }
main.doc h3 { font-size: 1.08rem; margin: 1.6rem 0 0.5rem; }
main.doc h3 code { background: #eef1f4; padding: 0.1em 0.4em; border-radius: 5px; font-size: 0.95em; }
main.doc a { color: #0969da; text-decoration: none; }
main.doc a:hover { text-decoration: underline; }
main.doc code { font-family: "SF Mono", Monaco, Consolas, monospace; font-size: 0.9em; }
main.doc :not(pre) > code { background: #eef1f4; padding: 0.12em 0.4em; border-radius: 5px; }
main.doc pre { background: #282c34; border-radius: 8px; padding: 1rem; overflow-x: auto; }
main.doc pre code { color: #abb2bf; font-size: 0.85rem; line-height: 1.5; }
main.doc table { border-collapse: collapse; width: 100%; margin: 1rem 0; font-size: 0.92rem; }
main.doc th, main.doc td { border: 1px solid #d0d7de; padding: 0.45rem 0.7rem; text-align: left; }
main.doc th { background: #f6f8fa; }
main.doc blockquote { margin: 0.8rem 0; padding: 0.2rem 1rem; border-left: 4px solid #d0d7de; color: #57606a; }
main.doc hr { border: none; border-top: 1px solid #e1e4e8; margin: 2.5rem 0; }
@media (prefers-color-scheme: dark) {
  body { background: #0d1117; color: #c9d1d9; }
  main.doc h1, main.doc h2 { border-color: #30363d; }
  main.doc :not(pre) > code, main.doc h3 code { background: #21262d; }
  main.doc th { background: #161b22; }
  main.doc th, main.doc td, main.doc hr, main.doc blockquote { border-color: #30363d; }
  main.doc a { color: #58a6ff; }
}
"""


def build_html(markdown: str) -> str:
    marked = _vendor("marked.umd.js")
    hljs = _vendor("highlight.min.js")
    hl_css = _vendor("atom-one-dark.min.css")
    payload = base64.b64encode(markdown.encode("utf-8")).decode("ascii")
    return (
        '<!doctype html>\n<html lang="en">\n<head>\n<meta charset="utf-8">\n'
        '<meta name="viewport" content="width=device-width, initial-scale=1">\n'
        "<title>DevPlace Documentation</title>\n"
        f"<style>{hl_css}</style>\n<style>{_LAYOUT_CSS}</style>\n"
        f"<script>{marked}</script>\n<script>{hljs}</script>\n"
        "</head>\n<body>\n"
        '<header class="doc-header">DevPlace <span>Documentation</span></header>\n'
        '<main id="doc" class="doc"></main>\n'
        f'<script id="docmd" type="application/x-markdown-base64">{payload}</script>\n'
        "<script>\n(function(){\n"
        "  var raw = atob(document.getElementById('docmd').textContent);\n"
        "  var md = decodeURIComponent(escape(raw));\n"
        "  marked.setOptions({ gfm: true, breaks: false });\n"
        "  document.getElementById('doc').innerHTML = marked.parse(md);\n"
        "  document.querySelectorAll('#doc pre code').forEach(function(el){ try { hljs.highlightElement(el); } catch (e) {} });\n"
        "})();\n</script>\n</body>\n</html>\n"
    )
