"""
OpenMemo Adapter v2.0 — Automatic Cognitive Memory & Task-Aware Execution.

Pipeline:
  scene mapping → fingerprint → pre-check → recall → inject → agent decision →
  tool execution → task memory extract → dual-layer async write

Core principle: install → run → memory works automatically
"""

import logging
from typing import List, Optional

from openmemo_openclaw.config import AdapterConfig
from openmemo_openclaw.memory_client import MemoryClient
from openmemo_openclaw.transformer import (
    transform_user_message,
    transform_agent_response,
    transform_tool_call,
    transform_task_complete,
    extract_memory_content,
    infer_memory_type,
)
from openmemo_openclaw.scenes import SceneMapper
from openmemo_openclaw.ranker import rank_memories
from openmemo_openclaw.injector import inject_context, inject_into_messages, build_prompt
from openmemo_openclaw.queue_worker import SyncMemoryWorker, AsyncMemoryWorker
from openmemo_openclaw.health import run_health_check, HealthCheckError
from openmemo_openclaw.fingerprint import generate_fingerprint, normalize_intent, fingerprint_from_event
from openmemo_openclaw.task_extractor import TaskTracker, extract_task_memory
from openmemo_openclaw.pre_check import PreTaskChecker, PreCheckResult, NO_MATCH
from openmemo_openclaw.version_check import check_version, log_version_status
from openmemo_openclaw.memory_rules import MemoryRulesEngine, load_rules
from openmemo_openclaw.soul_merger import merge_into_messages, merge_soul
from openmemo_openclaw.feedback_store import FeedbackStore
from openmemo_openclaw.memory_policy import PolicyEngine
from openmemo_openclaw.memory_policy.rule_store import RuleStore
from openmemo_openclaw.memory_policy.rule_generator import generate_rules_from_text
from openmemo_openclaw.activation import ActivationStore
from openmemo_openclaw.event_bridge import EventBridge
from openmemo_openclaw.ui_controller import UIController

logger = logging.getLogger("openmemo_openclaw")

TASK_CHECK_TEMPLATE = """Task Memory Check:
A similar task has been executed before.

Previous result:
- {summary}

Status: {status}
Recommended action: {action}"""


