"""MARPA cognitive framework (Mappa, Aree, Risorse, Progetti, Archivio)."""

from __future__ import annotations

import re
from pathlib import Path
from typing import Literal

from ...agent.plumber_config import PlumberLintConfig, apply_thermal_pause_cognitive
from ...graph.generational_cache import patch_generational_caches_for_paths
from ...graph.markdown_blocks import (
    atomic_write_bytes,
    atomic_write_bytes_if_unchanged,
    file_mtime_drifted,
    occ_snapshot,
)
from ...graph.page_properties import inject_page_properties, page_property_keys
from ...graph.page_write_lock import page_rmw_lock
from ...graph.path_sandbox import read_graph_file_text, resolved_graph_root
from ..plumber_llm import MarpaClassificationResult
from ..prompt_constraints import finalize_system_prompt
from ..prompt_layout import build_cache_aligned_prompt
from ._shared import ModuleOutcome, is_blank_page_content

MarpaDomain = Literal["mappa", "area", "risorsa", "progetto", "archivio"]

MARPA_DOMAIN_DEFINITIONS = (
    "- mappa: Strategic vision layer — long-horizon direction, V2MOM, north-star metrics, "
    "and portfolio OKRs. Excludes day-to-day operational execution.\n"
    "- area: Continuous operational domain — ongoing responsibilities, standards, and rhythms "
    "without a fixed end date or hard deliverable deadline.\n"
    "- risorsa: Timeless intellectual capital — durable reference material, specifications, "
    "frameworks, and reusable assets not tied to a single initiative.\n"
    "- progetto: Time-bounded initiative — tactical work with explicit deadlines, milestones, "
    "and deliverables requiring completion or cancellation.\n"
    "- archivio: Closed or dormant node — completed, cancelled, or superseded material removed "
    "from active planning surfaces."
)

_MARPA_CLASSIFY_SYSTEM_INSTRUCTIONS = (
    "You are the MARPA semantic taxonomy compiler for Logseq OG. Return JSON only. "
    "Assign exactly one domain from the user-provided catalog. "
    "Populate inferred_properties for missing structural fields (deadline, status, owner). "
    "Use standardized Logseq property keys; leave values empty when unknown."
)


def build_marpa_classify_system_prompt() -> str:
    """System prompt for MARPA domain classification (English instructions, multilingual output)."""
    return finalize_system_prompt(_MARPA_CLASSIFY_SYSTEM_INSTRUCTIONS)


def build_marpa_classify_user_prompt(
    page_title: str,
    content: str,
    *,
    namespace_hint: str | None,
    max_content_chars: int = 7000,
) -> str:
    """User prompt for MARPA domain classification."""
    hint = namespace_hint or "(none)"
    task = (
        "Task: classify the page content above into exactly one MARPA domain "
        "using these definitions:\n"
        f"{MARPA_DOMAIN_DEFINITIONS}\n\n"
        f"Page title: {page_title}\n"
        f"Namespace hint: {hint}"
    )
    return build_cache_aligned_prompt(
        content=content[:max_content_chars],
        task_instruction=task,
    )


_BULLET = re.compile(r"^(\s*)[-*+]\s+(.*)$")
_MIN_DUP_CHARS = 80
_MARPA_HEADING = "### Matryca MARPA Validation"
_MARPA_HEADER = f"- {_MARPA_HEADING}"


def detect_marpa_namespace(page_title: str) -> str | None:
    """Return namespace segment from ``Area/Topic`` or ``Area___Topic`` titles."""
    if "/" in page_title:
        return page_title.split("/", 1)[0].casefold()
    if "___" in page_title:
        return page_title.split("___", 1)[0].casefold()
    return None


def _has_top_level_type(content: str) -> bool:
    return "type" in page_property_keys(content)


def _scan_ssot_duplication(graph_root: Path, page_path: Path, content: str) -> list[str]:
    """Flag large verbatim blocks that appear to duplicate another page (SSoT guard)."""
    flags: list[str] = []
    blocks: list[str] = []
    for line in content.splitlines():
        match = _BULLET.match(line.rstrip("\n"))
        if match and len(match.group(2)) >= _MIN_DUP_CHARS:
            blocks.append(match.group(2).strip())
    if not blocks:
        return flags

    try:
        self_text = read_graph_file_text(page_path, graph_root)
    except OSError:
        self_text = content
    from ...graph.alias_index import is_scannable_graph_markdown

    root = resolved_graph_root(graph_root)
    pages_dir = root / "pages"
    if not pages_dir.is_dir():
        return flags

    for other in pages_dir.rglob("*.md"):
        if not other.is_file() or not is_scannable_graph_markdown(other, root):
            continue
        if other.resolve() == page_path.resolve():
            continue
        try:
            other_text = read_graph_file_text(other, root)
        except OSError:
            continue
        for block in blocks:
            if block in other_text and block not in self_text.replace(block, "", 1):
                rel = other.relative_to(root).as_posix()
                flags.append(f"ssot_duplicate:{rel}")
                break
    return flags


