"""
One-shot generator for PROJECT_SNAPSHOT.md.
Run once, then delete.
"""
from pathlib import Path
import re
import csv
import json
import sys

ROOT = Path(__file__).parent
OUT = ROOT / "PROJECT_SNAPSHOT.md"

EXCLUDE_DIRS = {"__pycache__", ".git", "Demo", "node_modules", ".venv", "venv", ".pytest_cache", ".mypy_cache"}
EXCLUDE_SUFFIXES = {".pyc"}


def is_excluded(p: Path) -> bool:
    parts = set(p.parts)
    if parts & EXCLUDE_DIRS:
        return True
    if p.suffix in EXCLUDE_SUFFIXES:
        return True
    return False


def walk_tree(root: Path):
    entries = []
    for p in sorted(root.rglob("*")):
        try:
            rel = p.relative_to(root)
        except ValueError:
            continue
        if is_excluded(rel):
            continue
        entries.append(rel)
    return entries


def render_tree(entries):
    lines = ["./"]
    for e in entries:
        depth = len(e.parts) - 1
        indent = "  " * depth
        suffix = "/" if (ROOT / e).is_dir() else ""
        lines.append(f"{indent}{e.name}{suffix}")
    return "\n".join(lines)


def first_n_lines(path: Path, n: int) -> str:
    try:
        with path.open(encoding="utf-8", errors="replace") as f:
            out = []
            for i, line in enumerate(f):
                if i >= n:
                    break
                out.append(line.rstrip("\n"))
            return "\n".join(out)
    except Exception as e:
        return f"<read error: {e}>"


def count_lines(path: Path) -> int:
    try:
        with path.open(encoding="utf-8", errors="replace") as f:
            return sum(1 for _ in f)
    except Exception:
        return -1


def py_signatures(path: Path):
    """Return list of (lineno, kind, text) for def/class lines."""
    sigs = []
    try:
        with path.open(encoding="utf-8", errors="replace") as f:
            for i, line in enumerate(f, 1):
                s = line.lstrip()
                if s.startswith("def ") or s.startswith("async def ") or s.startswith("class "):
                    kind = "class" if s.startswith("class ") else "def"
                    sigs.append((i, kind, line.rstrip("\n")))
    except Exception as e:
        sigs.append((0, "error", f"<read error: {e}>"))
    return sigs


def grep_lines(path: Path, patterns):
    """Return (lineno, line) for any line matching any of the regex patterns."""
    pats = [re.compile(p) for p in patterns]
    out = []
    try:
        with path.open(encoding="utf-8", errors="replace") as f:
            for i, line in enumerate(f, 1):
                if any(p.search(line) for p in pats):
                    out.append((i, line.rstrip("\n")))
    except Exception as e:
        out.append((0, f"<read error: {e}>"))
    return out


def csv_head_and_count(path: Path):
    rows = []
    try:
        with path.open(encoding="utf-8", errors="replace", newline="") as f:
            reader = csv.reader(f)
            for i, row in enumerate(reader):
                if i >= 3:
                    break
                rows.append(row)
    except Exception as e:
        return None, -1, f"<read error: {e}>"
    n = count_lines(path)
    return rows, n, None


def json_content(path: Path):
    try:
        text = path.read_text(encoding="utf-8", errors="replace")
        lines = text.split("\n")
        if len(lines) <= 50:
            return text, len(lines), True
        return "\n".join(lines[:30]), len(lines), False
    except Exception as e:
        return f"<read error: {e}>", -1, True


def xlsx_sheets(path: Path):
    try:
        from openpyxl import load_workbook
        wb = load_workbook(path, read_only=True, data_only=True)
        names = wb.sheetnames
        wb.close()
        return names, None
    except Exception as e:
        return [], str(e)


def section(title, level=2):
    return f"\n{'#' * level} {title}\n"


def code_block(text, lang=""):
    return f"```{lang}\n{text}\n```"


