"""STONE SDF storage + recovery · HTML / JS / CSS language vault.

Twelve birthstone facets map desk language surfaces to durable cells under
current/stone/. Free SPV samples never fold into SDF place. GRIN free=1.
"""
from __future__ import annotations

import hashlib
import json
import shutil
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

ROOT = Path(__file__).resolve().parents[1]
CUR = ROOT / "current"
STONE = CUR / "stone"
SPVS = ROOT / "spvs"
CSS = ROOT / "css"
SHEETS = ROOT.parent / "engine" / "sheets"
VOCAB = ROOT / "STONE-SDF-VOCAB.txt"
MANIFEST = CUR / "manifest.json"
HOSTESS_STONE = (
    ROOT.parent.parent / "AMOURANTHRTX" / "cache" / "fieldstorage" / "brain" / "stone"
)

# month · stone · language surface · recover sources
FACETS: list[dict[str, Any]] = [
    {
        "id": "01",
        "stone": "Garnet",
        "surface": "html_document",
        "lang": "HTML",
        "sources": ["spvs/html5_all.dom.html", "../engine/sheets/essie_menu.html"],
    },
    {
        "id": "02",
        "stone": "Amethyst",
        "surface": "css_cascade",
        "lang": "CSS",
        "sources": ["css/spv.css", "css/shell.css"],
    },
    {
        "id": "03",
        "stone": "Aquamarine",
        "surface": "js_registry",
        "lang": "JS",
        "sources": ["js/commands.js", "js/desktop.js", "spvs/js_all.js"],
    },
    {
        "id": "04",
        "stone": "Diamond",
        "surface": "essie_seats",
        "lang": "ESSIE",
        "sources": ["current/manifest.json", "current"],
    },
    {
        "id": "05",
        "stone": "Emerald",
        "surface": "ezzie_free",
        "lang": "EZZIE",
        "sources": ["spvs/ezzie.js", "spvs/ezzie.dom.html"],
    },
    {
        "id": "06",
        "stone": "Pearl",
        "surface": "phi_scale",
        "lang": "PHI",
        "sources": ["spvs/phi.js", "spvs/phi.dom.html"],
    },
    {
        "id": "07",
        "stone": "Ruby",
        "surface": "thermo_band",
        "lang": "THERMO",
        "sources": ["spvs/thermo.js", "spvs/thermo.dom.html"],
    },
    {
        "id": "08",
        "stone": "Peridot",
        "surface": "protocol_spvs",
        "lang": "SPV",
        "sources": ["spvs/proto_hub.js", "spvs/manifest.json"],
    },
    {
        "id": "09",
        "stone": "Sapphire",
        "surface": "stream_alsa",
        "lang": "MEDIA",
        "sources": ["settings/stream.json", "settings/audio.json"],
    },
    {
        "id": "10",
        "stone": "Opal",
        "surface": "sdf_out_chain",
        "lang": "SDF",
        "sources": ["rfc", "current/chain.json", "current/push_constants.json"],
    },
    {
        "id": "11",
        "stone": "Topaz",
        "surface": "engine_sheet",
        "lang": "SDF",
        "sources": ["../engine/sheets/essie_menu.html", "../engine/STATUS.txt"],
    },
    {
        "id": "12",
        "stone": "Turquoise",
        "surface": "hostess7_truth",
        "lang": "H7",
        "sources": [
            "STONE-SDF-VOCAB.txt",
            "../../AMOURANTHRTX/docs/HOSTESS7_V33.md",
        ],
    },
]


def _ts() -> str:
    return datetime.now(timezone.utc).isoformat()


def ensure_dirs() -> Path:
    STONE.mkdir(parents=True, exist_ok=True)
    HOSTESS_STONE.mkdir(parents=True, exist_ok=True)
    return STONE


def _resolve(rel: str) -> Path:
    # paths relative to webbrowser root unless absolute-ish ../
    p = (ROOT / rel).resolve()
    return p


def _sha256(path: Path) -> str:
    h = hashlib.sha256()
    with path.open("rb") as f:
        for chunk in iter(lambda: f.read(1 << 16), b""):
            h.update(chunk)
    return h.hexdigest()


def _facet_by_key(key: str) -> dict[str, Any] | None:
    k = key.strip().lower()
    for f in FACETS:
        if k in (
            f["id"],
            f["stone"].lower(),
            f["surface"].lower(),
            f["lang"].lower(),
            f["id"].lstrip("0") or "0",
        ):
            return f
    # aliases
    aliases = {
        "html": "01",
        "css": "02",
        "js": "03",
        "essie": "04",
        "ezzie": "05",
        "phi": "06",
        "thermo": "07",
        "proto": "08",
        "stream": "09",
        "alsa": "09",
        "chain": "10",
        "sdf": "10",
        "engine": "11",
        "sheet": "11",
        "hostess": "12",
        "h7": "12",
        "vocab": "12",
    }
    if k in aliases:
        return _facet_by_key(aliases[k])
    return None


