"""
Memory Inspector Server — built-in dashboard for openmemo-openclaw.

Provides a lightweight HTTP server (stdlib only, zero extra dependencies) that
serves the Inspector Dashboard UI and all required API endpoints.

Auto-starts when features["inspector"] is True in AdapterConfig.
Default port: 8765 → http://localhost:8765/inspector
"""

import json
import logging
import threading
import time
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs

from openmemo_openclaw.inspector_html import INSPECTOR_HTML
from openmemo_openclaw.version_check import get_local_versions

logger = logging.getLogger("openmemo_openclaw")


class InspectorServer:
    def __init__(self, adapter, port: int = 8765):
        self._adapter = adapter
        self._port = port
        self._server = None
        self._thread = None
        self._running = False

    def start(self):
        if self._running:
            return
        handler_class = _make_handler(self._adapter)
        try:
            self._server = HTTPServer(("127.0.0.1", self._port), handler_class)
            self._thread = threading.Thread(
                target=self._server.serve_forever,
                daemon=True,
                name="openmemo-inspector",
            )
            self._thread.start()
            self._running = True
            logger.info(
                "[openmemo] Memory Inspector started → http://localhost:%d/inspector",
                self._port,
            )
        except OSError as e:
            logger.warning("[openmemo] Inspector failed to start on port %d: %s", self._port, e)

    def stop(self):
        if self._server and self._running:
            self._server.shutdown()
            self._server.server_close()
            if self._thread:
                self._thread.join(timeout=5)
            self._running = False
            logger.info("[openmemo] Inspector stopped")

    @property
    def url(self) -> str:
        return f"http://localhost:{self._port}/inspector"

    @property
    def running(self) -> bool:
        return self._running