def _inject_type_and_properties(
    text: str,
    domain: MarpaDomain,
    inferred: dict[str, str],
) -> str:
    """Insert ``type::`` and inferred properties as page-level frontmatter."""
    props: dict[str, str] = {"type": domain}
    for key, value in inferred.items():
        if key.casefold() == "type":
            continue
        if value.strip():
            props[key] = value.strip()
    return inject_page_properties(text, props)


def _strip_marpa_validation_section(text: str) -> str:
    """Remove a trailing MARPA validation section so it can be upserted idempotently."""
    lines = text.splitlines(keepends=True)
    for i in range(len(lines) - 1, -1, -1):
        body = lines[i].strip()
        if body.startswith("- "):
            body = body[2:].strip()
        if body == _MARPA_HEADING:
            prefix = "".join(lines[:i]).rstrip("\n")
            return f"{prefix}\n" if prefix else ""
    return text


def _upsert_validation_section(
    lines: list[str],
    classification: MarpaClassificationResult,
    ssot_flags: list[str],
) -> None:
    """Replace any existing MARPA validation block, then append a fresh report."""
    body = _strip_marpa_validation_section("".join(lines))
    lines.clear()
    if body:
        lines.extend(body.splitlines(keepends=True))
    section_lines = [
        "",
        _MARPA_HEADER,
        f"- assigned-domain:: {classification.assigned_domain}",
    ]
    if classification.detected_tags:
        section_lines.append(
            "- detected-tags:: "
            + " ".join(f"#{t.lstrip('#')}" for t in classification.detected_tags),
        )
    if ssot_flags or classification.violates_ssot_duplication:
        section_lines.append("- ssot-warnings::")
        for flag in ssot_flags:
            section_lines.append(f"  - {flag} (prefer ((block-uuid)) transclusion)")
    section_lines.append("")
    lines.append("\n".join(section_lines))


def run_marpa_framework(
    graph_root: Path,
    page_path: Path,
    page_title: str,
    content: str,
    *,
    llm: object,
    config: PlumberLintConfig | None = None,
    llm_context: str | None = None,
) -> ModuleOutcome:
    """Classify page into MARPA domain and inject ``type::`` metadata safely."""
    outcome = ModuleOutcome()
    if is_blank_page_content(content):
        return outcome
    if not hasattr(llm, "classify_marpa_page"):
        return outcome

    namespace = detect_marpa_namespace(page_title)
    ssot_flags = _scan_ssot_duplication(graph_root, page_path, content)
    has_type = _has_top_level_type(content)
    baseline_mtime = occ_snapshot(page_path) if page_path.is_file() else None

    if has_type:
        domain: MarpaDomain = "risorsa"
        existing_type = page_property_keys(content).get("type", "").casefold()
        if existing_type in {"mappa", "area", "risorsa", "progetto", "archivio"}:
            domain = existing_type  # type: ignore[assignment]
        classification = MarpaClassificationResult(assigned_domain=domain)
    else:
        llm_body = (llm_context if llm_context is not None else content)[:8000]
        classification = llm.classify_marpa_page(
            page_title=page_title,
            content=llm_body,
            namespace_hint=namespace,
            page_path=page_path,
            graph_root=graph_root,
        )
        apply_thermal_pause_cognitive(config)
    if ssot_flags:
        classification.violates_ssot_duplication = True

    with page_rmw_lock(page_path):
        if page_path.is_file():
            text = read_graph_file_text(page_path, graph_root, errors="replace")
        else:
            text = content
        if baseline_mtime is not None and file_mtime_drifted(page_path, baseline_mtime):
            return outcome
        if is_blank_page_content(text):
            return outcome
        lines = text.splitlines(keepends=True)
        if not has_type:
            text = _inject_type_and_properties(
                text,
                classification.assigned_domain,
                classification.inferred_properties,
            )
            lines = text.splitlines(keepends=True)
        _upsert_validation_section(lines, classification, ssot_flags)
        new_text = "".join(lines)
        if new_text == text:
            return outcome
        if baseline_mtime is not None and not atomic_write_bytes_if_unchanged(
            page_path,
            new_text.encode("utf-8"),
            graph_root=graph_root,
            baseline_mtime=baseline_mtime,
        ):
            return outcome
        if baseline_mtime is None:
            atomic_write_bytes(page_path, new_text.encode("utf-8"), graph_root=graph_root)

    outcome.pages_modified.append(page_title)
    outcome.details.append(f"marpa:{classification.assigned_domain}")
    if classification.violates_ssot_duplication:
        outcome.details.append("marpa:ssot_flagged")
    patch_generational_caches_for_paths(graph_root, [page_path])
    return outcome


__all__ = [
    "MarpaDomain",
    "MARPA_DOMAIN_DEFINITIONS",
    "build_marpa_classify_system_prompt",
    "build_marpa_classify_user_prompt",
    "detect_marpa_namespace",
    "run_marpa_framework",
]