def main():
    md = []
    md.append("# Polleo Demand — Project Snapshot")
    md.append("")
    md.append(f"Generated by `_generate_snapshot.py`. Excludes: `Demo/`, `__pycache__`, `.git`, `*.pyc`.")
    md.append("")

    # ---------- 1. File tree ----------
    md.append(section("1. File Tree"))
    entries = walk_tree(ROOT)
    md.append(code_block(render_tree(entries)))

    # ---------- 2. Root .py files ----------
    md.append(section("2. Root `.py` Files — Imports + Signatures"))
    root_py = sorted([p for p in ROOT.glob("*.py") if not is_excluded(p.relative_to(ROOT))])
    for p in root_py:
        rel = p.relative_to(ROOT)
        md.append(section(f"`{rel}`", level=3))
        md.append("**First 5 lines:**")
        md.append(code_block(first_n_lines(p, 5), "python"))
        sigs = py_signatures(p)
        md.append(f"**Signatures ({len(sigs)}):**")
        if sigs:
            sig_text = "\n".join(f"L{ln:>5}  {kind:5}  {txt.strip()}" for ln, kind, txt in sigs)
            md.append(code_block(sig_text))
        else:
            md.append("_(no defs/classes)_")

    # ---------- 3. PromoTool + PromoCalendar ----------
    for sub in ["PromoTool", "PromoCalendar"]:
        sub_dir = ROOT / sub
        if not sub_dir.exists():
            continue
        md.append(section(f"3. `{sub}/` — Imports + Signatures"))
        for p in sorted(sub_dir.glob("*.py")):
            rel = p.relative_to(ROOT)
            md.append(section(f"`{rel}`", level=3))
            md.append("**First 5 lines:**")
            md.append(code_block(first_n_lines(p, 5), "python"))
            sigs = py_signatures(p)
            md.append(f"**Signatures ({len(sigs)}):**")
            if sigs:
                sig_text = "\n".join(f"L{ln:>5}  {kind:5}  {txt.strip()}" for ln, kind, txt in sigs)
                md.append(code_block(sig_text))
            else:
                md.append("_(no defs/classes)_")

    # ---------- 4. CSV files in data/ ----------
    md.append(section("4. CSV Files in `data/`"))
    data_dir = ROOT / "data"
    if data_dir.exists():
        csvs = sorted(data_dir.rglob("*.csv"))
        csvs = [c for c in csvs if not is_excluded(c.relative_to(ROOT))]
        for p in csvs:
            rel = p.relative_to(ROOT)
            rows, n, err = csv_head_and_count(p)
            md.append(section(f"`{rel}`  —  {n} lines", level=3))
            if err:
                md.append(code_block(err))
            else:
                preview = "\n".join(",".join(r) for r in rows)
                md.append(code_block(preview, "csv"))

    # ---------- 5. JSON files in data/ ----------
    md.append(section("5. JSON Files in `data/`"))
    if data_dir.exists():
        jsons = sorted(data_dir.rglob("*.json"))
        jsons = [j for j in jsons if not is_excluded(j.relative_to(ROOT))]
        for p in jsons:
            rel = p.relative_to(ROOT)
            text, n, full = json_content(p)
            label = "full" if full else f"first 30 of {n} lines"
            md.append(section(f"`{rel}`  —  {n} lines ({label})", level=3))
            md.append(code_block(text, "json"))

    # ---------- 6. XLSX files in data/ ----------
    md.append(section("6. XLSX Files in `data/` — Sheet Names"))
    if data_dir.exists():
        xlsxs = sorted(data_dir.rglob("*.xlsx"))
        xlsxs = [x for x in xlsxs if not is_excluded(x.relative_to(ROOT))]
        for p in xlsxs:
            rel = p.relative_to(ROOT)
            names, err = xlsx_sheets(p)
            md.append(section(f"`{rel}`", level=3))
            if err:
                md.append(f"_(error: {err})_")
            else:
                md.append(code_block("\n".join(names) if names else "(no sheets)"))

    # ---------- 7. Full files: constants.py, week_utils.py, CLAUDE.md ----------
    md.append(section("7. Full Contents — Key Files"))
    for name, lang in [("constants.py", "python"), ("week_utils.py", "python"), ("CLAUDE.md", "markdown")]:
        p = ROOT / name
        md.append(section(f"`{name}`", level=3))
        if p.exists():
            try:
                md.append(code_block(p.read_text(encoding="utf-8", errors="replace"), lang))
            except Exception as e:
                md.append(f"_(read error: {e})_")
        else:
            md.append("_(missing)_")

    # ---------- 8. app.py routing ----------
    md.append(section("8. `app.py` — Pages & Routing"))
    app_py = ROOT / "app.py"
    if app_py.exists():
        matches = grep_lines(app_py, [
            r"^\s*def page_",
            r"^\s*def render_",
            r"PAGES\s*=",
            r"module\s*==",
        ])
        md.append(f"**{len(matches)} matching lines:**")
        text = "\n".join(f"L{ln:>5}  {line}" for ln, line in matches)
        md.append(code_block(text))
    else:
        md.append("_(app.py missing)_")

    OUT.write_text("\n".join(md), encoding="utf-8")
    print(f"Wrote {OUT} ({OUT.stat().st_size:,} bytes)")


if __name__ == "__main__":
    main()
