"""
Chat → Kanban handoff for multi-step builds (Point 6).

Foreground chat is a single volatile context; the kanban board gives the same
work fresh-context runs, retries, and structured handoff state. These helpers
let the agent loop (a) notice when a chat run has become a multi-step build,
(b) nudge the model to file the work on the board, (c) grant one last-chance
handoff round at the hard-stop/max-rounds cliff, and (d) subscribe the origin
thread to terminal task events so results flow back to the conversation.

Everything here is best-effort and never raises: a failed handoff degrades to
today's behavior (the run ends in chat), never to a broken run.
"""

from __future__ import annotations

import logging
import re

_log = logging.getLogger(__name__)

# Tools whose execution marks a run as an iterative build (writes, script
# runs, skill/plugin authoring, shell). Read/search tools deliberately
# excluded — research loops have their own harness.
BUILD_SIGNATURE_TOOLS: frozenset[str] = frozenset({
    "write_text_file",
    "append_to_text_file",
    "save_to_file",
    "write_web_act_file",
    "run_web_script",
    "skill_manage",
    "shell_execute",
    "create_plugin",
    "create_dashboard_plugin",
    "validate_plugin",
    "run_installed_cli",
    "install_cli_package",
})

# The only tools offered in the last-chance handoff round.
KANBAN_HANDOFF_TOOL_NAMES: tuple[str, ...] = ("kanban_create_batch", "kanban_create_task")

_TASK_ID_RE = re.compile(r"(?m)\bid=(\S+) status=")


def kanban_available() -> bool:
    """True when the kanban kill switch is on (board accepts tasks)."""
    try:
        from core.config import get_kanban_config

        return bool(get_kanban_config().get("enabled", True))
    except Exception:
        return False


def parse_created_task_ids(result: str) -> list[str]:
    """Extract task ids from kanban_create_task / kanban_create_batch results.

    Both tools render rows via ``_format_task_summary`` (``id=<id> status=...``),
    which is the stable contract this parser relies on.
    """
    text = str(result or "")
    if not text.startswith("Created"):
        return []
    return _TASK_ID_RE.findall(text)


def auto_subscribe_origin_thread(thread_id: str, task_ids: list[str]) -> list[str]:
    """Subscribe *thread_id* to terminal events of the **root** tasks in *task_ids*.

    Child tasks are skipped (``done`` only fires for roots anyway; subscribing
    them would only add gave_up noise rows). Returns the ids actually
    subscribed. Never raises.
    """
    subscribed: list[str] = []
    tid = (thread_id or "").strip()
    if not tid:
        return subscribed
    try:
        import kanban_storage as ks
        from utils.kanban_notifications import kanban_notify_subscribe_if_absent

        for task_id in task_ids or []:
            t = (task_id or "").strip()
            if not t:
                continue
            try:
                if ks.get_parent_ids(t):
                    continue  # child task — terminal events roll up to the root
                kanban_notify_subscribe_if_absent(
                    t, channel="thread", target=tid, fire_on=("done", "gave_up")
                )
                subscribed.append(t)
            except Exception:
                _log.debug("auto-subscribe failed for task %s", t, exc_info=True)
    except Exception:
        _log.debug("auto-subscribe unavailable", exc_info=True)
    return subscribed


def format_build_handoff_nudge() -> str:
    """One-shot mid-run nudge: this chat run is a multi-step build; file it."""
    return (
        "[Mugi guard] This has become a multi-step build (multiple write/run "
        "iterations). A single chat context cannot hold a build like this "
        "durably — file it on the kanban board now so it gets fresh-context "
        "runs with retries:\n"
        "1. Call kanban_create_batch with the goal, what you have learned so "
        "far (key discoveries, file paths, what failed and why), acceptance "
        "criteria, and the remaining steps.\n"
        "2. Then tell the user you have moved the work to the board and that "
        "results will be reported back here.\n"
        "If you are genuinely one small step from finishing, you may finish "
        "instead — but do not start another write/debug cycle in this chat."
    )


def format_last_chance_handoff_prompt(run_log_tail: str, reason: str) -> str:
    """The final-round prompt at the hard-stop / max-rounds cliff.

    Only kanban create tools are offered this round; the model must preserve
    the work as a durable task instead of letting it die with the context.
    """
    log_block = (run_log_tail or "").strip()
    if log_block:
        log_block = (
            "\n\nYour run log so far (include the relevant parts in the task "
            "body so the worker does not repeat your dead ends):\n" + log_block
        )
    return (
        f"[Mugi guard] This chat run is ending ({reason}), but the build is "
        "unfinished. You have ONE more step, and only kanban task creation is "
        "available: call kanban_create_batch (or kanban_create_task) with the "
        "original goal, your discoveries, exact file paths, what failed and "
        "why, and the remaining steps — so the work continues with fresh "
        "context instead of dying here. Then briefly tell the user the work "
        "has moved to the board."
        + log_block
    )


def format_handoff_confirmation(task_ids: list[str]) -> str:
    """Synthesized user-facing close when the model filed tasks but wrote no prose."""
    n = len(task_ids or [])
    if n == 1:
        return (
            "I've hit the limit of what one conversation context can hold for this "
            "build, so I filed it on the task board to continue with a fresh start "
            "and what I've learned so far. I'll report back here when it completes."
        )
    return (
        f"I've hit the limit of what one conversation context can hold for this "
        f"build, so I filed {n} tasks on the board to continue the work with fresh "
        "context and my notes so far. I'll report back here as they complete."
    )