class OpenMemoAdapter:
    """
    Main adapter class connecting OpenClaw to OpenMemo.

    v2.0: Automatic memory — install, run, memory works.

    Usage (auto mode — recommended):
        adapter = OpenMemoAdapter()
        adapter.on_user_message("deploy my app")
        messages = adapter.inject_context(messages, "deploy my app")

    Pre-task check (avoid duplicate execution):
        result = adapter.pre_check("deploy my app")
        if result.matched and result.recommended_action == "reuse_or_skip":
            print("Task already done:", result.previous_summary)

    Async mode:
        adapter = OpenMemoAdapter(async_mode=True)
        await adapter.start_async()
    """

    def __init__(self, config: AdapterConfig = None, async_mode: bool = False):
        self._config = config or AdapterConfig()
        self._async_mode = async_mode
        self._scene_mapper = SceneMapper()
        self._session_id = ""
        self._current_task_id = ""
        self._current_scene = ""
        self._current_files: list = []
        self._task_tracker = TaskTracker()

        if self._config.health_check and self._config.backend != "library":
            try:
                resolved = run_health_check(self._config)
                logger.info("[openmemo] backend ready: %s", resolved)
            except HealthCheckError as e:
                logger.warning("[openmemo] %s", e)

        self._client = MemoryClient(self._config)
        self._pre_checker = PreTaskChecker(self._client)
        self._in_do_write = False

        if async_mode:
            self._async_worker = AsyncMemoryWorker(
                write_fn=self._do_write,
                max_retry=self._config.queue_max_retry,
                backoff_base=self._config.queue_backoff_base,
            )
            self._sync_worker = None
        else:
            self._sync_worker = SyncMemoryWorker(
                write_fn=self._do_write,
                max_retry=self._config.queue_max_retry,
                backoff_base=self._config.queue_backoff_base,
            )
            self._async_worker = None

        if self._config.scene_override:
            self._scene_mapper.scene_override(self._config.scene_override)

        logger.info("[openmemo] adapter v2.0 initialized (backend=%s, mode=%s, auto_write=%s, auto_recall=%s)",
                    self._config.backend, "async" if async_mode else "sync",
                    self._config.auto_write, self._config.auto_recall)

        try:
            log_version_status()
        except Exception:
            pass

        self._rules_engine = load_rules(self._config)
        if self._rules_engine.enabled:
            logger.info("[openmemo] memory rules active (version=%s, mode=%s, rules=%d)",
                        self._rules_engine.version, self._config.memory_rules_mode,
                        self._rules_engine.rule_count)

        self._fb_store = FeedbackStore()

        self._user_memory = None
        try:
            from openmemo.user_memory import UserMemory
            self._user_memory = UserMemory(db_path=self._config.db_path)
            logger.info("[openmemo] user_memory enabled")
        except Exception:
            pass

        self._orchestrator = None
        try:
            from openmemo.memory_orchestrator import MemoryOrchestrator
            self._orchestrator = MemoryOrchestrator(
                db_path=self._config.db_path,
            )
            logger.info("[openmemo] memory_orchestrator enabled")
        except Exception:
            pass

        self._rule_store = RuleStore()
        self._policy_engine = PolicyEngine(self._rule_store)
        logger.info("[openmemo] memory_policy active (mode=%s)",
                    self._rule_store.get_current().get("mode", "smart"))

        self._activation_store = ActivationStore()
        self._event_bridge = EventBridge()
        self._ui_controller = UIController()

        self._inspector = None
        if self._config.features.get("inspector", True):
            try:
                from openmemo_openclaw.inspector import InspectorServer
                self._inspector = InspectorServer(self, port=self._config.inspector_port)
                self._inspector.start()
            except Exception as e:
                logger.warning("[openmemo] inspector failed to start: %s", e)

        panel = self._activation_store.on_install()
        self._ui_controller.show_activation(panel)
        self._ui_controller.show_rule_card(self._policy_engine)
        logger.info("[openmemo] activation panel ready: %s", panel.get("title", ""))

        # Level 2: Intercept direct client writes so they are always tracked
        self._setup_write_intercept()

        # Level 1: Auto-sync any pre-existing memories into tracking (silent, zero user action)
        self._auto_sync_on_startup()

    async def start_async(self):
        if self._async_worker:
            await self._async_worker.start()
            logger.info("[openmemo] async worker started")

    async def stop_async(self):
        if self._async_worker:
            await self._async_worker.stop()
            logger.info("[openmemo] async worker stopped")

    def set_session(self, session_id: str):
        self._session_id = session_id

    def set_task(self, task_id: str):
        self._current_task_id = task_id
        self._task_tracker.reset()

    def set_files(self, file_paths: list):
        self._current_files = file_paths or []

    def scene_override(self, scene: str):
        self._scene_mapper.scene_override(scene)
        self._current_scene = scene

    def clear_scene_override(self):
        self._scene_mapper.clear_override()

    def on_install(self) -> dict:
        """
        Hook: called once after adapter setup.
        Returns Activation Panel payload. Event Bridge emits on_task_start.
        Also runs a storage consistency check to detect externally-written memories.
        """
        panel = self._activation_store.get_activation_panel()
        self._event_bridge.emit("on_task_start", metadata={"action": "install"})

        health = self._check_storage_consistency()
        if health["mismatch"]:
            logger.warning(
                "[openmemo] ⚠ Storage mismatch detected: %d memories exist outside OpenMemo tracking "
                "(tracked=%d). Use adapter.write_memory() for external writes, or call "
                "POST /inspector/sync to import them.",
                health["external_count"],
                health["tracked_count"],
            )
            self._event_bridge.emit(
                "on_storage_mismatch",
                metadata={
                    "external_count": health["external_count"],
                    "tracked_count": health["tracked_count"],
                    "untracked_count": health["untracked_count"],
                },
            )

        return self._ui_controller.show_activation(panel)

    def on_workflow_adopted(self, workflow_summary: str = "",
                            task_id: str = "") -> dict:
        """
        Hook: called when a workflow is reused.
        Event Bridge emits on_workflow_adopt.
        UI Controller shows Reuse Indicator.
        """
        tid = task_id or self._current_task_id
        self._activation_store.on_workflow_adopted(
            workflow_summary=workflow_summary,
            task_id=tid,
        )
        self._event_bridge.emit(
            "on_workflow_adopt",
            task_id=tid,
            metadata={"workflow_summary": workflow_summary},
        )
        return self._ui_controller.show_reuse_indicator(workflow_summary, task_id=tid)

    def on_user_message(self, message: str, task_id: str = ""):
        self._task_tracker.set_user_request(message)

        tid = task_id or self._current_task_id
        guidance = self._activation_store.on_task_start(task_id=tid, query=message)
        self._event_bridge.emit(
            "on_task_start",
            task_id=tid,
            metadata={"query": message[:120]},
        )
        if guidance:
            self._ui_controller.show_guided_first_task(task_id=tid, query=message)

        event = transform_user_message(
            message,
            session_id=self._session_id,
            task_id=tid,
        )
        if event and self._config.auto_write:
            self._process_event(event)

    def on_agent_response(self, response: str, tools: list = None,
                          task_id: str = ""):
        event = transform_agent_response(
            response,
            tools=tools,
            session_id=self._session_id,
            task_id=task_id or self._current_task_id,
        )
        if event and self._config.auto_write:
            self._process_event(event)

    def on_tool_call(self, tool_name: str, tool_output: str = "",
                     task_id: str = ""):
        self._task_tracker.add_tool_call(tool_name, tool_output)

        event = transform_tool_call(
            tool_name,
            tool_output,
            session_id=self._session_id,
            task_id=task_id or self._current_task_id,
        )
        if event and self._config.auto_write:
            self._process_event(event)

    def on_task_complete(self, summary: str, tools: list = None,
                         task_id: str = "", status: str = "success"):
        event = transform_task_complete(
            summary,
            tools=tools,
            session_id=self._session_id,
            task_id=task_id or self._current_task_id,
        )
        if event and self._config.auto_write:
            self._process_event(event)

        if self._task_tracker.has_data:
            self._write_task_memory(summary, status=status)

        tid = task_id or self._current_task_id
        fb = self._fb_store
        stored_count = len(fb.get_task_trace(tid).get("stored", [])) if fb else 0
        reused_count = getattr(fb, "_workflows_reused", 0) if fb else 0

        self._event_bridge.emit(
            "on_task_complete",
            task_id=tid,
            metadata={
                "summary": summary[:120],
                "status": status,
                "memories_stored": stored_count,
                "workflows_reused": reused_count,
            },
        )

        insight = self._activation_store.on_task_complete(
            summary=summary,
            memories_stored=stored_count,
            workflows_reused=reused_count,
            task_id=tid,
        )
        self._ui_controller.show_insight(insight)

    def pre_check(self, query: str, scene: str = "",
                  tools: list = None) -> PreCheckResult:
        if not self._config.features.get("task_precheck", True):
            return NO_MATCH

        effective_scene = scene or self._current_scene
        fingerprint = generate_fingerprint(
            scene=effective_scene,
            intent=query,
            tools=tools,
        )

        if not fingerprint:
            return NO_MATCH

        result = self._pre_checker.check(
            fingerprint=fingerprint,
            query=query,
            scene=effective_scene,
        )

        if result.matched:
            self._fb_store.on_workflow_reuse(task_id=self._current_task_id)

        return result

    def recall(self, query: str, scene: str = "",
               limit: int = None) -> List[str]:
        if not self._config.auto_recall:
            return []

        effective_scene = scene or self._current_scene
        memories = self._client.recall_context(
            query=query,
            scene=effective_scene,
            limit=limit or self._config.recall_limit,
        )
        logger.info("[openmemo] recall query=%r scene=%s hit=%d",
                    query[:50], effective_scene or "auto", len(memories))

        if memories:
            self._fb_store.on_recall(
                summaries=memories,
                task_id=self._current_task_id,
            )
            self._event_bridge.emit(
                "on_memory_recall",
                task_id=self._current_task_id,
                metadata={"count": len(memories), "scene": effective_scene},
            )

        return memories

    def recall_ranked(self, query: str, scene: str = "",
                      limit: int = None) -> List[dict]:
        effective_scene = scene or self._current_scene
        results = self._client.search_memory(
            query=query,
            scene=effective_scene,
            limit=limit or self._config.recall_limit,
        )

        conflict = self._config.conflict_policy
        if not self._config.features.get("suppression", True):
            conflict = "none"

        ranked = rank_memories(
            results,
            current_scene=effective_scene,
            conflict_policy=conflict,
        )

        return ranked[:self._config.max_injected_items]

    def inject_context(self, messages: List[dict], query: str,
                       scene: str = "",
                       include_pre_check: bool = True) -> List[dict]:
        result = list(messages)

        if (self._rules_engine.enabled and
                self._config.memory_rules_mode == "merged_soul"):
            result = merge_into_messages(result, self._rules_engine.rules_text)

        memories = self.recall(query, scene=scene)

        if include_pre_check:
            check_result = self.pre_check(query, scene=scene)
            if check_result.matched:
                task_context = TASK_CHECK_TEMPLATE.format(
                    summary=check_result.previous_summary,
                    status=check_result.task_status,
                    action=check_result.recommended_action,
                )
                memories = [task_context] + memories
                logger.info("[openmemo] pre-check injected: action=%s",
                            check_result.recommended_action)

        if not memories:
            logger.debug("[openmemo] cold start, no memories to inject")
            return result

        self._fb_store.on_inject(
            summaries=memories,
            task_id=self._current_task_id,
        )

        return inject_into_messages(
            result,
            memories,
            strategy=self._config.injection_strategy,
            max_items=self._config.max_injected_items,
            max_tokens=self._config.max_memory_tokens,
        )

    def get_context(self, query: str, scene: str = "") -> Optional[str]:
        memories = self.recall(query, scene=scene)
        if not memories:
            return None

        return inject_context(
            memories,
            strategy=self._config.injection_strategy,
            max_items=self._config.max_injected_items,
            max_tokens=self._config.max_memory_tokens,
        )

    def build_prompt(self, user_prompt: str, query: str = "",
                     scene: str = "") -> str:
        effective_query = query or user_prompt
        memories = self.recall(effective_query, scene=scene)

        prompt = user_prompt
        if (self._rules_engine.enabled and
                self._config.memory_rules_mode == "merged_soul"):
            prompt = merge_soul(prompt, self._rules_engine.rules_text)

        return build_prompt(
            original_prompt=prompt,
            memory_context=memories,
            strategy=self._config.injection_strategy,
            max_items=self._config.max_injected_items,
            max_tokens=self._config.max_memory_tokens,
        )

    def user_memory_recall(self, user_id: str = "", top_n: int = 5) -> str:
        """Recall user preferences, constraints, and environment memories."""
        if self._user_memory:
            try:
                uid = user_id or self._config.namespace or "default"
                return self._user_memory.format_for_prompt(uid, top_n=top_n)
            except Exception as e:
                logger.warning("[openmemo] user_memory_recall failed: %s", e)
        return ""

    def user_memory_store(self, text: str, user_id: str = "") -> list:
        """Extract and store user memory from a text block (e.g. task summary)."""
        if self._user_memory:
            try:
                uid = user_id or self._config.namespace or "default"
                return self._user_memory.store(uid, text)
            except Exception as e:
                logger.warning("[openmemo] user_memory_store failed: %s", e)
        return []

    def orchestrated_prompt(self, prompt: str, task_id: str = "",
                            scene: str = "") -> str:
        """
        Build a context-enriched prompt using the Memory Orchestrator.
        Falls back to standard build_prompt() if orchestrator is unavailable.
        """
        if self._orchestrator:
            try:
                result = self._orchestrator.run(
                    task=prompt,
                    agent_id=self._config.namespace,
                    task_id=task_id or self._current_task_id,
                    scene=scene or self._current_scene,
                )
                enriched = result.get("prompt") or result.get("context") or ""
                if enriched:
                    return enriched
            except Exception as e:
                logger.warning("[openmemo] orchestrator.run failed: %s", e)

        return self.build_prompt(prompt, scene=scene)

    def list_scenes(self) -> List[str]:
        return self._client.list_scenes()

    @property
    def memory_rules(self) -> MemoryRulesEngine:
        return self._rules_engine

    @property
    def feedback_store(self) -> FeedbackStore:
        return self._fb_store

    @property
    def policy_engine(self) -> PolicyEngine:
        return self._policy_engine

    @property
    def rule_store(self) -> RuleStore:
        return self._rule_store

    @property
    def activation_store(self) -> ActivationStore:
        return self._activation_store

    @property
    def event_bridge(self) -> EventBridge:
        return self._event_bridge

    @property
    def ui_controller(self) -> UIController:
        return self._ui_controller

    @property
    def stats(self) -> dict:
        worker_stats = (
            self._async_worker.stats if self._async_worker
            else self._sync_worker.stats if self._sync_worker
            else {}
        )
        return {
            "namespace": self._config.namespace,
            "backend": self._config.backend,
            "session_id": self._session_id,
            "current_scene": self._current_scene,
            "async_mode": self._async_mode,
            "auto_write": self._config.auto_write,
            "auto_recall": self._config.auto_recall,
            "worker_stats": worker_stats,
            "memory_rules": self._rules_engine.status,
        }

    def close(self):
        if self._inspector:
            self._inspector.stop()
        self._client.close()
        logger.info("[openmemo] adapter closed")

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def _process_event(self, event: dict):
        scene = self._detect_scene(event)
        self._current_scene = scene

        self._task_tracker.set_scene(scene)

        content = extract_memory_content(event)
        if not content:
            return

        memory_type = infer_memory_type(event)

        fp = fingerprint_from_event(event, scene)
        if not self._task_tracker.fingerprint:
            self._task_tracker.set_fingerprint(fp)

        payload = {
            "content": content,
            "scene": scene,
            "memory_type": memory_type,
            "metadata": {
                "task_id": event.get("task_id", ""),
                "session_id": event.get("session_id", ""),
                "event_type": event.get("type", ""),
                "task_fingerprint": fp,
            },
        }

        logger.info("[openmemo] scene=%s type=%s queued_write content=%r",
                    scene, memory_type, content[:60])

        self._enqueue_write(payload)

    def _write_task_memory(self, task_result: str, status: str = "success"):
        task_mem = self._task_tracker.extract(
            task_result=task_result,
            status=status,
        )

        content = (
            f"Task: {task_mem['task_name']} | "
            f"Fingerprint: {task_mem['task_fingerprint']} | "
            f"Status: {task_mem['status']} | "
            f"Summary: {task_mem['summary']}"
        )

        payload = {
            "content": content,
            "scene": task_mem.get("scene", ""),
            "memory_type": "task_execution",
            "metadata": {
                "task_name": task_mem.get("task_name", ""),
                "task_fingerprint": task_mem.get("task_fingerprint", ""),
                "status": task_mem.get("status", ""),
                "tools_used": task_mem.get("tools_used", []),
                "user_request": task_mem.get("user_request", ""),
            },
        }

        logger.info("[openmemo] task_memory: name=%s fp=%s status=%s",
                    task_mem.get("task_name"), task_mem.get("task_fingerprint"), status)

        self._enqueue_write(payload)
        self._task_tracker.reset()

    def _enqueue_write(self, payload: dict):
        if self._async_mode and self._async_worker:
            self._async_worker.enqueue_sync(payload)
        elif self._sync_worker:
            self._sync_worker.write(payload)

    def _detect_scene(self, event: dict) -> str:
        tools = event.get("tools", [])
        task_id = event.get("task_id", "")
        content = event.get("content", "")

        return self._scene_mapper.detect(
            task_name=task_id,
            tools=tools,
            file_paths=self._current_files,
            prompt=content,
        )

    def write_memory(self, content: str, scene: str = "",
                     memory_type: str = "observation",
                     metadata: dict = None) -> str:
        """
        Public write path — the CORRECT way to write memories from external code.

        All writes through this method are guaranteed to:
          1. Pass through the Policy Engine (filtered by memory type rules)
          2. Be tracked by FeedbackStore (visible in Inspector)
          3. Emit EventBridge events (on_memory_store / on_policy_block)

        Usage:
            adapter.write_memory(
                content="DUODA prefers concise responses",
                scene="duoda",
                memory_type="preference",
            )

        Do NOT write memories directly via the OpenMemo API — they bypass tracking
        and will not appear in the Inspector. Always use this method instead.
        """
        if not content or not content.strip():
            logger.warning("[openmemo] write_memory called with empty content, skipping")
            return ""

        payload = {
            "content": content.strip(),
            "scene": scene or self._current_scene,
            "memory_type": memory_type,
            "metadata": {
                **(metadata or {}),
                "task_id": self._current_task_id,
                "session_id": self._session_id,
                "write_source": "external",
            },
        }

        logger.info("[openmemo] write_memory type=%s scene=%s content=%r",
                    memory_type, payload["scene"], content[:60])
        self._do_write(payload)
        return content[:40]

    def _check_storage_consistency(self) -> dict:
        """
        Compare memories in the backend storage vs. memories tracked by FeedbackStore.
        Returns a dict describing any mismatch so callers can warn or auto-sync.
        """
        external_count = 0
        try:
            results = self._client.search_memory(query="", limit=1000)
            external_count = len(results) if results else 0
        except Exception:
            pass

        tracked_count = 0
        if self._fb_store:
            try:
                tracked_count = self._fb_store.get_metrics().get("memories_stored", 0)
            except Exception:
                pass

        untracked_count = max(0, external_count - tracked_count)
        mismatch = external_count > 0 and tracked_count == 0

        return {
            "external_count": external_count,
            "tracked_count": tracked_count,
            "untracked_count": untracked_count,
            "mismatch": mismatch,
            "status": "warning" if mismatch else "ok",
        }

    def _setup_write_intercept(self):
        """
        Level 2: Wrap _client.write_memory so ALL writes — whether through the
        Adapter hooks or called directly on the client — are automatically tracked
        by FeedbackStore and emitted to EventBridge.

        Uses _in_do_write flag to prevent double-counting when _do_write() calls
        the client (that path already tracks via _fb_store.on_store directly).
        """
        original_write = self._client.write_memory

        def _intercepted_write(content, scene="", memory_type="observation",
                               confidence=0.8, metadata=None):
            result = original_write(
                content=content,
                scene=scene,
                memory_type=memory_type,
                confidence=confidence,
                metadata=metadata or {},
            )
            if not self._in_do_write:
                task_id = (metadata or {}).get("task_id", "") or self._current_task_id
                self._fb_store.on_store(
                    content=content,
                    memory_type=memory_type,
                    task_id=task_id,
                    memory_id=result or "",
                )
                self._event_bridge.emit(
                    "on_memory_store",
                    task_id=task_id,
                    metadata={
                        "memory_type": memory_type,
                        "memory_id": result or "",
                        "source": "direct_api_intercepted",
                    },
                )
                logger.info(
                    "[openmemo] ⚡ Direct write intercepted and tracked: type=%s content=%r",
                    memory_type, content[:60],
                )
            return result

        self._client.write_memory = _intercepted_write
        logger.info("[openmemo] write interceptor active — all writes now tracked")

    def _auto_sync_on_startup(self):
        """
        Level 1: On startup, silently import any pre-existing memories that were
        written before the intercept was active (e.g. from a previous session or
        via direct API calls before upgrading to v2.7.x).

        Zero user action required. Runs once at adapter initialization.
        """
        try:
            health = self._check_storage_consistency()
            if not health["mismatch"]:
                return

            results = self._client.search_memory(query="", limit=1000)
            synced = 0
            for r in results:
                rid = r.get("id") or r.get("memory_id") or ""
                content = r.get("summary") or r.get("content") or ""
                if not isinstance(content, str):
                    content = str(content)
                content = content[:120]
                mt = r.get("memory_type") or r.get("type") or "memory"
                if content:
                    self._fb_store.on_store(
                        content=content,
                        memory_type=mt,
                        task_id="startup_sync",
                        memory_id=rid,
                    )
                    synced += 1

            if synced > 0:
                self._event_bridge.emit(
                    "on_storage_sync",
                    metadata={"synced_count": synced, "source": "startup_auto_sync"},
                )
                logger.info(
                    "[openmemo] ✓ Auto-synced %d pre-existing memories into tracking on startup",
                    synced,
                )
        except Exception as e:
            logger.debug("[openmemo] startup auto-sync skipped: %s", e)

    def _do_write(self, payload: dict):
        content = payload["content"]
        memory_type = payload.get("memory_type", "observation")

        if not self._policy_engine.should_store(memory_type):
            logger.info("[policy] BLOCKED type=%s content=%r", memory_type, content[:60])
            self._event_bridge.emit(
                "on_policy_block",
                task_id=payload.get("metadata", {}).get("task_id", ""),
                metadata={"memory_type": memory_type},
            )
            return

        self._in_do_write = True
        try:
            result = self._client.write_memory(
                content=content,
                scene=payload.get("scene", ""),
                memory_type=memory_type,
                metadata=payload.get("metadata", {}),
            )
        finally:
            self._in_do_write = False

        task_id = payload.get("metadata", {}).get("task_id", "") or self._current_task_id
        self._fb_store.on_store(
            content=content,
            memory_type=memory_type,
            task_id=task_id,
            memory_id=result or "",
        )
        self._event_bridge.emit(
            "on_memory_store",
            task_id=task_id,
            metadata={"memory_type": memory_type, "memory_id": result or ""},
        )
        # Also extract user memory from task completions
        if memory_type in ("task_execution", "decision") and self._user_memory:
            self.user_memory_store(content)