def list_facets() -> list[dict[str, Any]]:
    ensure_dirs()
    out: list[dict[str, Any]] = []
    for f in FACETS:
        cell = STONE / f"{f['id']}_{f['stone'].lower()}"
        meta_p = cell / "cell.json"
        meta = {}
        if meta_p.is_file():
            try:
                meta = json.loads(meta_p.read_text(encoding="utf-8"))
            except json.JSONDecodeError:
                meta = {"bad": True}
        size = 0
        if cell.is_dir():
            for p in cell.rglob("*"):
                if p.is_file():
                    size += p.stat().st_size
        out.append(
            {
                **f,
                "cell": str(cell),
                "stored": meta_p.is_file(),
                "bytes": size,
                "stored_at": meta.get("stored_at"),
                "files": meta.get("files", 0),
                "grin": meta.get("grin", 0),
            }
        )
    return out


def store(key: str = "all") -> dict[str, Any]:
    """Snapshot language sources into STONE cells. Free never folds."""
    ensure_dirs()
    targets = FACETS if key in ("all", "*") else [_facet_by_key(key)]
    if not targets or targets[0] is None:
        return {"ok": False, "error": f"unknown facet: {key}"}

    results: list[dict[str, Any]] = []
    for f in targets:
        assert f is not None
        cell = STONE / f"{f['id']}_{f['stone'].lower()}"
        blob = cell / "blob"
        if blob.exists():
            shutil.rmtree(blob)
        blob.mkdir(parents=True, exist_ok=True)
        files_meta: list[dict[str, str]] = []
        for rel in f["sources"]:
            src = _resolve(rel)
            if not src.exists():
                files_meta.append({"rel": rel, "ok": "0", "note": "missing"})
                continue
            if src.is_dir():
                # only shallow copy of known seat files — not full history explosion
                dest = blob / rel.replace("/", "_")
                dest.mkdir(parents=True, exist_ok=True)
                for child in sorted(src.iterdir()):
                    if child.is_file() and child.suffix in (
                        ".json",
                        ".html",
                        ".js",
                        ".css",
                        ".txt",
                        ".md",
                    ):
                        shutil.copy2(child, dest / child.name)
                        files_meta.append(
                            {
                                "rel": f"{rel}/{child.name}",
                                "ok": "1",
                                "sha": _sha256(dest / child.name),
                            }
                        )
                continue
            dest = blob / rel.replace("/", "_")
            dest.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(src, dest)
            files_meta.append({"rel": rel, "ok": "1", "sha": _sha256(dest)})

        ok_n = sum(1 for m in files_meta if m.get("ok") == "1")
        meta = {
            "id": f["id"],
            "stone": f["stone"],
            "surface": f["surface"],
            "lang": f["lang"],
            "stored_at": _ts(),
            "files": ok_n,
            "inventory": files_meta,
            "grin": 1,  # free=1 truth marker
            "law": "SDF place stored · SPV free never folds",
            "vocab": str(VOCAB) if VOCAB.is_file() else None,
        }
        (cell / "cell.json").write_text(json.dumps(meta, indent=2) + "\n", encoding="utf-8")
        # Turquoise mirror into Hostess fieldstorage when present
        if f["id"] == "12" and VOCAB.is_file():
            HOSTESS_STONE.mkdir(parents=True, exist_ok=True)
            shutil.copy2(VOCAB, HOSTESS_STONE / "STONE-SDF-VOCAB.txt")
            (HOSTESS_STONE / "turquoise.json").write_text(
                json.dumps(
                    {
                        "stone": "Turquoise",
                        "stored_at": _ts(),
                        "vocab": "STONE-SDF-VOCAB.txt",
                        "grin": 1,
                    },
                    indent=2,
                )
                + "\n",
                encoding="utf-8",
            )
        results.append(
            {
                "id": f["id"],
                "stone": f["stone"],
                "lang": f["lang"],
                "files": ok_n,
                "cell": str(cell),
            }
        )

    # merge into vault index so single-facet store does not erase 12/12 for Hostess
    prev_cells: list[dict[str, Any]] = []
    idx_path = STONE / "index.json"
    if idx_path.is_file():
        try:
            prev = json.loads(idx_path.read_text(encoding="utf-8"))
            prev_cells = list(prev.get("cells") or [])
        except json.JSONDecodeError:
            prev_cells = []
    by_id = {c["id"]: c for c in prev_cells if isinstance(c, dict) and "id" in c}
    for c in results:
        by_id[c["id"]] = c
    merged = [by_id[f["id"]] for f in FACETS if f["id"] in by_id]
    summary = {
        "ok": True,
        "stored_at": _ts(),
        "count": len(merged),
        "cells": merged,
        "vault": str(STONE),
        "grin": 1,
        "last_batch": len(results),
    }
    idx_path.write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8")
    return summary


