"""
Personal connections journal — narrative cross-topic threads (maintenance class).

Facts extracted from consolidation route to User Model ``connections`` via proposals.
This file holds reviewable narrative links (like insights.md), not accepted personal state.
"""

from __future__ import annotations

import hashlib
import os
import re
from datetime import datetime, timezone

from persistence import secure_atomic_write
from utils.path_safety import safe_resolve_path

CONNECTIONS_REL = "memory/connections.md"
_DEFAULT_MAX_BYTES = 65_536
_DEFAULT_PRELOAD_CHARS = 4_096

_HEADER = (
    "# Connections Journal\n\n"
    "Cross-topic threads Mugi has noticed across conversations and work. "
    "Reviewable narrative — durable facts go through the You pane as proposals.\n"
)


def _journal_path(workspace: str, *, for_write: bool = False) -> str | None:
    abs_path, err = safe_resolve_path(
        workspace,
        CONNECTIONS_REL,
        allow_write=for_write,
        create_parents=for_write,
    )
    if err:
        return None
    return abs_path


def read_connections_journal_tail(workspace: str, max_chars: int = _DEFAULT_PRELOAD_CHARS) -> str:
    """Return the tail of the connections journal for preload, or empty string."""
    path = _journal_path(workspace, for_write=False)
    if not path or not os.path.isfile(path):
        return ""
    try:
        with open(path, encoding="utf-8", errors="replace") as f:
            data = f.read()
    except OSError:
        return ""
    if len(data) <= max_chars:
        return data.strip()
    return data[-max_chars:].lstrip()


def _line_hash(line: str) -> str:
    norm = re.sub(r"\s+", " ", (line or "").strip().lower())
    return hashlib.sha256(norm.encode("utf-8")).hexdigest()[:16]


def append_connections_journal(
    workspace: str,
    lines: list[str],
    *,
    source: str = "consolidation",
    max_bytes: int | None = None,
    skip_salience: bool = False,
) -> int:
    """Append dated narrative bullets; salience-dedupe per line. Returns count appended."""
    if not (workspace or "").strip():
        return 0
    cleaned: list[str] = []
    for raw in lines:
        text = re.sub(r"^\s*[-*]\s+", "", str(raw or "").strip()).strip()
        if len(text) < 14:
            continue
        if not skip_salience:
            key = f"connections_journal:{_line_hash(text)}"
            try:
                from utils.proactive_queue import should_emit_proactive_notification

                if not should_emit_proactive_notification(key, text):
                    continue
            except Exception:
                continue
        cleaned.append(text[:800])
    if not cleaned:
        return 0

    path = _journal_path(workspace, for_write=True)
    if not path:
        return 0

    cap = int(max_bytes or _DEFAULT_MAX_BYTES)
    now = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    block_lines = [f"\n## {now} — {source}\n"]
    for text in cleaned:
        block_lines.append(f"- {text}\n")
    block = "".join(block_lines)

    existing = ""
    if os.path.isfile(path):
        try:
            with open(path, encoding="utf-8", errors="replace") as f:
                existing = f.read()
        except OSError:
            existing = ""

    if not existing.strip():
        content = _HEADER + block
    else:
        content = existing.rstrip() + "\n" + block

    if len(content.encode("utf-8")) > cap:
        content = content.encode("utf-8")[-cap:].decode("utf-8", errors="ignore")
        if not content.lstrip().startswith("#"):
            content = _HEADER + content

    try:
        secure_atomic_write(path, content)
        os.chmod(path, 0o600)
    except Exception:
        try:
            with open(path, "w", encoding="utf-8") as f:
                f.write(content)
            os.chmod(path, 0o600)
        except OSError:
            return 0
    return len(cleaned)


def extract_related_topics(text: str, *, max_topics: int = 6) -> list[str]:
    """Light keyword extraction for connection ``related_to`` metadata."""
    cleaned = str(text or "")
    for pattern, repl in (
        (r"\$\\leftrightarrow\$", " "),
        (r"\\leftrightarrow", " "),
        (r"↔", " "),
    ):
        cleaned = re.sub(pattern, repl, cleaned, flags=re.IGNORECASE)
    stop = {
        "the", "and", "with", "that", "this", "from", "into", "about", "user",
        "often", "together", "links", "ties", "work", "related", "between",
        "leftrightarrow", "rightarrow", "leftarrow",
    }
    tokens = re.findall(r"[a-zA-Z][a-zA-Z0-9_-]{2,}", cleaned.lower())
    out: list[str] = []
    for tok in tokens:
        if tok in stop or tok in out:
            continue
        out.append(tok)
        if len(out) >= max_topics:
            break
    return out
