"""
OpenMemo REST Server — Memory Core API v1.0

Agent-First HTTP API for memory operations.
Install with: pip install openmemo[server]

Endpoints:
    POST /memory/write       — Write a memory
    POST /memory/search      — Basic search
    POST /memory/recall      — Recall context (kv/narrative/raw)
    GET  /memory/scenes      — List scenes
    POST /memory/governance  — Memory governance operations
    DELETE /memory/{id}      — Delete a memory
    POST /agent/context      — Get context for prompt injection
    GET  /version            — Get version info
"""

import os

try:
    from flask import Flask, request, jsonify, make_response
    from flask_cors import CORS
except ImportError:
    raise ImportError("Install server dependencies: pip install openmemo[server]")

from openmemo.api.sdk import Memory
from openmemo.api.docs import API_DOCS_HTML
from openmemo.api.inspector_html import INSPECTOR_HTML
from openmemo.config import OpenMemoConfig

API_VERSION = "1.0"
ENGINE_VERSION = "0.11.0"
SCHEMA_VERSION = "3"
CORE_VERSION = "0.10.0"
ADAPTER_VERSION = "2.4.0"


def create_app(db_path: str = None, config: OpenMemoConfig = None,
               cloud_store=None) -> Flask:
    app = Flask(__name__)
    CORS(app)

    db = db_path or os.environ.get("OPENMEMO_DB", "openmemo.db")
    from openmemo.storage.sqlite_store import SQLiteStore
    store = SQLiteStore(db_path=db, check_same_thread=False)
    memory = Memory(db_path=db, store=store, config=config,
                    cloud_store=cloud_store)

    @app.route("/")
    def index():
        return jsonify({
            "service": "OpenMemo Memory API",
            "api_version": API_VERSION,
            "engine_version": ENGINE_VERSION,
            "description": "The Memory Infrastructure for AI Agents",
            "status": "running",
            "docs": "/docs",
            "github": "https://github.com/openmemoai/openmemo",
            "endpoints": {
                "write": "POST /memory/write",
                "search": "POST /memory/search",
                "recall": "POST /memory/recall",
                "scenes": "GET /memory/scenes",
                "governance": "POST /memory/governance",
                "delete": "DELETE /memory/{id}",
                "graph": "GET /memory/graph?memory_id=X",
                "edges": "GET/POST /memory/edges",
                "conflicts": "GET /memory/conflicts",
                "consolidate": "POST /memory/consolidate",
                "patterns": "GET /memory/patterns",
                "duplicates": "GET /memory/duplicates",
                "sync_push": "POST /sync/push",
                "sync_pull": "GET /sync/pull",
                "sync_status": "GET /sync/status",
                "skills_list": "GET /skills",
                "skill_get": "GET /skills/{id}",
                "skill_recall": "POST /skills/recall",
                "skill_execute": "POST /skills/{id}/execute",
                "skill_feedback": "POST /skills/{id}/feedback",
                "skill_extract": "POST /skills/extract",
                "skill_evolve": "POST /skills/evolve",
                "team_promote": "POST /team/promote",
                "team_memories": "GET /team/memories",
                "task_memories": "GET /team/task/{task_id}",
                "context": "POST /agent/context",
                "health": "GET /health",
                "version": "GET /version",
                "stats": "GET /api/stats",
            },
        })

    @app.route("/docs")
    def docs():
        response = make_response(API_DOCS_HTML)
        response.headers["Content-Type"] = "text/html"
        return response

    @app.route("/version")
    def version():
        return jsonify({
            "latest_core": CORE_VERSION,
            "latest_adapter": ADAPTER_VERSION,
            "schema_version": SCHEMA_VERSION,
        })

    @app.route("/health")
    def health():
        return jsonify({
            "status": "ok",
            "service": "openmemo",
            "api_version": API_VERSION,
            "engine_version": ENGINE_VERSION,
        })

    @app.route("/memory/write", methods=["POST"])
    @app.route("/api/memories", methods=["POST"])
    def write_memory():
        data = request.get_json()
        if not data or "content" not in data:
            return jsonify({"error": "content is required"}), 400

        memory_type = data.get("type", data.get("memory_type", data.get("cell_type", "fact")))
        memory_id = memory.write_memory(
            content=data["content"],
            scene=data.get("scene", ""),
            memory_type=memory_type,
            confidence=data.get("confidence", 0.8),
            agent_id=data.get("agent_id", ""),
            metadata=data.get("metadata", {}),
            scope=data.get("scope", ""),
            conversation_id=data.get("conversation_id", ""),
            team_id=data.get("team_id", ""),
            task_id=data.get("task_id", ""),
        )
        return jsonify({"memory_id": memory_id, "status": "stored"}), 201

    @app.route("/memory/search", methods=["POST"])
    @app.route("/api/memories/search", methods=["POST"])
    def search_mem():
        data = request.get_json()
        if not data or "query" not in data:
            return jsonify({"error": "query is required"}), 400

        results = memory.search_memory(
            query=data["query"],
            scene=data.get("scene", ""),
            agent_id=data.get("agent_id", ""),
            limit=data.get("limit", 10),
            conversation_id=data.get("conversation_id", ""),
            team_id=data.get("team_id", ""),
            task_id=data.get("task_id", ""),
        )
        return jsonify({"results": results})

    @app.route("/memory/recall", methods=["POST"])
    @app.route("/api/memories/recall", methods=["POST"])
    def recall_mem():
        data = request.get_json()
        if not data or "query" not in data:
            return jsonify({"error": "query is required"}), 400

        result = memory.recall_context(
            query=data["query"],
            scene=data.get("scene", ""),
            agent_id=data.get("agent_id", ""),
            limit=data.get("limit", 5),
            mode=data.get("mode", "kv"),
            conversation_id=data.get("conversation_id", ""),
            team_id=data.get("team_id", ""),
            task_id=data.get("task_id", ""),
        )
        return jsonify(result)

    @app.route("/memory/scenes", methods=["GET"])
    def scenes():
        agent_id = request.args.get("agent_id", "")
        scene_names = memory.list_scenes(agent_id=agent_id)
        return jsonify({"scenes": scene_names})

    @app.route("/memory/governance", methods=["POST"])
    @app.route("/api/maintain", methods=["POST"])
    def governance():
        data = request.get_json() or {}
        operation = data.get("operation", "cleanup")
        result = memory.memory_governance(operation=operation)
        return jsonify(result)

    @app.route("/constitution", methods=["GET"])
    def get_constitution():
        if memory.constitution:
            return jsonify(memory.constitution.summary())
        return jsonify({"error": "constitution not enabled"}), 404

    @app.route("/constitution/profiles", methods=["GET"])
    def list_profiles():
        profiles = memory.list_profiles()
        return jsonify({"profiles": profiles, "active": memory.active_profile()})

    @app.route("/constitution/switch", methods=["POST"])
    def switch_profile():
        data = request.get_json()
        if not data or "profile" not in data:
            return jsonify({"error": "profile name is required"}), 400
        try:
            result = memory.load_profile(data["profile"])
            return jsonify(result)
        except ValueError as e:
            return jsonify({"error": str(e)}), 404

    @app.route("/constitution/register", methods=["POST"])
    def register_profile():
        data = request.get_json()
        if not data or "name" not in data or "config" not in data:
            return jsonify({"error": "name and config are required"}), 400
        memory.register_profile(data["name"], data["config"])
        return jsonify({"status": "registered", "profile": data["name"]}), 201

    @app.route("/agents/register", methods=["POST"])
    @app.route("/agents", methods=["POST"])
    def register_agent():
        data = request.get_json()
        if not data or "agent_id" not in data:
            return jsonify({"error": "agent_id is required"}), 400
        result = memory.register_agent(
            agent_id=data["agent_id"],
            agent_type=data.get("agent_type", ""),
            description=data.get("description", ""),
        )
        return jsonify(result), 201

    @app.route("/agents", methods=["GET"])
    def list_agents():
        agents = memory.list_agents()
        return jsonify({"agents": agents})

    @app.route("/conversations", methods=["POST"])
    def start_conversation():
        data = request.get_json()
        if not data or "conversation_id" not in data:
            return jsonify({"error": "conversation_id is required"}), 400
        result = memory.start_conversation(
            conversation_id=data["conversation_id"],
            agent_id=data.get("agent_id", ""),
            scene=data.get("scene", ""),
        )
        return jsonify(result), 201

    @app.route("/conversations", methods=["GET"])
    def list_conversations():
        agent_id = request.args.get("agent_id", "")
        convs = memory.list_conversations(agent_id=agent_id)
        return jsonify({"conversations": convs})

    @app.route("/memory/promote", methods=["POST"])
    def promote_shared():
        result = memory.promote_shared_memories()
        return jsonify(result)

    @app.route("/memory/graph", methods=["GET"])
    def memory_graph():
        memory_id = request.args.get("memory_id", "")
        if not memory_id:
            return jsonify({"error": "memory_id is required"}), 400
        depth = request.args.get("depth", 1, type=int)
        result = memory.get_memory_graph(memory_id, depth=depth)
        return jsonify(result)

    @app.route("/memory/edges", methods=["POST"])
    def add_edge():
        data = request.get_json()
        if not data or "memory_a" not in data or "memory_b" not in data:
            return jsonify({"error": "memory_a and memory_b are required"}), 400
        edge = memory.add_memory_edge(
            memory_a=data["memory_a"],
            memory_b=data["memory_b"],
            relation_type=data.get("relation_type", "related"),
            confidence=data.get("confidence", 0.7),
        )
        return jsonify(edge), 201

    @app.route("/memory/edges", methods=["GET"])
    def list_edges():
        limit = request.args.get("limit", 100, type=int)
        edges = memory.list_edges(limit=limit)
        return jsonify({"edges": edges})

    @app.route("/memory/conflicts", methods=["GET"])
    def get_conflicts():
        agent_id = request.args.get("agent_id", "")
        scene = request.args.get("scene", "")
        conflicts = memory.detect_conflicts(agent_id=agent_id, scene=scene)
        return jsonify({"conflicts": conflicts})

    @app.route("/memory/consolidate", methods=["POST"])
    def consolidate():
        data = request.get_json() or {}
        result = memory.consolidate(
            agent_id=data.get("agent_id", ""),
            scene=data.get("scene", ""),
        )
        return jsonify(result)

    @app.route("/memory/patterns", methods=["GET"])
    def get_patterns():
        agent_id = request.args.get("agent_id", "")
        scene = request.args.get("scene", "")
        patterns = memory.get_patterns(agent_id=agent_id, scene=scene)
        items = [{
            "id": p.get("id", ""),
            "content": p.get("content", ""),
            "type": p.get("cell_type", ""),
            "confidence": p.get("metadata", {}).get("confidence", 0.5)
                          if isinstance(p.get("metadata"), dict)
                          else 0.5,
            "scene": p.get("scene", ""),
        } for p in patterns]
        return jsonify({"patterns": items})

    @app.route("/memory/duplicates", methods=["GET"])
    def get_duplicates():
        agent_id = request.args.get("agent_id", "")
        duplicates = memory.detect_duplicates(agent_id=agent_id)
        return jsonify({"duplicates": duplicates})

    @app.route("/sync/push", methods=["POST"])
    def sync_push():
        result = memory.push_sync()
        return jsonify(result)

    @app.route("/sync/pull", methods=["GET"])
    def sync_pull():
        since = request.args.get("since", 0, type=float)
        result = memory.pull_sync(since=since)
        return jsonify(result)

    @app.route("/sync/status", methods=["GET"])
    def sync_status():
        return jsonify(memory.get_sync_status())

    @app.route("/memory/<memory_id>", methods=["DELETE"])
    def delete_memory(memory_id):
        deleted = memory.delete(memory_id)
        if deleted:
            return jsonify({"deleted": True}), 200
        return jsonify({"error": "not found"}), 404

    @app.route("/agent/context", methods=["POST"])
    def agent_context():
        data = request.get_json()
        if not data or "query" not in data:
            return jsonify({"error": "query is required"}), 400

        result = memory.recall_context(
            query=data["query"],
            scene=data.get("scene", ""),
            agent_id=data.get("agent_id", ""),
            limit=data.get("limit", 3),
            mode="kv",
        )
        return jsonify({"memory_context": result.get("context", [])})

    @app.route("/memory/reconstruct", methods=["POST"])
    @app.route("/api/memories/reconstruct", methods=["POST"])
    def reconstruct_memory():
        data = request.get_json()
        if not data or "query" not in data:
            return jsonify({"error": "query is required"}), 400

        result = memory.reconstruct(
            query=data["query"],
            agent_id=data.get("agent_id", ""),
            max_sources=data.get("max_sources", 10),
        )
        return jsonify(result)

    @app.route("/api/stats")
    def api_stats():
        return jsonify(memory.stats())

    @app.route("/skills", methods=["GET"])
    def list_skills():
        scene = request.args.get("scene", "")
        status = request.args.get("status", "")
        skills = memory.list_skills(scene=scene, status=status)
        return jsonify({"skills": skills, "total": len(skills)})

    @app.route("/skills/<skill_id>", methods=["GET"])
    def get_skill(skill_id):
        skill = memory.get_skill(skill_id)
        if not skill:
            return jsonify({"error": "skill not found"}), 404
        return jsonify(skill)

    @app.route("/skills/recall", methods=["POST"])
    def recall_skills():
        data = request.get_json(force=True)
        query = data.get("query", "")
        scene = data.get("scene", "")
        top_k = data.get("top_k", 5)
        if not query:
            return jsonify({"error": "query required"}), 400
        skills = memory.recall_skills(query, scene=scene, top_k=top_k)
        return jsonify({"skills": skills, "total": len(skills)})

    @app.route("/skills/<skill_id>/execute", methods=["POST"])
    def execute_skill(skill_id):
        data = request.get_json(force=True) if request.data else {}
        mode = data.get("mode", "suggest")
        result = memory.execute_skill(skill_id, mode=mode)
        if "error" in result:
            return jsonify(result), 404
        return jsonify(result)

    @app.route("/skills/<skill_id>/feedback", methods=["POST"])
    def skill_feedback(skill_id):
        data = request.get_json(force=True)
        success = data.get("success", False)
        result_text = data.get("result", "")
        result = memory.record_skill_feedback(skill_id, success, result_text)
        if "error" in result:
            return jsonify(result), 404
        return jsonify(result)

    @app.route("/skills/extract", methods=["POST"])
    def extract_skills():
        new_skills = memory.extract_skills_from_memory()
        return jsonify({"skills": new_skills, "extracted": len(new_skills)})

    @app.route("/skills/evolve", methods=["POST"])
    def evolve_skills():
        result = memory.evolve_skills()
        return jsonify(result)

    @app.route("/inspector")
    def inspector():
        response = make_response(INSPECTOR_HTML)
        response.headers["Content-Type"] = "text/html"
        return response

    @app.route("/api/inspector/checklist")
    def inspector_checklist():
        checks = []

        checks.append({"name": "Adapter Loaded", "status": "ok"})

        try:
            memory.stats()
            checks.append({"name": "Memory Backend Connected", "status": "ok"})
        except Exception:
            checks.append({"name": "Memory Backend Connected", "status": "fail"})

        checks.append({"name": "Pipeline Active", "status": "ok"})

        try:
            s = memory.stats()
            if s.get("notes", 0) >= 0:
                checks.append({"name": "Memory Write Pipeline Healthy", "status": "ok"})
            else:
                checks.append({"name": "Memory Write Pipeline Healthy", "status": "warning"})
        except Exception:
            checks.append({"name": "Memory Write Pipeline Healthy", "status": "fail"})

        try:
            result = memory.search_memory(query="test", limit=1)
            if isinstance(result, list) and len(result) == 0:
                checks.append({"name": "Memory Recall Working", "status": "cold_start"})
            else:
                checks.append({"name": "Memory Recall Working", "status": "ok"})
        except Exception:
            checks.append({"name": "Memory Recall Working", "status": "fail"})

        checks.append({"name": "Task Memory & Deduplication Active", "status": "ok"})

        if memory.constitution:
            checks.append({"name": "Constitution Active", "status": "ok"})
        else:
            checks.append({"name": "Constitution Active", "status": "warning"})

        return jsonify({"checks": checks})

    @app.route("/api/inspector/memory-summary")
    def inspector_memory_summary():
        s = memory.stats()
        notes = memory.store.list_notes(limit=10000)
        cells = memory.store.list_cells(limit=10000)
        scenes_list = memory.list_scenes()

        type_dist = {}
        for c in cells:
            ct = c.get("cell_type", c.get("type", "unknown"))
            type_dist[ct] = type_dist.get(ct, 0) + 1

        scene_dist = {}
        for n in notes:
            sc = n.get("scene", "") or ""
            scene_dist[sc] = scene_dist.get(sc, 0) + 1

        return jsonify({
            "total_memories": s.get("notes", 0),
            "total_cells": s.get("cells", 0),
            "total_scenes": s.get("scenes", 0),
            "type_distribution": type_dist,
            "scene_distribution": scene_dist,
        })

    @app.route("/api/inspector/recent")
    def inspector_recent():
        limit = request.args.get("limit", 10, type=int)
        notes = memory.store.list_notes(limit=limit)
        recent = []
        for n in reversed(notes[-limit:]):
            recent.append({
                "content": n.get("content", ""),
                "scene": n.get("scene", ""),
                "memory_type": n.get("memory_type", n.get("type", "")),
                "timestamp": n.get("created_at", ""),
            })
        return jsonify({"recent": recent})

    @app.route("/api/inspector/search")
    def inspector_search():
        q = request.args.get("q", "")
        if not q:
            return jsonify({"results": []})
        limit = request.args.get("limit", 10, type=int)
        results = memory.search_memory(query=q, limit=limit)
        return jsonify({"results": results})

    @app.route("/api/inspector/health")
    def inspector_health():
        s = memory.stats()
        return jsonify({
            "status": "ok",
            "backend": "openmemo",
            "api_version": API_VERSION,
            "engine_version": ENGINE_VERSION,
            "total_memories": s.get("notes", 0),
            "total_scenes": s.get("scenes", 0),
        })

    @app.route("/team/promote", methods=["POST"])
    def team_promote():
        data = request.get_json() or {}
        result = memory.promote_to_team(team_id=data.get("team_id", ""))
        return jsonify(result)

    @app.route("/team/memories", methods=["GET"])
    def team_memories():
        team_id = request.args.get("team_id", "")
        scene = request.args.get("scene", "")
        results = memory.list_team_memories(team_id=team_id, scene=scene)
        return jsonify({"results": results, "count": len(results)})

    @app.route("/team/task/<task_id>", methods=["GET"])
    def task_memories(task_id):
        team_id = request.args.get("team_id", "")
        results = memory.list_task_memories(task_id=task_id, team_id=team_id)
        return jsonify({"results": results, "count": len(results)})

    # ─── Phase 23.5: Observation + DAG + Expandable Recall ─────────

    @app.route("/memory/ingest", methods=["POST"])
    def memory_ingest():
        """
        Ingest an Observation into the DAG Memory Graph.

        Body (JSON):
            type        — observation type (tool_execution, agent_step, etc.)
            agent_id    — agent identifier
            task_id     — task identifier
            content     — structured payload dict
            context     — scene, tags dict
            confidence  — float 0-1

        Returns:
            { node_id, obs_id, status }
        """
        data = request.get_json()
        if not data:
            return jsonify({"error": "JSON body required"}), 400
        result = memory.ingest(data)
        return jsonify(result), 201

    @app.route("/memory/expand", methods=["POST"])
    def memory_expand():
        """
        Expand a DAG node to show its raw children.

        Body: { "node_id": "node_abc123" }
        Returns: { node, children, child_count }
        """
        data = request.get_json() or {}
        node_id = data.get("node_id", "")
        if not node_id:
            return jsonify({"error": "node_id is required"}), 400
        result = memory.expand_node(node_id)
        return jsonify(result)

    @app.route("/memory/compress", methods=["POST"])
    def memory_compress():
        """
        Trigger memory compression — clusters raw nodes into summaries.

        Body: { "agent_id": "...", "scene": "..." }
        Returns: { raw_processed, clusters_found, summaries_created, skipped }
        """
        data = request.get_json() or {}
        result = memory.compress_memory(
            agent_id=data.get("agent_id", ""),
            scene=data.get("scene", ""),
        )
        return jsonify(result)

    @app.route("/memory/observations", methods=["GET"])
    def memory_observations():
        """
        List raw observations.
        Query params: agent_id, task_id, type, limit
        """
        agent_id = request.args.get("agent_id", "")
        task_id = request.args.get("task_id", "")
        obs_type = request.args.get("type", "")
        limit = int(request.args.get("limit", 100))
        results = memory.list_observations(
            agent_id=agent_id, task_id=task_id,
            obs_type=obs_type, limit=limit,
        )
        return jsonify({"results": results, "count": len(results)})

    @app.route("/memory/nodes", methods=["GET"])
    def memory_nodes():
        """
        List DAG memory nodes.
        Query params: type (raw/summary), agent_id, scene, limit
        """
        node_type = request.args.get("type", "")
        agent_id = request.args.get("agent_id", "")
        scene = request.args.get("scene", "")
        limit = int(request.args.get("limit", 100))
        nodes = memory.store.list_memory_nodes(
            node_type=node_type, agent_id=agent_id,
            scene=scene, limit=limit,
        )
        return jsonify({"results": nodes, "count": len(nodes)})

    @app.route("/memory/nodes/<node_id>", methods=["GET"])
    def memory_node_get(node_id):
        """Get a specific DAG node by ID."""
        node = memory.store.get_memory_node(node_id)
        if not node:
            return jsonify({"error": f"node {node_id} not found"}), 404
        return jsonify(node)

    @app.route("/memory/engine/stats", methods=["GET"])
    def engine_stats():
        """Return DAG engine statistics."""
        agent_id = request.args.get("agent_id", "")
        scene = request.args.get("scene", "")
        result = memory.engine_stats(agent_id=agent_id, scene=scene)
        return jsonify(result)

    # ─── Phase 23.6: Experience Extraction Engine ───────────────────

    @app.route("/experience/extract", methods=["POST"])
    def experience_extract():
        """
        Extract experiences from DAG memory nodes.

        Body: { agent_id, scene, task_id, generate_workflows (bool) }
        Returns: { experiences_extracted, workflows_generated,
                   experience_ids, workflow_ids }
        """
        data = request.get_json() or {}
        result = memory.extract_experiences(
            agent_id=data.get("agent_id", ""),
            scene=data.get("scene", ""),
            task_id=data.get("task_id", ""),
            generate_workflows=data.get("generate_workflows", True),
        )
        return jsonify(result)

    @app.route("/experience/recall", methods=["POST"])
    def experience_recall():
        """
        Find relevant experiences for a query.

        Body: { query, scene, agent_id, top_k }
        Returns: { results: [experience dicts], count }
        """
        data = request.get_json() or {}
        query = data.get("query", "")
        if not query:
            return jsonify({"error": "query is required"}), 400
        results = memory.recall_experiences(
            query=query,
            scene=data.get("scene", ""),
            agent_id=data.get("agent_id", ""),
            top_k=int(data.get("top_k", 5)),
        )
        return jsonify({"results": results, "count": len(results)})

    @app.route("/experience/list", methods=["GET"])
    def experience_list():
        """
        List stored experiences.
        Query params: agent_id, scene, type, limit
        """
        results = memory.list_experiences(
            agent_id=request.args.get("agent_id", ""),
            scene=request.args.get("scene", ""),
            exp_type=request.args.get("type", ""),
            limit=int(request.args.get("limit", 50)),
        )
        return jsonify({"results": results, "count": len(results)})

    @app.route("/experience/stats", methods=["GET"])
    def experience_stats():
        """Return Experience Engine statistics."""
        result = memory.experience_stats(
            agent_id=request.args.get("agent_id", ""),
            scene=request.args.get("scene", ""),
        )
        return jsonify(result)

    @app.route("/experience/workflows", methods=["GET"])
    def experience_workflows():
        """
        List workflow templates.
        Query params: agent_id, scene, limit
        """
        results = memory.list_experience_workflows(
            agent_id=request.args.get("agent_id", ""),
            scene=request.args.get("scene", ""),
            limit=int(request.args.get("limit", 30)),
        )
        return jsonify({"results": results, "count": len(results)})

    @app.route("/experience/workflows/recall", methods=["POST"])
    def experience_workflow_recall():
        """
        Recall relevant workflow templates for a query.

        Body: { query, scene, agent_id, top_k }
        """
        data = request.get_json() or {}
        query = data.get("query", "")
        if not query:
            return jsonify({"error": "query is required"}), 400
        results = memory.recall_workflows(
            query=query,
            scene=data.get("scene", ""),
            agent_id=data.get("agent_id", ""),
            top_k=int(data.get("top_k", 3)),
        )
        return jsonify({"results": results, "count": len(results)})

    @app.route("/experience/workflows/<wf_id>/result", methods=["POST"])
    def experience_workflow_result(wf_id):
        """
        Record execution result for a workflow (updates success_rate).

        Body: { success: bool }
        """
        data = request.get_json() or {}
        success = bool(data.get("success", False))
        result = memory.record_workflow_result(wf_id, success)
        return jsonify(result)

    @app.route("/experience/prompt", methods=["POST"])
    def experience_prompt():
        """
        Get prompt-ready experience + workflow context.

        Body: { query, scene, agent_id }
        Returns: { context: "..." }
        """
        data = request.get_json() or {}
        query = data.get("query", "")
        if not query:
            return jsonify({"error": "query is required"}), 400
        context = memory.recall_experience_for_prompt(
            query=query,
            scene=data.get("scene", ""),
            agent_id=data.get("agent_id", ""),
        )
        return jsonify({"context": context})

    # ─── Phase 23.7: Self-Improving Loop ────────────────────────────

    @app.route("/experience/outcome", methods=["POST"])
    def experience_capture_outcome():
        """
        Capture the result of applying an experience.

        Body: {
          experience_id (required),
          result: "success" | "failure" | "partial" | "unknown",
          metrics: {},
          agent_feedback: "",
          task_id: "",
          workflow_id: "",
          agent_id: "",
          score: -1.0
        }
        """
        data = request.get_json() or {}
        experience_id = data.get("experience_id", "")
        if not experience_id:
            return jsonify({"error": "experience_id is required"}), 400
        result = memory.capture_outcome(
            experience_id=experience_id,
            result=data.get("result", "unknown"),
            metrics=data.get("metrics", {}),
            agent_feedback=data.get("agent_feedback", ""),
            task_id=data.get("task_id", ""),
            workflow_id=data.get("workflow_id", ""),
            agent_id=data.get("agent_id", ""),
            score=float(data.get("score", -1.0)),
        )
        return jsonify(result)

    @app.route("/experience/outcomes", methods=["GET"])
    def experience_list_outcomes():
        """
        List captured outcomes.
        Query params: experience_id, workflow_id, agent_id, limit
        """
        results = memory.list_outcomes(
            experience_id=request.args.get("experience_id", ""),
            workflow_id=request.args.get("workflow_id", ""),
            agent_id=request.args.get("agent_id", ""),
            limit=int(request.args.get("limit", 50)),
        )
        return jsonify({"results": results, "count": len(results)})

    @app.route("/experience/evaluate", methods=["POST"])
    def experience_evaluate():
        """
        Evaluate an experience's effectiveness from outcome history.

        Body: { experience_id (required), limit: 100 }
        Returns: EvaluationResult (score, status, success_rate, recommendation…)
        """
        data = request.get_json() or {}
        experience_id = data.get("experience_id", "")
        if not experience_id:
            return jsonify({"error": "experience_id is required"}), 400
        result = memory.evaluate_experience(
            experience_id=experience_id,
            limit=int(data.get("limit", 100)),
        )
        return jsonify(result)

    @app.route("/experience/improve", methods=["POST"])
    def experience_improve():
        """
        Run the Self-Improving Loop for one experience.

        Body: { experience_id (required), auto_improve: false }

        Note: auto_improve defaults to False (Lite mode — data collection only).
        Returns: { action, allowed, reason, updated, evaluation }
        """
        data = request.get_json() or {}
        experience_id = data.get("experience_id", "")
        if not experience_id:
            return jsonify({"error": "experience_id is required"}), 400
        result = memory.run_improvement_cycle(
            experience_id=experience_id,
            auto_improve=bool(data.get("auto_improve", False)),
        )
        return jsonify(result)

    @app.route("/experience/improve/stats", methods=["GET"])
    def experience_improve_stats():
        """
        Improvement loop statistics.
        Query params: experience_id (optional)
        """
        result = memory.improvement_stats(
            experience_id=request.args.get("experience_id", ""),
        )
        return jsonify(result)

    @app.route("/experience/versions", methods=["GET"])
    def experience_versions():
        """
        List version history for an experience.
        Query params: experience_id (required), limit
        """
        experience_id = request.args.get("experience_id", "")
        if not experience_id:
            return jsonify({"error": "experience_id is required"}), 400
        results = memory.list_experience_versions(
            experience_id=experience_id,
            limit=int(request.args.get("limit", 50)),
        )
        return jsonify({"results": results, "count": len(results)})

    @app.route("/experience/rollback", methods=["POST"])
    def experience_rollback():
        """
        Roll back an experience to a specific version.

        Body: { experience_id (required), target_version (required) }
        Always allowed — safety escape hatch.
        """
        data = request.get_json() or {}
        experience_id = data.get("experience_id", "")
        target_version = data.get("target_version")
        if not experience_id or target_version is None:
            return jsonify({"error": "experience_id and target_version are required"}), 400
        result = memory.rollback_experience(
            experience_id=experience_id,
            target_version=int(target_version),
        )
        return jsonify(result)

    # ── Full Mode endpoints (Phase 23.7 Full) ──────────────────────

    @app.route("/experience/improve/readiness", methods=["GET"])
    def experience_improve_readiness():
        """
        Check auto-unlock readiness for one experience (Full mode).

        Query params: experience_id (required)

        Returns:
            { experience_id, ready, conditions, outcomes_count,
              usage_count, blocking }
        """
        experience_id = request.args.get("experience_id", "")
        if not experience_id:
            return jsonify({"error": "experience_id is required"}), 400
        result = memory.check_improvement_readiness(experience_id)
        return jsonify(result)

    @app.route("/experience/improve/readiness/all", methods=["GET"])
    def experience_improve_readiness_all():
        """
        Check auto-unlock readiness for all experiences (read-only, Full mode).

        Query params: agent_id (optional), scene (optional), limit (optional)

        Returns:
            { total, ready, not_ready, results: [...] }
        """
        result = memory.check_all_readiness(
            agent_id=request.args.get("agent_id", ""),
            scene=request.args.get("scene", ""),
            limit=int(request.args.get("limit", 500)),
        )
        return jsonify(result)

    @app.route("/experience/improve/all", methods=["POST"])
    def experience_improve_all():
        """
        Run Self-Improving Loop for all experiences in bulk (Full mode).

        Body: { agent_id (optional), scene (optional), limit (optional) }

        Returns:
            { total_scanned, ready_for_improvement, improved,
              blocked, skipped, errors, details }
        """
        data = request.get_json() or {}
        result = memory.run_all_improvements(
            agent_id=data.get("agent_id", ""),
            scene=data.get("scene", ""),
            limit=int(data.get("limit", 500)),
        )
        return jsonify(result)

    @app.route("/experience/worker/start", methods=["POST"])
    def experience_worker_start():
        """
        Start background ImprovementWorkerThread (Full mode daemon).

        Body: { interval_seconds (optional, default 300),
                agent_id (optional), scene (optional) }

        Returns:
            { started: bool, interval_seconds, message }
        """
        data = request.get_json() or {}
        result = memory.start_improvement_worker(
            interval_seconds=float(data.get("interval_seconds", 300.0)),
            agent_id=data.get("agent_id", ""),
            scene=data.get("scene", ""),
        )
        return jsonify(result)

    @app.route("/experience/worker/stop", methods=["POST"])
    def experience_worker_stop():
        """
        Stop the background ImprovementWorkerThread.

        Returns:
            { stopped: bool, message }
        """
        result = memory.stop_improvement_worker()
        return jsonify(result)

    @app.route("/experience/worker/status", methods=["GET"])
    def experience_worker_status():
        """
        Return current status of the background ImprovementWorkerThread.

        Returns:
            { running, interval_seconds, run_count, last_run_stats }
        """
        result = memory.improvement_worker_status()
        return jsonify(result)

    # ── v1 Developer API (supermemory-style simple API) ───────────────────────
    from openmemo.api.v1 import create_v1_blueprint
    v1_bp = create_v1_blueprint(store=store, config=config)
    app.register_blueprint(v1_bp)

    # ── User Memory Layer ──────────────────────────────────────────────────────
    from openmemo.user_memory.api import create_user_memory_blueprint
    um_bp = create_user_memory_blueprint(db_path=db)
    app.register_blueprint(um_bp)

    # ── Context Memory Layer ───────────────────────────────────────────────────
    from openmemo.context_memory.api import create_context_blueprint
    ctx_bp = create_context_blueprint(db_path=db)
    app.register_blueprint(ctx_bp)

    # ── Memory Orchestrator ────────────────────────────────────────────────────
    from openmemo.memory_orchestrator.api import create_orchestrator_blueprint
    orch_bp = create_orchestrator_blueprint(db_path=db)
    app.register_blueprint(orch_bp)

    return app


if __name__ == "__main__":
    app = create_app()
    port = int(os.environ.get("PORT", 8765))
    app.run(host="127.0.0.1", port=port)
