#!/usr/bin/env bash
# sanitize-ref-css.sh — copy captured reference CSS into impl/src/ref-css safely.
#
# Usage:
#   bash scripts/extract/sanitize-ref-css.sh <ref-dir> <impl-root> [copy-to]
#
# Copies <ref-dir>/css/*.css to <impl-root>/<copy-to> (default: src/ref-css),
# preserving filenames, while repairing known browser-tolerated but Vite-strict
# tokens observed in production bundles. Writes a provenance report to
# <ref-dir>/ref-css-sanitize-report.json.
set -euo pipefail

REF_DIR="${1:-}"
IMPL_ROOT="${2:-}"
COPY_TO="${3:-src/ref-css}"

if [ -z "$REF_DIR" ] || [ -z "$IMPL_ROOT" ]; then
  echo "Usage: $0 <ref-dir> <impl-root> [copy-to]" >&2
  exit 2
fi
if [ ! -d "$REF_DIR" ]; then
  echo "sanitize-ref-css: ref-dir not found: $REF_DIR" >&2
  exit 2
fi
if [ ! -d "$REF_DIR/css" ]; then
  echo "sanitize-ref-css: no ref css dir: $REF_DIR/css" >&2
  exit 2
fi

python3 - "$REF_DIR" "$IMPL_ROOT" "$COPY_TO" <<'PY'
from __future__ import annotations

import hashlib
import json
import re
import sys
from pathlib import Path

ref_dir = Path(sys.argv[1]).resolve()
impl_root = Path(sys.argv[2]).resolve()
copy_to = sys.argv[3].strip("/") or "src/ref-css"
src_dir = ref_dir / "css"
dst_dir = impl_root / copy_to
dst_dir.mkdir(parents=True, exist_ok=True)

URLISH_VAR_RE = re.compile(
    r"(?P<prefix>(?:^|[;{]\s*)"
    r"(?:background(?:-image)?|border-image(?:-source)?|list-style-image|"
    r"mask(?:-image)?|-webkit-mask(?:-image)?)\s*:\s*)"
    r"var\(\s*(?P<quote>['\"])(?P<url>[^'\"]+\."
    r"(?:png|jpe?g|gif|webp|svg|ico|avif)(?:\?[^'\"]*)?)"
    r"(?P=quote)\s*\)",
    re.IGNORECASE,
)
HIDDEN_ROOT_SELECTOR_RE = re.compile(
    r"(^|[,\s])(?P<selector>(?:html|body)(?:[.#:[\]\w=\"'-]+)?|#(?:root|__next|app))\s*(?:,|\{)",
    re.IGNORECASE,
)
HIDDEN_DECL_RE = re.compile(
    r"(?:opacity\s*:\s*(?:0(?:\.0+)?)(?:\s*!important)?\b|"
    r"visibility\s*:\s*hidden(?:\s*!important)?\b|"
    r"display\s*:\s*none(?:\s*!important)?\b)",
    re.IGNORECASE,
)


def sha256(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


def sanitize_css(text: str) -> tuple[str, list[dict[str, str]]]:
    replacements: list[dict[str, str]] = []

    def replace(match: re.Match[str]) -> str:
        original = match.group(0)
        url = match.group("url")
        replacement = f"{match.group('prefix')}url(\"{url}\")"
        replacements.append(
            {
                "kind": "urlish-var-to-url",
                "original": original.strip(),
                "replacement": replacement.strip(),
            }
        )
        return replacement

    return URLISH_VAR_RE.sub(replace, text), replacements


# Production CSS can be multi-megabyte and minified. The previous whole-file
# rule regex ([^{}]+\{[^{}]*\}) backtracks O(n^2) over long brace-free tails
# (e.g. sourcemap data URIs) and spun for minutes on a 2.6MB bundle. Mirror
# generation-plan.sh: inspect only the prefix where first-paint root locks
# normally live, and walk braces directly instead of regex-matching rules.
RUNTIME_UNLOCK_SCAN_CHARS = 256 * 1024


def runtime_unlock_hints(text: str, rel_source: str) -> list[dict[str, str]]:
    hints: list[dict[str, str]] = []
    window = text[:RUNTIME_UNLOCK_SCAN_CHARS]
    search_from = 0
    while len(hints) < 20:
        open_idx = window.find("{", search_from)
        if open_idx == -1:
            break
        close_idx = window.find("}", open_idx + 1)
        if close_idx == -1:
            break
        selector_start = max(window.rfind("}", 0, open_idx), window.rfind("{", 0, open_idx)) + 1
        selectors = window[selector_start:open_idx].strip()
        body = window[open_idx + 1 : close_idx]
        search_from = open_idx + 1
        if not HIDDEN_ROOT_SELECTOR_RE.search(selectors + "{"):
            continue
        decl = HIDDEN_DECL_RE.search(body)
        if not decl:
            continue
        hints.append(
            {
                "source": rel_source,
                "selector": selectors[:160],
                "declaration": decl.group(0)[:120],
            }
        )
    return hints


files = []
all_runtime_unlock_hints: list[dict[str, str]] = []
for src in sorted(src_dir.glob("*.css")):
    if not src.is_file():
        continue
    raw = src.read_bytes()
    text = raw.decode("utf-8", errors="replace")
    sanitized, replacements = sanitize_css(text)
    rel_source = str(src.relative_to(ref_dir))
    hints = runtime_unlock_hints(sanitized, rel_source)
    all_runtime_unlock_hints.extend(hints)
    dst = dst_dir / src.name
    data = sanitized.encode("utf-8")
    dst.write_bytes(data)
    files.append(
        {
            "source": str(src.relative_to(ref_dir)),
            "destination": str(dst.relative_to(impl_root)),
            "sourceSha256": sha256(raw),
            "destinationSha256": sha256(data),
            "bytes": len(data),
            "changed": bool(replacements),
            "replacementCount": len(replacements),
            "replacements": replacements[:20],
            "requiresRuntimeUnlock": bool(hints),
            "runtimeUnlockHints": hints[:20],
        }
    )

report = {
    "schemaVersion": 1,
    "tool": "scripts/extract/sanitize-ref-css.sh",
    "copyTo": copy_to,
    "fileCount": len(files),
    "changedFileCount": sum(1 for f in files if f["changed"]),
    "replacementCount": sum(int(f["replacementCount"]) for f in files),
    "requiresRuntimeUnlock": bool(all_runtime_unlock_hints),
    "runtimeUnlockHints": all_runtime_unlock_hints[:50],
    "files": files,
}
(ref_dir / "ref-css-sanitize-report.json").write_text(
    json.dumps(report, indent=2, ensure_ascii=False) + "\n",
    encoding="utf-8",
)
print(json.dumps(report, ensure_ascii=False))
PY