def _make_handler(adapter):
    class InspectorHandler(BaseHTTPRequestHandler):
        def log_message(self, format, *args):
            pass

        def _send_json(self, data, status=200):
            body = json.dumps(data).encode("utf-8")
            self.send_response(status)
            self.send_header("Content-Type", "application/json; charset=utf-8")
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)

        def _send_html(self, html):
            body = html.encode("utf-8")
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)

        def do_OPTIONS(self):
            self.send_response(204)
            self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
            self.send_header("Access-Control-Allow-Headers", "Content-Type")
            self.end_headers()

        def do_GET(self):
            parsed = urlparse(self.path)
            path = parsed.path.rstrip("/")
            qs = parse_qs(parsed.query)

            if path == "/inspector" or path == "":
                self._serve_inspector()
            # ── Policy API ─────────────────────────────────────────────
            elif path == "/policy/current":
                self._serve_policy_current()
            elif path == "/activation/status":
                self._serve_activation_status()
            elif path == "/activation/first-task":
                self._serve_activation_first_task()
            # ── Event Bridge API ───────────────────────────────────────
            elif path == "/events/recent":
                n = int(qs.get("n", ["50"])[0])
                et = qs.get("type", [""])[0]
                self._serve_events_recent(n, et or None)
            elif path == "/events/stats":
                self._serve_events_stats()
            # ── UI Controller API ──────────────────────────────────────
            elif path == "/ui/current":
                self._serve_ui_current()
            elif path == "/ui/cards":
                n = int(qs.get("n", ["20"])[0])
                self._serve_ui_cards(n)
            # ── V3 Inspector API endpoints ─────────────────────────────
            elif path == "/inspector/metrics":
                self._serve_v3_metrics()
            elif path == "/inspector/task-trace":
                task_id = qs.get("task_id", [""])[0]
                self._serve_v3_task_trace(task_id)
            elif path == "/inspector/timeline":
                task_id = qs.get("task_id", [""])[0]
                self._serve_v3_timeline(task_id)
            elif path == "/inspector/memory-profile":
                memory_id = qs.get("memory_id", [""])[0]
                self._serve_v3_memory_profile(memory_id)
            elif path == "/inspector/top-memories":
                n = int(qs.get("n", ["10"])[0])
                self._serve_v3_top_memories(n)
            elif path == "/inspector/storage-check":
                self._serve_storage_check()
            # ── Legacy API endpoints (backward-compat) ─────────────────
            elif path == "/api/inspector/checklist":
                self._serve_checklist()
            elif path == "/api/inspector/memory-summary":
                self._serve_memory_summary()
            elif path == "/api/inspector/health":
                self._serve_health()
            elif path == "/api/inspector/recent":
                self._serve_recent()
            elif path == "/api/inspector/search":
                q = qs.get("q", [""])[0]
                self._serve_search(q)
            elif path == "/api/inspector/rules":
                self._serve_rules()
            elif path == "/version":
                self._serve_version()
            else:
                self.send_response(404)
                self.end_headers()

        def do_POST(self):
            parsed = urlparse(self.path)
            path = parsed.path.rstrip("/")

            if path == "/session/scene_override":
                self._handle_scene_override()
            elif path == "/policy/generate":
                self._handle_policy_generate()
            elif path == "/policy/update":
                self._handle_policy_update()
            elif path == "/demo/simulate":
                self._handle_demo_simulate()
            elif path == "/demo/reset":
                self._handle_demo_reset()
            elif path == "/inspector/sync":
                self._handle_storage_sync()
            else:
                self.send_response(404)
                self.end_headers()

        def _serve_inspector(self):
            self._send_html(INSPECTOR_HTML)

        # ── Policy API methods ─────────────────────────────────────────

        def _serve_policy_current(self):
            rs = getattr(adapter, "_rule_store", None)
            pe = getattr(adapter, "_policy_engine", None)
            if rs:
                policy = rs.get_current()
                rule_card = pe.get_rule_card() if pe else {}
                self._send_json({
                    "policy": policy,
                    "rule_card": rule_card,
                })
            else:
                self._send_json({"error": "policy not initialized"}, 503)

        def _handle_policy_generate(self):
            try:
                length = int(self.headers.get("Content-Length", 0))
                body = json.loads(self.rfile.read(length)) if length > 0 else {}
                text = body.get("text", "").strip()
                if not text:
                    self._send_json({"error": "text required"}, 400)
                    return
                from openmemo_openclaw.memory_policy.rule_generator import generate_rules_from_text
                generated = generate_rules_from_text(text)
                self._send_json({"ok": True, "generated_rules": generated})
            except Exception as e:
                self._send_json({"error": str(e)}, 500)

        def _handle_policy_update(self):
            try:
                length = int(self.headers.get("Content-Length", 0))
                body = json.loads(self.rfile.read(length)) if length > 0 else {}
                rs = getattr(adapter, "_rule_store", None)
                if not rs:
                    self._send_json({"error": "policy not initialized"}, 503)
                    return
                updated = rs.update(body)
                self._send_json({"ok": True, "policy": updated})
            except Exception as e:
                self._send_json({"error": str(e)}, 500)

        def _serve_activation_status(self):
            act = getattr(adapter, "_activation_store", None)
            if act:
                self._send_json(act.get_status())
            else:
                self._send_json({"error": "activation not initialized"}, 503)

        def _serve_activation_first_task(self):
            act = getattr(adapter, "_activation_store", None)
            if act:
                self._send_json(act.get_first_task())
            else:
                self._send_json({"error": "activation not initialized"}, 503)

        # ── Event Bridge API methods ───────────────────────────────────

        def _serve_events_recent(self, n: int = 50, event_type=None):
            bridge = getattr(adapter, "_event_bridge", None)
            if bridge:
                events = bridge.get_recent(limit=n, event_type=event_type)
                self._send_json({"events": events, "total": len(events)})
            else:
                self._send_json({"events": [], "total": 0})

        def _serve_events_stats(self):
            bridge = getattr(adapter, "_event_bridge", None)
            if bridge:
                self._send_json(bridge.get_stats())
            else:
                self._send_json({})

        # ── UI Controller API methods ──────────────────────────────────

        def _serve_ui_current(self):
            ui = getattr(adapter, "_ui_controller", None)
            if ui:
                card = ui.get_current_card()
                self._send_json({"card": card, "stats": ui.get_stats()})
            else:
                self._send_json({"card": None, "stats": {}})

        def _serve_ui_cards(self, n: int = 20):
            ui = getattr(adapter, "_ui_controller", None)
            if ui:
                self._send_json({
                    "cards": ui.get_all_cards(limit=n),
                    "stats": ui.get_stats(),
                })
            else:
                self._send_json({"cards": [], "stats": {}})

        # ── Demo Simulation ────────────────────────────────────────────

        def _handle_demo_simulate(self):
            """
            POST /demo/simulate
            Runs a full simulated agent workflow to demonstrate the event flow.

            Simulates:
              Step 1: Install  → Activation Panel + Rule Card
              Step 2: Task 1   → Memory Recall (cold) + Task Complete → Memory Created card
              Step 3: Workflow → Reuse Indicator
              Step 4: Task 2   → Memory Recall (warm) + Task Complete → Optimization Detected card
            """
            import time as _time

            bridge = getattr(adapter, "_event_bridge", None)
            ui = getattr(adapter, "_ui_controller", None)
            act = getattr(adapter, "_activation_store", None)
            pe = getattr(adapter, "_policy_engine", None)

            if not all([bridge, ui, act]):
                self._send_json({"error": "adapter not fully initialized"}, 503)
                return

            steps = []

            # ── Step 1: Install ────────────────────────────────────────
            steps.append({"step": 1, "label": "Install", "events": [], "cards": []})
            bridge.emit("on_task_start", task_id="demo-install",
                        metadata={"action": "install", "simulated": True})
            panel = act.get_activation_panel()
            c1 = ui.show_activation(panel)
            c2 = ui.show_rule_card(pe)
            steps[-1]["events"].append({"type": "on_task_start", "label": "System initialized"})
            steps[-1]["cards"].extend([c1, c2])

            # ── Step 2: First Task ─────────────────────────────────────
            steps.append({"step": 2, "label": "First Task", "events": [], "cards": []})
            t1 = "demo-task-001"
            bridge.emit("on_task_start", task_id=t1,
                        metadata={"query": "Deploy backend service", "simulated": True})
            steps[-1]["events"].append({"type": "on_task_start", "label": "Task started: Deploy backend service"})

            guidance = act.on_task_start(task_id=t1, query="Deploy backend service")
            if guidance:
                cg = ui.show_guided_first_task(task_id=t1, query="Deploy backend service")
                steps[-1]["cards"].append(cg)

            bridge.emit("on_memory_recall", task_id=t1,
                        metadata={"count": 0, "scene": "devops", "note": "cold start"})
            steps[-1]["events"].append({"type": "on_memory_recall", "label": "Memory recall: 0 hits (cold start)"})

            bridge.emit("on_memory_store", task_id=t1,
                        metadata={"memory_type": "workflow", "note": "Docker deploy workflow captured"})
            steps[-1]["events"].append({"type": "on_memory_store", "label": "Stored: Docker deploy workflow"})

            bridge.emit("on_task_complete", task_id=t1,
                        metadata={"summary": "Backend deployed successfully", "status": "success",
                                  "memories_stored": 2, "workflows_reused": 0})
            steps[-1]["events"].append({"type": "on_task_complete", "label": "Task complete: Backend deployed"})

            insight1 = act.on_task_complete(
                summary="Backend deployed successfully",
                memories_stored=2, workflows_reused=0, task_id=t1,
            )
            ci1 = ui.show_insight(insight1)
            steps[-1]["cards"].append(ci1)

            # ── Step 3: Workflow Adopted ───────────────────────────────
            steps.append({"step": 3, "label": "Workflow Reuse", "events": [], "cards": []})
            t2 = "demo-task-002"
            bridge.emit("on_task_start", task_id=t2,
                        metadata={"query": "Deploy backend service again", "simulated": True})
            steps[-1]["events"].append({"type": "on_task_start", "label": "Task started: Deploy backend service again"})

            bridge.emit("on_memory_recall", task_id=t2,
                        metadata={"count": 3, "scene": "devops", "note": "3 memories found"})
            steps[-1]["events"].append({"type": "on_memory_recall", "label": "Memory recall: 3 hits (warm start)"})

            bridge.emit("on_workflow_adopt", task_id=t2,
                        metadata={"workflow_summary": "Docker deploy workflow", "simulated": True})
            act.on_workflow_adopted("Docker deploy workflow", task_id=t2)
            cr = ui.show_reuse_indicator("Docker deploy workflow", task_id=t2)
            steps[-1]["events"].append({"type": "on_workflow_adopt", "label": "Workflow adopted: Docker deploy"})
            steps[-1]["cards"].append(cr)

            # ── Step 4: Second Task Complete ───────────────────────────
            steps.append({"step": 4, "label": "Optimization", "events": [], "cards": []})
            bridge.emit("on_task_complete", task_id=t2,
                        metadata={"summary": "Backend deployed (skipped 2 steps)", "status": "success",
                                  "memories_stored": 1, "workflows_reused": 1})
            steps[-1]["events"].append({"type": "on_task_complete", "label": "Task complete: 2 steps skipped"})

            insight2 = act.on_task_complete(
                summary="Backend deployed again — 2 steps skipped via workflow reuse",
                memories_stored=1, workflows_reused=1, steps_skipped=2, task_id=t2,
            )
            ci2 = ui.show_insight(insight2)
            steps[-1]["events"].append({"type": "on_task_complete", "label": "Optimization detected"})
            steps[-1]["cards"].append(ci2)

            self._send_json({
                "ok": True,
                "steps": steps,
                "total_events": bridge.get_stats(),
                "total_cards": ui.get_stats(),
                "message": "Demo simulation complete. Refresh the Event Flow section to see results.",
            })

        def _handle_demo_reset(self):
            """POST /demo/reset — clears event and UI card history."""
            bridge = getattr(adapter, "_event_bridge", None)
            act = getattr(adapter, "_activation_store", None)
            if bridge:
                bridge.clear()
            self._send_json({"ok": True, "message": "Demo state reset."})

        # ── V3 Inspector API methods ───────────────────────────────────

        def _serve_v3_metrics(self):
            fb = getattr(adapter, "_fb_store", None)
            base = fb.get_metrics() if fb else {}

            total_stored = base.get("memories_stored", 0)
            if total_stored == 0:
                try:
                    results = adapter._client.search_memory(query="", limit=500)
                    total_stored = len(results)
                except Exception:
                    pass

            self._send_json({
                "memories_stored": total_stored,
                "total_memories": total_stored,
                "memories_recalled": base.get("memories_recalled", 0),
                "workflows_reused": base.get("workflows_reused", 0),
                "tasks_avoided": base.get("tasks_avoided", 0),
                "tokens_saved": base.get("tokens_saved", 0),
            })

        def _serve_v3_task_trace(self, task_id: str):
            fb = getattr(adapter, "_fb_store", None)
            if fb and task_id:
                self._send_json(fb.get_task_trace(task_id))
            else:
                self._send_json({"task_id": task_id, "recalled": [], "stored": [], "injected": []})

        def _serve_v3_timeline(self, task_id: str):
            fb = getattr(adapter, "_fb_store", None)
            if fb and task_id:
                self._send_json(fb.get_timeline(task_id))
            else:
                self._send_json({"task_id": task_id, "events": []})

        def _serve_v3_memory_profile(self, memory_id: str):
            fb = getattr(adapter, "_fb_store", None)
            if not memory_id:
                self._send_json({"error": "memory_id required"}, 400)
                return
            if fb:
                profile = fb.get_memory_profile(memory_id)
            else:
                profile = {"memory_id": memory_id, "summary": memory_id[:80],
                           "type": "memory", "quality_score": 0.5, "usage_count": 0}
            try:
                search_results = adapter._client.search_memory(query=memory_id[:60], limit=5)
                for r in search_results:
                    rid = r.get("id") or r.get("memory_id") or ""
                    if rid == memory_id or not profile.get("summary"):
                        profile["summary"] = r.get("summary") or r.get("content", "")[:120] or profile["summary"]
                        break
            except Exception:
                pass
            self._send_json(profile)

        def _serve_v3_top_memories(self, n: int = 10):
            fb = getattr(adapter, "_fb_store", None)
            items = fb.get_top_memories(n) if fb else []
            if not items:
                try:
                    results = adapter._client.search_memory(query="", limit=n * 2)
                    for r in results[:n]:
                        rid = r.get("id") or r.get("memory_id") or ""
                        summary = r.get("summary") or r.get("content", "")[:120]
                        mt = r.get("memory_type") or r.get("type") or "memory"
                        items.append({
                            "memory_id": rid,
                            "summary": summary,
                            "type": mt,
                            "memory_layer": mt,
                            "quality_score": 0.5,
                            "usage_count": 1,
                        })
                except Exception:
                    pass
            self._send_json(items)

        # ── Storage Health API ─────────────────────────────────────────

        def _serve_storage_check(self):
            """
            GET /inspector/storage-check
            Returns storage consistency status between the backend memory store
            and what OpenMemo's FeedbackStore has tracked through the Adapter.
            """
            try:
                if hasattr(adapter, "_check_storage_consistency"):
                    result = adapter._check_storage_consistency()
                else:
                    external_count = 0
                    try:
                        results = adapter._client.search_memory(query="", limit=1000)
                        external_count = len(results) if results else 0
                    except Exception:
                        pass
                    fb = getattr(adapter, "_fb_store", None)
                    tracked_count = fb.get_metrics().get("memories_stored", 0) if fb else 0
                    untracked = max(0, external_count - tracked_count)
                    result = {
                        "external_count": external_count,
                        "tracked_count": tracked_count,
                        "untracked_count": untracked,
                        "mismatch": external_count > 0 and tracked_count == 0,
                        "status": "warning" if (external_count > 0 and tracked_count == 0) else "ok",
                    }
                self._send_json(result)
            except Exception as e:
                self._send_json({"error": str(e)}, 500)

        def _handle_storage_sync(self):
            """
            POST /inspector/sync
            Imports all memories from the backend storage into FeedbackStore tracking,
            so they appear correctly in the Inspector dashboard.

            This is a one-time repair operation for users who wrote memories via direct
            API calls (bypassing the Adapter). After sync, all memories are visible.
            """
            try:
                results = adapter._client.search_memory(query="", limit=1000)
                fb = getattr(adapter, "_fb_store", None)
                bridge = getattr(adapter, "_event_bridge", None)
                synced_count = 0

                if fb and results:
                    for r in results:
                        rid = r.get("id") or r.get("memory_id") or ""
                        content = r.get("summary") or r.get("content") or ""
                        if isinstance(content, str):
                            content = content[:120]
                        else:
                            content = str(content)[:120]
                        mt = r.get("memory_type") or r.get("type") or "memory"
                        if content:
                            fb.on_store(
                                content=content,
                                memory_type=mt,
                                task_id="inspector_sync",
                                memory_id=rid,
                            )
                            synced_count += 1

                    if bridge:
                        bridge.emit(
                            "on_storage_sync",
                            task_id="inspector_sync",
                            metadata={"synced_count": synced_count, "source": "inspector_sync"},
                        )

                self._send_json({
                    "ok": True,
                    "synced_count": synced_count,
                    "message": f"Successfully synced {synced_count} memories into OpenMemo tracking. Refresh the Inspector to see updated counts.",
                })
            except Exception as e:
                self._send_json({"error": str(e)}, 500)

        # ── Legacy API methods ─────────────────────────────────────────

        def _serve_checklist(self):
            checks = []

            try:
                adapter._client.list_scenes()
                checks.append({"name": "Backend Connected", "status": "ok"})
            except Exception:
                checks.append({"name": "Backend Connected", "status": "fail"})

            worker = adapter._async_worker or adapter._sync_worker
            if worker:
                checks.append({"name": "Pipeline Active", "status": "ok"})
                stats = worker.stats if hasattr(worker, "stats") else {}
                if stats.get("failed", 0) == 0:
                    checks.append({"name": "Write Healthy", "status": "ok"})
                else:
                    checks.append({"name": "Write Healthy", "status": "warning"})
            else:
                checks.append({"name": "Pipeline Active", "status": "fail"})
                checks.append({"name": "Write Healthy", "status": "fail"})

            try:
                adapter._client.search_memory(query="health_check_probe", limit=1)
                checks.append({"name": "Recall Working", "status": "ok"})
            except Exception:
                checks.append({"name": "Recall Working", "status": "fail"})

            feat = adapter._config.features
            checks.append({
                "name": "Task Dedup Active",
                "status": "ok" if feat.get("task_precheck", True) else "warning",
            })
            checks.append({
                "name": "Conflict Suppression",
                "status": "ok" if feat.get("suppression", True) else "warning",
            })

            mr = adapter.memory_rules if hasattr(adapter, "memory_rules") else None
            checks.append({
                "name": "Memory Rules",
                "status": "ok" if mr and mr.enabled else "warning",
            })

            overall = "ok"
            if any(c["status"] == "fail" for c in checks):
                overall = "fail"
            elif any(c["status"] == "warning" for c in checks):
                overall = "warning"

            self._send_json({"status": overall, "checks": checks})

        def _serve_memory_summary(self):
            try:
                scenes = adapter._client.list_scenes()
            except Exception:
                scenes = []

            memories = []
            try:
                memories = adapter._client.search_memory(query="", limit=200)
            except Exception:
                try:
                    memories = adapter._client.search_memory(query="*", limit=200)
                except Exception:
                    pass

            type_dist = {}
            scene_dist = {}
            for m in memories:
                mt = m.get("memory_type") or m.get("cell_type") or m.get("type") or "unknown"
                sc = m.get("scene") or "general"
                type_dist[mt] = type_dist.get(mt, 0) + 1
                scene_dist[sc] = scene_dist.get(sc, 0) + 1

            if not scene_dist and scenes:
                for s in scenes:
                    scene_dist[s] = scene_dist.get(s, 0)

            self._send_json({
                "total_memories": len(memories),
                "total_cells": len(memories),
                "total_scenes": len(scenes) or len(scene_dist),
                "type_distribution": type_dist,
                "scene_distribution": scene_dist,
            })

        def _serve_health(self):
            status = "ok"
            try:
                adapter._client.list_scenes()
            except Exception:
                status = "fail"

            worker = adapter._async_worker or adapter._sync_worker
            if worker and hasattr(worker, "stats"):
                wstats = worker.stats
                if wstats.get("failed", 0) > 0:
                    status = "warning" if status == "ok" else status

            total_memories = 0
            total_scenes = 0
            try:
                scenes = adapter._client.list_scenes()
                total_scenes = len(scenes)
                results = adapter._client.search_memory(query="", limit=1)
                total_memories = len(results)
            except Exception:
                if status == "ok":
                    status = "cold_start"

            from openmemo_openclaw import __version__ as adapter_version

            self._send_json({
                "status": status,
                "backend": adapter._config.backend,
                "api_version": "0.6.0",
                "engine_version": f"openmemo-openclaw {adapter_version}",
                "total_memories": total_memories,
                "total_scenes": total_scenes,
            })

        def _serve_recent(self):
            try:
                results = adapter._client.search_memory(query="", limit=20)
                if not results:
                    results = adapter._client.search_memory(query="*", limit=20)
            except Exception:
                results = []

            try:
                from openmemo_openclaw.ranker import rank_memories
                conflict = adapter._config.conflict_policy
                if not adapter._config.features.get("suppression", True):
                    conflict = "none"
                ranked = rank_memories(
                    results,
                    current_scene=adapter._current_scene,
                    conflict_policy=conflict,
                )
                results = ranked
            except Exception:
                pass

            self._send_json({"recent": results})

        def _serve_search(self, query):
            if not query or len(query) < 2:
                self._send_json({"results": []})
                return
            try:
                results = adapter._client.search_memory(query=query, limit=20)
            except Exception:
                results = []

            try:
                from openmemo_openclaw.ranker import rank_memories
                conflict = adapter._config.conflict_policy
                if not adapter._config.features.get("suppression", True):
                    conflict = "none"
                results = rank_memories(
                    results,
                    current_scene=adapter._current_scene,
                    conflict_policy=conflict,
                )
            except Exception:
                pass

            self._send_json({"results": results})

        def _serve_rules(self):
            rules = adapter.memory_rules if hasattr(adapter, "memory_rules") else None
            if rules:
                status = rules.status
                self._send_json({
                    "enabled": status["enabled"],
                    "version": status["version"],
                    "mode": adapter._config.memory_rules_mode,
                    "rule_count": status["rule_count"],
                    "custom_rules": status["custom_rules"],
                    "rules_text": rules.rules_text if status["enabled"] else "",
                })
            else:
                self._send_json({
                    "enabled": False,
                    "version": "0",
                    "mode": "disabled",
                    "rule_count": 0,
                    "custom_rules": 0,
                    "rules_text": "",
                })

        def _serve_version(self):
            versions = get_local_versions()
            from openmemo_openclaw import __version__ as adapter_version
            self._send_json({
                "version": adapter_version,
                "schema_version": 3,
                "latest_core": versions.get("core") or "0.23.1",
                "latest_adapter": versions.get("adapter") or adapter_version,
                "features": {
                    "user_memory": getattr(adapter, "_user_memory", None) is not None,
                    "orchestrator": getattr(adapter, "_orchestrator", None) is not None,
                    "memory_feedback": getattr(adapter, "_fb_store", None) is not None,
                    "inspector_v3": True,
                },
            })

        def _handle_scene_override(self):
            try:
                length = int(self.headers.get("Content-Length", 0))
                body = json.loads(self.rfile.read(length)) if length > 0 else {}
                scene = body.get("scene", "")
                if not scene:
                    self._send_json({"error": "scene required"}, 400)
                    return
                adapter.scene_override(scene)
                self._send_json({"ok": True, "scene": scene})
            except Exception as e:
                self._send_json({"error": str(e)}, 500)

    return InspectorHandler
