"""Infer and append missing structured properties for tagged pages (Tana-style)."""

from __future__ import annotations

from pathlib import Path

import yaml

from ...agent.plumber_config import PlumberLintConfig, apply_thermal_pause_cognitive
from ...graph.markdown_blocks import (
    atomic_write_bytes_if_unchanged,
    file_mtime_drifted,
    occ_snapshot,
)
from ...graph.page_properties import inject_page_properties
from ...graph.page_write_lock import page_rmw_lock
from ...graph.path_sandbox import read_graph_file_text
from ._shared import ModuleOutcome, extract_inline_tags, is_blank_page_content, page_property_keys

_DEFAULT_RULES: dict[str, list[str]] = {
    "project": ["status", "deadline"],
    "person": ["role", "email"],
    "meeting": ["date", "attendees"],
}


def _load_tag_rules(rules_path: Path | None) -> dict[str, list[str]]:
    if rules_path is None or not rules_path.is_file():
        return dict(_DEFAULT_RULES)
    try:
        raw = yaml.safe_load(rules_path.read_text(encoding="utf-8"))
    except (OSError, yaml.YAMLError):
        return dict(_DEFAULT_RULES)
    if not isinstance(raw, dict):
        return dict(_DEFAULT_RULES)
    tag_rules = raw.get("tag_rules", raw)
    if not isinstance(tag_rules, dict):
        return dict(_DEFAULT_RULES)
    out: dict[str, list[str]] = {}
    for tag, spec in tag_rules.items():
        if isinstance(spec, dict):
            req = spec.get("required_properties", spec.get("properties", []))
        elif isinstance(spec, list):
            req = spec
        else:
            continue
        if isinstance(req, list):
            keys = [str(k).strip().lower() for k in req if str(k).strip()]
            if keys:
                out[str(tag).lstrip("#").casefold()] = keys
    return out or dict(_DEFAULT_RULES)


def run_property_hygiene(
    graph_root: Path,
    page_path: Path,
    page_title: str,
    content: str,
    *,
    llm: object,
    rules_path: Path | None,
    infer_missing: bool,
    config: PlumberLintConfig | None = None,
    llm_context: str | None = None,
) -> ModuleOutcome:
    """Append missing ``key:: value`` property lines inferred from page context."""
    outcome = ModuleOutcome()
    if is_blank_page_content(content):
        return outcome
    rules = _load_tag_rules(rules_path)
    tags = extract_inline_tags(content)
    if not tags:
        return outcome

    existing = page_property_keys(content)
    missing_by_tag: dict[str, list[str]] = {}
    for tag in tags:
        required = rules.get(tag, [])
        missing = [key for key in required if key not in existing or not existing[key].strip()]
        if missing:
            missing_by_tag[tag] = missing

    if not missing_by_tag:
        return outcome

    baseline_mtime = occ_snapshot(page_path) if page_path.is_file() else None

    llm_body = llm_context if llm_context is not None else content

    inferred: dict[str, str] = {}
    if infer_missing and hasattr(llm, "infer_tag_properties"):
        for tag, keys in missing_by_tag.items():
            result = llm.infer_tag_properties(
                tag=tag,
                required_keys=keys,
                page_title=page_title,
                content=llm_body[:6000],
            )
            apply_thermal_pause_cognitive(config)
            for key, value in result.properties.items():
                norm_key = key.strip().lower()
                if norm_key in keys and value.strip():
                    inferred[norm_key] = value.strip()
    else:
        for _tag, keys in missing_by_tag.items():
            for key in keys:
                inferred.setdefault(key, "unknown")

    if not inferred:
        return outcome

    with page_rmw_lock(page_path):
        text = read_graph_file_text(page_path, graph_root, errors="replace")
        if is_blank_page_content(text):
            return outcome
        if baseline_mtime is not None and file_mtime_drifted(page_path, baseline_mtime):
            return outcome
        if baseline_mtime is None:
            baseline_mtime = occ_snapshot(page_path)
        if baseline_mtime is None:
            return outcome
        new_text = inject_page_properties(text, inferred)
        if new_text == text:
            return outcome
        if not atomic_write_bytes_if_unchanged(
            page_path,
            new_text.encode("utf-8"),
            graph_root=graph_root,
            baseline_mtime=baseline_mtime,
        ):
            return outcome

    outcome.pages_modified.append(page_title)
    outcome.details.append(f"properties:{','.join(sorted(inferred))}")
    return outcome


__all__ = ["run_property_hygiene"]
