#!/usr/bin/env python3
"""Check design runtime dependencies without mutating the host."""

from __future__ import annotations

import argparse
import json
import os
import shutil
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Iterable, List

TARGET_CHOICES = ("verify", "deck-pdf", "deck-pptx", "video", "all")

def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Check design workflow dependencies and report gaps."
    )
    parser.add_argument(
        "--cwd",
        default=os.getcwd(),
        help="Project directory used for local node module resolution.",
    )
    parser.add_argument(
        "--target",
        choices=TARGET_CHOICES,
        default="all",
        help="Workflow target to evaluate.",
    )
    parser.add_argument(
        "--format",
        choices=("pretty", "json"),
        default="pretty",
        help="Output format.",
    )
    parser.add_argument(
        "--strict",
        action="store_true",
        help="Exit with code 2 when the selected target is not ready.",
    )
    return parser

def run_command(argv: List[str]) -> subprocess.CompletedProcess[str]:
    return subprocess.run(
        argv,
        text=True,
        capture_output=True,
        check=False,
    )

def npm_global_root() -> str | None:
    npm = shutil.which("npm")
    if not npm:
        return None
    result = run_command([npm, "root", "-g"])
    if result.returncode != 0:
        return None
    root = result.stdout.strip()
    return root or None

def search_roots(cwd: Path, skill_root: Path) -> List[str]:
    roots: List[str] = []
    for candidate in (cwd, skill_root, skill_root / "scripts"):
        text = str(candidate)
        if text not in roots:
            roots.append(text)
    global_root = npm_global_root()
    if global_root and global_root not in roots:
        roots.append(global_root)
    return roots

def probe_python_playwright(python_executable: str | None) -> Dict[str, Any]:
    if not python_executable:
        return {
            "module_available": False,
            "browser_available": False,
            "executable_path": None,
        }

    script = """
import json
import os

payload = {
    "module_available": False,
    "browser_available": False,
    "executable_path": None,
}

try:
    from playwright.sync_api import sync_playwright
    payload["module_available"] = True
    with sync_playwright() as p:
        executable_path = getattr(p.chromium, "executable_path", None)
        payload["executable_path"] = executable_path
        payload["browser_available"] = bool(executable_path and os.path.exists(executable_path))
except Exception as exc:
    payload["error"] = str(exc)

print(json.dumps(payload))
"""
    result = run_command([python_executable, "-c", script])
    if result.returncode != 0:
        return {
            "module_available": False,
            "browser_available": False,
            "executable_path": None,
            "error": result.stderr.strip() or result.stdout.strip() or None,
        }
    try:
        return json.loads(result.stdout)
    except json.JSONDecodeError:
        return {
            "module_available": False,
            "browser_available": False,
            "executable_path": None,
            "error": "Failed to parse python Playwright probe output.",
        }

def probe_node_package(
    node_executable: str | None,
    package_name: str,
    roots: List[str],
) -> Dict[str, Any]:
    if not node_executable:
        return {
            "available": False,
            "resolved_path": None,
            "search_roots": roots,
        }

    script = """
const roots = JSON.parse(process.argv[1]);
const packageName = process.argv[2];
let resolved = null;

for (const root of roots) {
  try {
    resolved = require.resolve(packageName, { paths: [root] });
    break;
  } catch (error) {
    // keep searching
  }
}

console.log(JSON.stringify({
  available: !!resolved,
  resolved_path: resolved || null,
}));
"""
    result = run_command([node_executable, "-e", script, json.dumps(roots), package_name])
    if result.returncode != 0:
        return {
            "available": False,
            "resolved_path": None,
            "search_roots": roots,
            "error": result.stderr.strip() or result.stdout.strip() or None,
        }
    try:
        payload = json.loads(result.stdout)
    except json.JSONDecodeError:
        payload = {
            "available": False,
            "resolved_path": None,
            "error": "Failed to parse node package probe output.",
        }
    payload["search_roots"] = roots
    return payload

def probe_node_playwright_browser(
    node_executable: str | None,
    roots: List[str],
) -> Dict[str, Any]:
    if not node_executable:
        return {
            "package_available": False,
            "browser_available": False,
            "resolved_path": None,
            "executable_path": None,
            "search_roots": roots,
        }

    script = """
const fs = require("fs");
const roots = JSON.parse(process.argv[1]);
let resolved = null;

for (const root of roots) {
  try {
    resolved = require.resolve("playwright", { paths: [root] });
    break;
  } catch (error) {
    // keep searching
  }
}

if (!resolved) {
  console.log(JSON.stringify({
    package_available: false,
    browser_available: false,
    resolved_path: null,
    executable_path: null,
  }));
  process.exit(0);
}

try {
  const playwright = require(resolved);
  const executablePath = playwright.chromium.executablePath();
  console.log(JSON.stringify({
    package_available: true,
    browser_available: !!(executablePath && fs.existsSync(executablePath)),
    resolved_path: resolved,
    executable_path: executablePath || null,
  }));
} catch (error) {
  console.log(JSON.stringify({
    package_available: true,
    browser_available: false,
    resolved_path: resolved,
    executable_path: null,
    error: String(error && error.message ? error.message : error),
  }));
}
"""
    result = run_command([node_executable, "-e", script, json.dumps(roots)])
    if result.returncode != 0:
        return {
            "package_available": False,
            "browser_available": False,
            "resolved_path": None,
            "executable_path": None,
            "search_roots": roots,
            "error": result.stderr.strip() or result.stdout.strip() or None,
        }
    try:
        payload = json.loads(result.stdout)
    except json.JSONDecodeError:
        payload = {
            "package_available": False,
            "browser_available": False,
            "resolved_path": None,
            "executable_path": None,
            "error": "Failed to parse node Playwright probe output.",
        }
    payload["search_roots"] = roots
    return payload

