"""VP assembly_master.md — read / append / merge by ## heading."""

from __future__ import annotations

import re
from pathlib import Path
from typing import TYPE_CHECKING, Any

from niannian.virtual_persona.profile_md import parse_profile_sections

if TYPE_CHECKING:
    from niannian.virtual_persona.store import VirtualPersonaStore

ASSEMBLY_MASTER = "assembly_master.md"
DEFAULT_TITLE = "Assembly Master"
DAILY_NOTES_HEADING = "Daily notes"
DEFAULT_DAILY_APPEND_CHARS = 2000
PROACTIVE_ASSEMBLY_EXCERPT_MAX = 800


def truncate_assembly_excerpt(text: str, max_chars: int = PROACTIVE_ASSEMBLY_EXCERPT_MAX) -> str:
    """Truncate assembly_master for proactive context (SSE / reflect)."""
    s = (text or "").strip()
    if len(s) <= max_chars:
        return s
    return s[: max_chars - 1].rstrip() + "…"

_SECTION_RE = re.compile(r"^## (.+)$", re.MULTILINE)


def assembly_master_path(store: VirtualPersonaStore, vp_id: str) -> Path:
    return store.assembly_master_path(vp_id)


def read_assembly_master(store: VirtualPersonaStore, vp_id: str) -> str:
    path = assembly_master_path(store, vp_id)
    if path.is_file():
        return path.read_text(encoding="utf-8")
    return ""


def parse_assembly_sections(md: str) -> tuple[str, dict[str, str]]:
    text = str(md or "")
    title = DEFAULT_TITLE
    for line in text.splitlines():
        if line.startswith("# ") and not line.startswith("## "):
            title = line[2:].strip() or title
            break

    sections: dict[str, str] = {}
    matches = list(_SECTION_RE.finditer(text))
    for idx, match in enumerate(matches):
        name = match.group(1).strip()
        start = match.end()
        end = matches[idx + 1].start() if idx + 1 < len(matches) else len(text)
        sections[name] = text[start:end].strip()
    return title, sections


def _format_document(title: str, sections: dict[str, str], *, order: list[str] | None = None) -> str:
    keys = order if order is not None else list(sections.keys())
    seen: set[str] = set()
    lines = [f"# {title}", ""]
    for heading in keys:
        if heading in sections:
            lines.append(f"## {heading}")
            lines.append(sections[heading])
            lines.append("")
            seen.add(heading)
    for heading, body in sections.items():
        if heading in seen:
            continue
        lines.append(f"## {heading}")
        lines.append(body)
        lines.append("")
    return "\n".join(lines).rstrip() + "\n"


def _section_from_item(item: dict[str, Any]) -> tuple[str, str]:
    heading = str(item.get("heading") or item.get("topic") or item.get("section") or "").strip()
    body = str(item.get("body") or item.get("summary") or item.get("draft_body") or "").strip()
    return heading, body


def _default_title_from_profile(store: VirtualPersonaStore, vp_id: str) -> str:
    row = store.get(vp_id)
    label = str(row.get("label") or vp_id)
    parsed = parse_profile_sections(str(row.get("profile_md") or ""))
    return str(parsed.get("__title__") or label)


def append_section(store: VirtualPersonaStore, vp_id: str, heading: str, body: str) -> None:
    path = assembly_master_path(store, vp_id)
    section = f"## {heading}\n\n{body.strip()}\n"
    if path.is_file():
        existing = path.read_text(encoding="utf-8")
        marker = f"## {heading}"
        if marker in existing:
            return
        with path.open("a", encoding="utf-8") as f:
            if not existing.endswith("\n"):
                f.write("\n")
            f.write("\n" + section)
    else:
        title = _default_title_from_profile(store, vp_id)
        path.write_text(f"# {title}\n\n{section}", encoding="utf-8")


def _normalize_locked_headings(locked_sections: list[str] | None) -> set[str]:
    locked: set[str] = set()
    for item in locked_sections or []:
        name = str(item).strip()
        if name.startswith("## "):
            name = name[3:].strip()
        elif name.startswith("#"):
            name = name.lstrip("#").strip()
        if name:
            locked.add(name)
    return locked


def append_section_body(
    store: VirtualPersonaStore,
    vp_id: str,
    heading: str,
    append_body: str,
    *,
    max_append_chars: int = DEFAULT_DAILY_APPEND_CHARS,
    locked_sections: list[str] | None = None,
) -> bool:
    """Append to an existing ## section or create it. Returns False if section is locked."""
    if heading in _normalize_locked_headings(locked_sections):
        return False

    body = str(append_body or "").strip()
    if not body:
        return False
    limit = int(max_append_chars)
    if limit > 0 and len(body) > limit:
        body = body[: limit - 3] + "..."

    existing = read_assembly_master(store, vp_id)
    if existing:
        doc_title, parsed = parse_assembly_sections(existing)
    else:
        doc_title = _default_title_from_profile(store, vp_id)
        parsed = {}

    prev = str(parsed.get(heading) or "").strip()
    parsed[heading] = f"{prev}\n\n{body}".strip() if prev else body

    content = _format_document(doc_title, parsed)
    path = assembly_master_path(store, vp_id)
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(content, encoding="utf-8")
    return True


def merge_deep_learn_results(
    store: VirtualPersonaStore,
    vp_id: str,
    sections: list[dict[str, Any]],
    *,
    title: str | None = None,
    order: list[str] | None = None,
) -> str:
    existing = read_assembly_master(store, vp_id)
    doc_title, parsed = parse_assembly_sections(existing) if existing else (title or DEFAULT_TITLE, {})
    if title:
        doc_title = title

    for item in sections:
        heading, body = _section_from_item(item)
        if heading and body:
            parsed[heading] = body

    content = _format_document(doc_title, parsed, order=order)
    path = assembly_master_path(store, vp_id)
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(content, encoding="utf-8")
    return content