def recover(key: str = "all", *, dry_run: bool = False) -> dict[str, Any]:
    """Restore stored blobs back toward live paths (careful · dry_run default for all)."""
    ensure_dirs()
    targets = FACETS if key in ("all", "*") else [_facet_by_key(key)]
    if not targets or targets[0] is None:
        return {"ok": False, "error": f"unknown facet: {key}"}

    # full all recover is dry_run unless single facet explicit
    if key in ("all", "*") and not dry_run:
        dry_run = True  # safety: never mass-overwrite without single key

    actions: list[dict[str, Any]] = []
    for f in targets:
        assert f is not None
        cell = STONE / f"{f['id']}_{f['stone'].lower()}"
        meta_p = cell / "cell.json"
        blob = cell / "blob"
        if not meta_p.is_file():
            actions.append({"stone": f["stone"], "ok": False, "note": "not stored"})
            continue
        meta = json.loads(meta_p.read_text(encoding="utf-8"))
        restored = 0
        for inv in meta.get("inventory", []):
            if inv.get("ok") != "1":
                continue
            rel = inv["rel"]
            # blob name encoding
            if "/" in rel and (blob / rel.replace("/", "_")).is_file():
                src = blob / rel.replace("/", "_")
            elif (blob / rel.replace("/", "_")).is_dir():
                # directory blob — skip auto mass recover of seats unless dry
                actions.append(
                    {
                        "stone": f["stone"],
                        "rel": rel,
                        "note": "dir blob · seat recover via ESSIE wb in",
                        "dry_run": dry_run,
                    }
                )
                continue
            else:
                # try flat
                cand = blob / rel.replace("/", "_")
                if not cand.is_file():
                    continue
                src = cand
            dest = _resolve(rel.split("/")[0] + "/" + "/".join(rel.split("/")[1:]) if "/" in rel else rel)
            # safer dest: map from original rel under ROOT
            dest = (ROOT / rel).resolve() if not rel.startswith("..") else _resolve(rel)
            if dry_run:
                actions.append(
                    {
                        "stone": f["stone"],
                        "rel": rel,
                        "dest": str(dest),
                        "dry_run": True,
                        "sha": inv.get("sha", "")[:12],
                    }
                )
            else:
                dest.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(src, dest)
                restored += 1
                actions.append(
                    {
                        "stone": f["stone"],
                        "rel": rel,
                        "dest": str(dest),
                        "restored": True,
                    }
                )
        actions.append(
            {
                "stone": f["stone"],
                "ok": True,
                "restored": restored,
                "dry_run": dry_run,
                "grin": 1,
            }
        )

    return {
        "ok": True,
        "dry_run": dry_run,
        "actions": actions,
        "hint": "single facet recover writes · all=dry_run · ESSIE seats use wb in",
        "grin": 1,
    }


def verify() -> dict[str, Any]:
    """GRIN check · cells present · hashes readable · free=1 law."""
    ensure_dirs()
    rows = list_facets()
    stored = [r for r in rows if r["stored"]]
    missing = [r for r in rows if not r["stored"]]
    bad_grin = [r for r in stored if r.get("grin") != 1]
    vocab_ok = VOCAB.is_file()
    return {
        "ok": len(bad_grin) == 0 and vocab_ok,
        "stored": len(stored),
        "missing": [m["stone"] for m in missing],
        "bad_grin": [b["stone"] for b in bad_grin],
        "vocab": str(VOCAB) if vocab_ok else None,
        "vault": str(STONE),
        "law": "SDF place · SPV free never folds · GRIN free=1",
        "grin": 1 if not bad_grin else 0,
    }


def print_list() -> None:
    print("=== STONE SDF vault · language cells ===")
    print(f"vault {STONE}")
    for r in list_facets():
        flag = "STORED" if r["stored"] else "empty "
        print(
            f"  {r['id']} {r['stone']:<12} {r['lang']:<7} {flag}  "
            f"{r['bytes']:>8} B  {r.get('stored_at') or '-'}"
        )
    v = verify()
    print(f"verify ok={v['ok']} stored={v['stored']}/12 grin={v['grin']} vocab={bool(v['vocab'])}")
