"""
AI bridge for PHG (DGE++ parser -> normalized PHG -> vis.exe / web).
"""

from __future__ import annotations

import importlib
import importlib.util
import json
import os
import subprocess
import sys
import tempfile
from typing import Any, Dict, Iterable, List, Optional

from .visphg import vis as _vis, image as _image
from .web_three import write_three_view_html

DEFAULT_AI_ROOT = r"C:\Users\18858\Documents\_AILab\DGE++\PHG"
_AI_MODULE = None
_AI_MODULE_ROOT = None


def resolve_ai_root(ai_root: Optional[str] = None) -> str:
    root = ai_root or os.environ.get("PHG_AI_ROOT") or DEFAULT_AI_ROOT
    if not os.path.isdir(root):
        raise FileNotFoundError(
            f"AI PHG root not found: {root}. "
            "Set PHG_AI_ROOT or pass ai_root explicitly."
        )
    return root


def _run_ai_cli(args: List[str], ai_root: Optional[str] = None, timeout: int = 120) -> subprocess.CompletedProcess:
    root = resolve_ai_root(ai_root)
    cmd = [sys.executable, "-m", "phg"] + args
    return subprocess.run(
        cmd,
        cwd=root,
        capture_output=True,
        text=True,
        timeout=timeout,
        check=False,
    )


def _load_ai_module(ai_root: Optional[str] = None):
    global _AI_MODULE, _AI_MODULE_ROOT
    root = resolve_ai_root(ai_root)
    if _AI_MODULE is not None and _AI_MODULE_ROOT == root:
        return _AI_MODULE

    pkg_dir = os.path.join(root, "phg")
    init_path = os.path.join(pkg_dir, "__init__.py")
    if not os.path.isfile(init_path):
        raise FileNotFoundError(f"AI PHG package not found: {init_path}")

    spec = importlib.util.spec_from_file_location(
        "phg_ai",
        init_path,
        submodule_search_locations=[pkg_dir],
    )
    if spec is None or spec.loader is None:
        raise ImportError("Failed to load AI PHG package")

    module = importlib.util.module_from_spec(spec)
    sys.modules["phg_ai"] = module
    spec.loader.exec_module(module)
    _AI_MODULE = module
    _AI_MODULE_ROOT = root
    return module


def _write_temp_phg(script: str) -> str:
    fd, path = tempfile.mkstemp(suffix=".phg")
    os.close(fd)
    with open(path, "w", encoding="utf-8") as f:
        f.write(script)
    return path


def dump_scene(script: str, ai_root: Optional[str] = None) -> Dict[str, Any]:
    """
    Use AI PHG (DGE++) to parse script and dump scene JSON.
    """
    phg_path = _write_temp_phg(script)
    fd, json_path = tempfile.mkstemp(suffix=".json")
    os.close(fd)
    try:
        try:
            ai = _load_ai_module(ai_root)
            engine_mod = importlib.import_module("phg_ai.engine")
            engine = engine_mod.Engine()
            engine.run(script)
            if "setup" not in engine.calls:
                engine.setup()
            if "draw" not in engine.calls:
                engine.draw()
            return engine._scene_to_dict(engine.scene.root)
        except Exception:
            proc = _run_ai_cli(["dump", phg_path, "--out", json_path], ai_root=ai_root)
            if proc.returncode != 0:
                raise RuntimeError(proc.stderr.strip() or proc.stdout.strip() or "AI dump failed")
            with open(json_path, "r", encoding="utf-8") as f:
                return json.load(f)
    finally:
        for p in (phg_path, json_path):
            try:
                os.remove(p)
            except OSError:
                pass


def _format_value(value: Any) -> str:
    if value is None:
        return ""
    if isinstance(value, bool):
        return "1" if value else "0"
    if isinstance(value, (int, float)):
        return str(value)
    if isinstance(value, (list, tuple)):
        return ",".join(str(v) for v in value)
    return str(value)


def _emit_props(props: Dict[str, Any], indent: str) -> List[str]:
    lines = []
    for key, value in props.items():
        val = _format_value(value)
        if val == "":
            lines.append(f"{indent}{key};")
        else:
            lines.append(f"{indent}{key}:{val};")
    return lines


def _emit_node(node: Dict[str, Any], indent: str = "    ") -> List[str]:
    name = node.get("name", "node")
    props = node.get("props", {}) or {}
    children = node.get("children", []) or []

    lines = [f"{indent}{name}{{"]
    lines.extend(_emit_props(props, indent + "    "))
    for child in children:
        lines.extend(_emit_node(child, indent + "    "))
    lines.append(f"{indent}}}")
    return lines


def scene_to_phg(scene: Dict[str, Any], include_setup: bool = True) -> str:
    """
    Convert AI scene JSON (dump) to normalized PHG script.
    """
    root_props = scene.get("props", {}) or {}
    root_children = scene.get("children", []) or []

    lines = ["{"]
    if root_props:
        # Preserve root-level props by creating a synthetic node.
        root_node = {
            "name": scene.get("name", "root"),
            "props": root_props,
            "children": root_children,
        }
        lines.extend(_emit_node(root_node, indent="    "))
    else:
        for child in root_children:
            lines.extend(_emit_node(child, indent="    "))
    lines.append("}")
    if include_setup:
        lines.append("setup_draw();")
    return "\n".join(lines)


def compile_ai_phg(script: str, ai_root: Optional[str] = None, include_setup: bool = True) -> str:
    scene = dump_scene(script, ai_root=ai_root)
    return scene_to_phg(scene, include_setup=include_setup)


def vis_ai(script: str, ai_root: Optional[str] = None) -> None:
    phg_script = compile_ai_phg(script, ai_root=ai_root, include_setup=True)
    _vis(phg_script)


def image_ai(script: str, filename: str = "shot.png", ai_root: Optional[str] = None) -> None:
    phg_script = compile_ai_phg(script, ai_root=ai_root, include_setup=True)
    _image(phg_script, filename=filename)


def export_ai_obj(script: str, out_obj: str, ai_root: Optional[str] = None) -> str:
    try:
        ai = _load_ai_module(ai_root)
        engine_mod = importlib.import_module("phg_ai.engine")
        engine = engine_mod.Engine()
        engine.run(script)
        if "setup" not in engine.calls:
            engine.setup()
        if "draw" not in engine.calls:
            engine.draw()
        engine.export_obj(out_obj)
    finally:
        pass
    return out_obj


def web_view_ai(
    script: str,
    out_html: str,
    out_obj: Optional[str] = None,
    ai_root: Optional[str] = None,
    title: str = "PHG Web Viewer",
) -> str:
    if out_obj is None:
        base, _ = os.path.splitext(out_html)
        out_obj = base + ".obj"
    export_ai_obj(script, out_obj, ai_root=ai_root)
    write_three_view_html(out_obj, out_html, title=title)
    return out_html
