#!/usr/bin/env python3
"""Render Markdown documentation under docs/ into a static HTML bundle.

The script converts every Markdown file into a standalone HTML document,
mirroring the original folder structure under the output directory. A
`manifest.json` file is also produced to list the generated documents and
surface basic metadata (title, source path, output path).

Usage:
    python scripts/render_docs.py --source docs --output build/docs-site

Dependencies:
    pip install markdown pygments
"""

from __future__ import annotations

import argparse
import json
import logging
import os
import shutil
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Iterable, List

import markdown

# Enable fenced code blocks, tables, and definition lists. These cover the
# Markdown constructs heavily used across the documentation set.
MD_EXTENSIONS = [
    "fenced_code",
    "codehilite",
    "tables",
    "toc",
    "def_list",
    "admonition",
]

HTML_TEMPLATE = """<!DOCTYPE html>
<html lang=\"en\">
<head>
  <meta charset=\"utf-8\" />
  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />
  <title>{title}</title>
  <style>
    :root {{
      color-scheme: light dark;
      font-family: system-ui, -apple-system, Segoe UI, sans-serif;
      line-height: 1.6;
    }}
    body {{
      margin: 2.5rem auto;
      padding: 0 1.5rem;
      max-width: 70ch;
      background: var(--background, #1118270d);
    }}
    pre {{
      overflow: auto;
      padding: 1rem;
      background: #11182714;
      border-radius: 0.5rem;
    }}
    code {{
      font-family: SFMono-Regular, Consolas, 'Liberation Mono', monospace;
      font-size: 0.95em;
    }}
    table {{
      width: 100%;
      border-collapse: collapse;
      margin: 1rem 0;
    }}
    th, td {{
      border: 1px solid #4b5563;
      padding: 0.5rem;
      text-align: left;
    }}
    a {{
      color: #2563eb;
    }}
    footer {{
      margin-top: 3rem;
      font-size: 0.85rem;
      color: #6b7280;
    }}
  </style>
</head>
<body>
  <main>
{body}
  </main>
  <footer>
    <p>Generated on {generated_at} UTC · Source: {source}</p>
  </footer>
</body>
</html>
"""


@dataclass
class DocEntry:
    source: Path
    output: Path
    title: str

    def to_manifest(self) -> dict[str, str]:
        return {
            "source": self.source.as_posix(),
            "output": self.output.as_posix(),
            "title": self.title,
        }


def discover_markdown_files(source_root: Path) -> Iterable[Path]:
    for path in source_root.rglob("*.md"):
        if path.is_file():
            yield path


def read_title(markdown_text: str, fallback: str) -> str:
    for raw_line in markdown_text.splitlines():
        line = raw_line.strip()
        if line.startswith("#"):
            return line.lstrip("#").strip() or fallback
    return fallback


def convert_markdown(path: Path, source_root: Path, output_root: Path) -> DocEntry:
    relative = path.relative_to(source_root)
    output_path = output_root / relative.with_suffix(".html")
    output_path.parent.mkdir(parents=True, exist_ok=True)

    text = path.read_text(encoding="utf-8")
    html_body = markdown.markdown(text, extensions=MD_EXTENSIONS)

    title = read_title(text, fallback=relative.stem.replace("_", " "))
    generated_at = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S")

    output_path.write_text(
        HTML_TEMPLATE.format(
            title=title,
            body=html_body,
            generated_at=generated_at,
            source=relative.as_posix(),
        ),
        encoding="utf-8",
    )

    return DocEntry(source=relative, output=output_path.relative_to(output_root), title=title)


def copy_static_assets(source_root: Path, output_root: Path) -> None:
    for path in source_root.rglob("*"):
        if path.is_dir() or path.suffix.lower() == ".md":
            # Skip Markdown (already rendered separately).
            continue
        relative = path.relative_to(source_root)
        destination = output_root / relative
        destination.parent.mkdir(parents=True, exist_ok=True)
        destination.write_bytes(path.read_bytes())
        logging.info("Copied asset %s", relative)


def write_manifest(entries: Iterable[DocEntry], output_root: Path) -> None:
    manifest_path = output_root / "manifest.json"
    manifest = [entry.to_manifest() for entry in entries]
    manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
    logging.info("Wrote manifest with %d entries", len(manifest))


def write_index(entries: List[DocEntry], output_root: Path) -> None:
    index_path = output_root / "index.html"
    generated_at = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S")

    items = "\n".join(
        f"      <li><a href='{entry.output.as_posix()}'>{entry.title}</a>" f" · <code>{entry.source.as_posix()}</code></li>"
        for entry in sorted(entries, key=lambda e: e.title.lower())
    )

    html = f"""<!DOCTYPE html>
<html lang=\"en\">
<head>
  <meta charset=\"utf-8\" />
  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />
  <title>Stella Ops Documentation Index</title>
  <style>
    body {{
      margin: 2.5rem auto;
      padding: 0 1.5rem;
      max-width: 70ch;
      font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
      line-height: 1.6;
    }}
    h1 {{ font-size: 2.25rem; margin-bottom: 1rem; }}
    ul {{ list-style: none; padding: 0; }}
    li {{ margin-bottom: 0.75rem; }}
    code {{ background: #11182714; padding: 0.2rem 0.35rem; border-radius: 0.35rem; }}
  </style>
</head>
<body>
  <h1>Stella Ops Documentation</h1>
  <p>Generated on {generated_at} UTC</p>
  <ul>
{items}
  </ul>
</body>
</html>
"""
    index_path.write_text(html, encoding="utf-8")
    logging.info("Wrote HTML index with %d entries", len(entries))


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Render documentation bundle")
    parser.add_argument("--source", default="docs", type=Path, help="Directory containing Markdown sources")
    parser.add_argument("--output", default=Path("build/docs-site"), type=Path, help="Directory for rendered output")
    parser.add_argument("--clean", action="store_true", help="Remove the output directory before rendering")
    return parser.parse_args()


def main() -> int:
    logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
    args = parse_args()

    source_root: Path = args.source.resolve()
    output_root: Path = args.output.resolve()

    if not source_root.exists():
        logging.error("Source directory %s does not exist", source_root)
        return os.EX_NOINPUT

    if args.clean and output_root.exists():
        logging.info("Cleaning existing output directory %s", output_root)
        shutil.rmtree(output_root)

    output_root.mkdir(parents=True, exist_ok=True)

    entries: List[DocEntry] = []
    for md_file in discover_markdown_files(source_root):
        entry = convert_markdown(md_file, source_root, output_root)
        entries.append(entry)
        logging.info("Rendered %s -> %s", entry.source, entry.output)

    write_manifest(entries, output_root)
    write_index(entries, output_root)
    copy_static_assets(source_root, output_root)

    logging.info("Documentation bundle available at %s", output_root)
    return os.EX_OK


if __name__ == "__main__":
    raise SystemExit(main())