def build_target_report(name: str, missing: List[str], install_hint: str, note: str) -> Dict[str, Any]:
    return {
        "ready": not missing,
        "missing": missing,
        "install_hint": install_hint if missing else None,
        "note": note,
    }

def selected_target_names(target: str) -> List[str]:
    if target == "all":
        return ["verify", "deck-pdf", "deck-pptx", "video"]
    return [target]

def render(payload: Dict[str, Any], fmt: str) -> str:
    if fmt == "json":
        return json.dumps(payload, separators=(",", ":"), sort_keys=True)
    return json.dumps(payload, indent=2, sort_keys=True)

def main() -> int:
    args = build_parser().parse_args()
    cwd = Path(args.cwd).expanduser().resolve()
    skill_root = Path(__file__).resolve().parent.parent

    python_executable = sys.executable or shutil.which("python") or shutil.which("python3")
    node_executable = shutil.which("node")
    ffmpeg_executable = shutil.which("ffmpeg")
    roots = search_roots(cwd, skill_root)

    python_playwright = probe_python_playwright(python_executable)
    node_playwright = probe_node_playwright_browser(node_executable, roots)
    node_packages = {
        package_name: probe_node_package(node_executable, package_name, roots)
        for package_name in ("playwright", "pdf-lib", "pptxgenjs", "sharp")
    }

    verify_missing: List[str] = []
    if not python_executable:
        verify_missing.append("python")
    if not python_playwright.get("module_available"):
        verify_missing.append("python:playwright")
    if not python_playwright.get("browser_available"):
        verify_missing.append("python:chromium")

    deck_pdf_missing: List[str] = []
    if not node_executable:
        deck_pdf_missing.append("node")
    for package_name in ("playwright", "pdf-lib"):
        if not node_packages[package_name].get("available"):
            deck_pdf_missing.append(f"node:{package_name}")
    if not node_playwright.get("browser_available"):
        deck_pdf_missing.append("node:chromium")

    deck_pptx_missing: List[str] = []
    if not node_executable:
        deck_pptx_missing.append("node")
    for package_name in ("playwright", "pptxgenjs", "sharp"):
        if not node_packages[package_name].get("available"):
            deck_pptx_missing.append(f"node:{package_name}")
    if not node_playwright.get("browser_available"):
        deck_pptx_missing.append("node:chromium")

    video_missing: List[str] = []
    if not node_executable:
        video_missing.append("node")
    if not node_packages["playwright"].get("available"):
        video_missing.append("node:playwright")
    if not node_playwright.get("browser_available"):
        video_missing.append("node:chromium")
    if not ffmpeg_executable:
        video_missing.append("ffmpeg")

    targets = {
        "verify": build_target_report(
            "verify",
            verify_missing,
            "pip install playwright && playwright install chromium",
            "verify.py uses the current Python interpreter's Playwright runtime.",
        ),
        "deck-pdf": build_target_report(
            "deck-pdf",
            deck_pdf_missing,
            "npm install playwright pdf-lib",
            "export_deck_pdf.mjs resolves node packages from the project or skill search roots.",
        ),
        "deck-pptx": build_target_report(
            "deck-pptx",
            deck_pptx_missing,
            "npm install playwright pptxgenjs sharp",
            "export_deck_pptx.mjs depends on html2pptx.js plus Node-side Playwright.",
        ),
        "video": build_target_report(
            "video",
            video_missing,
            "npm install playwright  (or npm install -g playwright if running render-video.js directly)  and ensure ffmpeg is on PATH",
            "render-video.js needs both Playwright and ffmpeg.",
        ),
    }

    selected = selected_target_names(args.target)
    selected_ready = all(targets[name]["ready"] for name in selected)

    payload: Dict[str, Any] = {
        "format_version": 1,
        "checked_at": datetime.now(timezone.utc).isoformat(),
        "cwd": str(cwd),
        "skill_root": str(skill_root),
        "selected_target": args.target,
        "selected_ready": selected_ready,
        "environment": {
            "python_executable": python_executable,
            "node_executable": node_executable,
            "ffmpeg_executable": ffmpeg_executable,
        },
        "probes": {
            "python_playwright": python_playwright,
            "node_playwright": node_playwright,
            "node_packages": node_packages,
        },
        "targets": targets,
    }

    sys.stdout.write(render(payload, args.format))
    sys.stdout.write("\n")

    if args.strict and not selected_ready:
        return 2
    return 0

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