# 中文注释：
# 文件：echobot/agent/memory_auto.py
# 说明：自动提取用户偏好并写入长期记忆。

"""Automatic memory writing for stable user preferences."""

from __future__ import annotations

import re

from echobot.agent.memory import MemoryStore


class MemoryAutoWriter:
    """
    自动记忆写入器。

    作用：
    1. 从用户消息中提取可长期保存的偏好信息。
    2. 将偏好写入 memory/MEMORY.md 的 `## Preferences` 区块。
    3. 做去重，避免重复写入同一条偏好。
    """

    SECTION_HEADER = "## Preferences"
    _PLACEHOLDER_LINES = {
        "(User preferences learned over time)",
        "（User preferences learned over time）",
        "(用户偏好)",
    }
    _EXPLICIT_PATTERNS = (
        re.compile(r"^(?:请)?记住[：:\s,-]*(.+)$", re.IGNORECASE),
        re.compile(r"^(?:请)?帮我记住[：:\s,-]*(.+)$", re.IGNORECASE),
        re.compile(r"^remember(?:\s+that)?[ :,-]*(.+)$", re.IGNORECASE),
    )
    _PREFERENCE_PATTERNS = (
        re.compile(r"^我(?:比较|更)?喜欢.+"),
        re.compile(r"^我(?:比较|更)?不喜欢.+"),
        re.compile(r"^我(?:比较|更)?偏好.+"),
        re.compile(r"^我习惯.+"),
        re.compile(r"^请叫我.+"),
        re.compile(r"^叫我.+"),
        re.compile(r"^默认(?:请)?(?:用|使用|设置|走).+"),
        re.compile(r"^(?:以后|今后|之后).+"),
        re.compile(r"^请用(?:中文|英文).+"),
        re.compile(r"^用(?:中文|英文).+"),
        re.compile(r"^my preference is.+", re.IGNORECASE),
        re.compile(r"^i prefer.+", re.IGNORECASE),
    )

    def __init__(self, memory: MemoryStore, max_note_len: int = 200):
        self.memory = memory
        self.max_note_len = max_note_len

    def remember_from_message(self, message: str) -> list[str]:
        """
        从用户消息自动提取并写入偏好。

        Returns:
            实际新写入的偏好列表（去重后）。
        """
        notes = self.extract_preferences(message)
        if not notes:
            return []

        saved: list[str] = []
        for note in notes:
            if self.append_preference(note):
                saved.append(note)
        return saved

    def extract_preferences(self, message: str) -> list[str]:
        """提取偏好候选文本。"""
        if not message or not message.strip():
            return []

        extracted: list[str] = []
        seen: set[str] = set()

        for raw_line in message.splitlines():
            line = self._normalize(raw_line.strip(" \t-•"))
            if not line:
                continue

            explicit_note = self._extract_explicit(line)
            if explicit_note:
                key = self._normalize_for_compare(explicit_note)
                if key and key not in seen:
                    seen.add(key)
                    extracted.append(explicit_note)
                continue

            if self._looks_like_question(line):
                continue

            if any(pattern.match(line) for pattern in self._PREFERENCE_PATTERNS):
                note = self._clean_note(line)
                key = self._normalize_for_compare(note)
                if key and key not in seen:
                    seen.add(key)
                    extracted.append(note)

        return extracted

    def append_preference(self, note: str) -> bool:
        """
        追加偏好到 MEMORY.md 的 Preferences 区块。

        Returns:
            True 表示新增写入，False 表示已存在或无效输入。
        """
        cleaned_note = self._clean_note(note)
        if not cleaned_note:
            return False

        existing_content = self.memory.read_long_term()
        lines = existing_content.splitlines() if existing_content else ["# Long-term Memory"]

        start, end = self._find_preferences_section(lines)
        if start is None:
            if lines and lines[-1].strip():
                lines.append("")
            lines.append(self.SECTION_HEADER)
            lines.append("")
            start = len(lines) - 2
            end = len(lines)

        section_lines = lines[start + 1 : end]
        section_lines = [
            line for line in section_lines if line.strip() not in self._PLACEHOLDER_LINES
        ]

        existing_notes = {
            self._normalize_for_compare(match.group(1))
            for line in section_lines
            if (match := re.match(r"^\s*[-*]\s+(.+)$", line))
        }
        normalized_note = self._normalize_for_compare(cleaned_note)
        if normalized_note in existing_notes:
            return False

        if section_lines and section_lines[-1].strip():
            section_lines.append("")
        section_lines.append(f"- {cleaned_note}")

        updated_lines = lines[: start + 1] + section_lines + lines[end:]
        updated_content = "\n".join(updated_lines).rstrip() + "\n"
        self.memory.write_long_term(updated_content)
        return True

    def _extract_explicit(self, line: str) -> str | None:
        """提取显式记忆指令中的正文。"""
        for pattern in self._EXPLICIT_PATTERNS:
            match = pattern.match(line)
            if not match:
                continue
            note = self._clean_note(match.group(1))
            if note:
                return note
        return None

    def _find_preferences_section(self, lines: list[str]) -> tuple[int | None, int | None]:
        """定位 `## Preferences` 区块的起止索引。"""
        start: int | None = None
        for idx, line in enumerate(lines):
            if line.strip().lower() == self.SECTION_HEADER.lower():
                start = idx
                break
        if start is None:
            return None, None

        end = len(lines)
        for idx in range(start + 1, len(lines)):
            if lines[idx].startswith("## "):
                end = idx
                break
        return start, end

    def _clean_note(self, text: str) -> str:
        """规范化单条偏好。"""
        note = self._normalize(text.strip().strip("\"'`“”‘’"))
        note = note.rstrip("。.!！?？")
        if len(note) < 2:
            return ""
        if len(note) > self.max_note_len:
            return ""
        return note

    @staticmethod
    def _normalize(text: str) -> str:
        """折叠多余空白。"""
        return " ".join(text.split())

    @staticmethod
    def _normalize_for_compare(text: str) -> str:
        """用于去重比较的规范化。"""
        cleaned = MemoryAutoWriter._normalize(text).lower()
        cleaned = cleaned.rstrip("。.!！?？")
        return cleaned

    @staticmethod
    def _looks_like_question(text: str) -> bool:
        """粗粒度判定问句，避免把提问误写成偏好。"""
        lowered = text.lower()
        question_prefixes = ("如何", "怎么", "为什么", "can you", "could you", "what", "how")
        return text.endswith(("?", "？")) or lowered.startswith(question_prefixes)
