"""Task-plan Activity bus helpers: dedupe, redacted summaries, emit wrappers.

Used when the continuation guard exhausts or when stale-plan reconciliation
fires. Shares a per-thread short TTL dedupe so guard_exhaust + stale do not
double-flood the Activity feed.
"""
from __future__ import annotations

import logging
import threading
import time
from typing import Any

from utils.redaction import cap_str

_log = logging.getLogger(__name__)

# Same-process dedupe window (seconds) for any task_plan-family Activity row.
_TASK_PLAN_ACTIVITY_DEDUPE_SEC = 60.0
_dedupe_lock = threading.Lock()
_last_emit_mono: dict[str, float] = {}

SOURCE_TASK_PLAN = "task_plan"


def try_acquire_task_plan_activity_slot(thread_id: str) -> bool:
    """Return True if caller may emit a task_plan Activity event now.

    One slot per *thread_id* within :data:`_TASK_PLAN_ACTIVITY_DEDUPE_SEC` wall
    clock using monotonic deltas (process-local).
    """
    tid = (thread_id or "").strip()
    if not tid:
        return False
    now = time.monotonic()
    with _dedupe_lock:
        prev = _last_emit_mono.get(tid)
        if prev is not None and (now - prev) < _TASK_PLAN_ACTIVITY_DEDUPE_SEC:
            return False
        _last_emit_mono[tid] = now
        return True


def plan_step_counts(plan: dict[str, Any] | None) -> tuple[int, int]:
    """Return (open_step_count, in_progress_count) for *plan* dict."""
    if not plan or not isinstance(plan, dict):
        return 0, 0
    steps = plan.get("steps")
    if not isinstance(steps, list):
        return 0, 0
    open_n = 0
    in_prog = 0
    terminal = frozenset({"done", "skipped", "blocked"})
    for step in steps:
        if not isinstance(step, dict):
            open_n += 1
            continue
        raw = step.get("status")
        st = raw.strip().lower() if isinstance(raw, str) else ""
        if st == "in_progress":
            in_prog += 1
        if st not in terminal:
            open_n += 1
    return open_n, in_prog


def build_guard_exhaust_summary(
    *,
    open_steps: int,
    in_progress_steps: int,
    reason: str,
    cap_value: int,
) -> str:
    """Aggregate-only summary for Activity (no raw step text)."""
    parts = [
        "Task plan still has open work; continuation guard stopped after retries.",
        f"Open steps: {open_steps} ({in_progress_steps} in progress).",
        f"Reason: {reason} (cap {cap_value}).",
    ]
    raw = " ".join(parts)
    return cap_str(raw, 500)


def emit_task_plan_guard_exhausted_activity(
    *,
    thread_id: str,
    plan: dict[str, Any] | None,
    reason: str,
    cap_value: int,
) -> None:
    """Emit Activity when the agent loop allows final answer but plan is open."""
    tid = (thread_id or "").strip()
    if not tid:
        return
    if not try_acquire_task_plan_activity_slot(tid):
        return
    open_n, in_prog = plan_step_counts(plan)
    try:
        from activity_bus import emit_activity_enriched

        summary = build_guard_exhaust_summary(
            open_steps=open_n,
            in_progress_steps=in_prog,
            reason=reason,
            cap_value=cap_value,
        )
        emit_activity_enriched(
            {
                "source": SOURCE_TASK_PLAN,
                "state": "guard_exhausted",
                "summary": summary,
                "thread_id": tid,
                "meta": {
                    "open_step_count": open_n,
                    "in_progress_count": in_prog,
                    "cap": cap_value,
                    "reason": reason[:80],
                },
            },
            tags=["task_plan"],
        )
    except Exception:
        _log.debug("emit_task_plan_guard_exhausted_activity failed", exc_info=True)


def emit_task_plan_stale_activity(*, thread_id: str, summary: str, meta: dict[str, Any]) -> None:
    """Emit Activity for stale / stranded plan signal (idle-tick path)."""
    tid = (thread_id or "").strip()
    if not tid:
        return
    if not try_acquire_task_plan_activity_slot(tid):
        return
    try:
        from activity_bus import emit_activity_enriched

        emit_activity_enriched(
            {
                "source": SOURCE_TASK_PLAN,
                "state": "stale_plan",
                "summary": cap_str(summary, 500),
                "thread_id": tid,
                "meta": meta,
            },
            tags=["task_plan"],
        )
    except Exception:
        _log.debug("emit_task_plan_stale_activity failed", exc_info=True)
