"""Action plan write-through utility.

Adds items to the persistent ``action-plan.json`` file in
``system/logs/simulation/``. Used by:

  - ``simulate.py:_save_action_plan`` — after health checks / simulations
  - ``heartbeat.py`` — after SkillSchool ``analyze_zero_effects()``

Deduplicates by item ``id``, prunes old resolved items, and persists
atomically. Safe to call concurrently from multiple async tasks —
uses a module-level lock.
"""

from __future__ import annotations

import asyncio
import json
import logging
from datetime import UTC, datetime
from pathlib import Path

log = logging.getLogger(__name__)

_lock = asyncio.Lock()


def _plan_path(vault_dir: Path) -> Path:
    """Return the action-plan.json path, creating parents if needed."""
    p = vault_dir / "system" / "logs" / "simulation" / "action-plan.json"
    p.parent.mkdir(parents=True, exist_ok=True)
    return p


async def append_action_plan_items(
    vault_dir: Path,
    items: list[dict],
) -> int:
    """Append items to action-plan.json, dedup by id, prune old resolved.

    Returns the number of NEW items actually added (after dedup).
    """
    if not items:
        return 0

    path = _plan_path(vault_dir)

    async with _lock:
        existing: list[dict] = []
        if path.exists():
            try:
                existing = json.loads(path.read_text(encoding="utf-8"))
            except Exception:
                existing = []

        _ACTIVE = {"pending", "info", "applied", "needs_retry"}
        existing_ids = {
            item.get("id")
            for item in existing
            if item.get("status") in _ACTIVE
        }

        added = 0
        for item in items:
            item_id = item.get("id", "")
            if item_id and item_id not in existing_ids:
                item.setdefault("status", "pending")
                item.setdefault("created_at", datetime.now(UTC).isoformat())
                existing.append(item)
                existing_ids.add(item_id)
                added += 1

        # Prune old resolved items (keep only last 20 completed)
        _DONE = {"verified", "rejected"}
        active = [i for i in existing if i.get("status") not in _DONE]
        done = [i for i in existing if i.get("status") in _DONE]
        existing = active + done[-20:]

        path.write_text(
            json.dumps(existing, indent=2, default=str),
            encoding="utf-8",
        )

    if added:
        log.info("Action plan: %d new item(s) added, %d total active", added, len(active))
    return added


def skillschool_proposals_to_plan_items(proposals: list[dict]) -> list[dict]:
    """Convert SkillSchool zero-effect proposals to action plan items.

    Each proposal from ``analyze_zero_effects()`` has: what, why, who,
    how, fix_type, skill, agent, count, severity. This function maps
    them to the action plan schema used by simulate.py and the Agents
    page UI.
    """
    items: list[dict] = []
    ts = datetime.now(UTC).isoformat()
    for p in proposals:
        skill = p.get("skill", "unknown")
        agent = p.get("agent", "unknown")
        items.append({
            "id": f"zero-effect-{skill}-{agent}",
            "what": p.get("what", f"Skill '{skill}' zero-effect"),
            "why": p.get("why", ""),
            "who": p.get("who", "user"),
            "how": p.get("how", []),
            "fix_type": p.get("fix_type", "tune_skill"),
            "fix_detail": {
                "skill": skill,
                "agent": agent,
                "count": p.get("count", 0),
            },
            "severity": "fail" if p.get("severity") == "high" else "warn",
            "status": "pending",
            "agent_key": agent,
            "agent_name": agent,
            "agent_type": "specialist",
            "created_at": ts,
        })
    return items
