"""Ephemeral message generator for Modal Workflow System.

Aligned with Antigravity's ephemeral prompt design while maintaining
generic mode/artifact support for multi-scenario use cases.
"""

import os
from .task_state import ConversationState, ArtifactRoles

# =============================================================================
# Task State Reminders (mutually exclusive)
# =============================================================================

ACTIVE_TASK_REMINDER = """\
<active_task_reminder>
Remember to update the task as appropriate. The current task is:
task_name:"{task_name}" task_status:"{task_status}" task_summary:"{task_summary}" mode:{ctx_mode}
Tools since last update: {tools_since_update}
Task status changes: {task_update_count}

TASK UPDATE GUIDELINES:
- Update task_boundary only when entering a NEW work phase — NOT with every tool call
- Do not update the status too frequently, a work phase typically spans around 5 tool calls; update status/summary accordingly, Never make two status updates in a row without doing anything in between.
- If calling task_boundary with other tools in parallel, list task_boundary FIRST
- Use %SAME% for unchanged fields (task_name, mode, task_status, task_summary)
- CRITICAL REMINDER: task_status describes NEXT STEPS; task_summary describes what you've DONE

YOUR CURRENT MODE IS: {ctx_mode}. Embody this mindset.
REMEMBER: User WILL NOT SEE your messages. Use notify_user to communicate.
</active_task_reminder>"""

NO_ACTIVE_TASK_REMINDER = """\
<no_active_task_reminder>
You are currently not in a task because: {reason}
If there is no obvious task from the user or if you are just conversing, then it is acceptable to not have a task set. If you are just handling simple one-off requests, such as explaining a single file, or making one or two ad-hoc code edit requests, or making an obvious refactoring request such as renaming or moving code into a helper function, it is also acceptable to not have a task set.
Otherwise, you should use the task_boundary tool to set a task if there is one evident.
Remember that task boundaries should correspond to the artifact task.md, if you have not created the artifact task.md, you should do that first before setting the task_boundary. Remember that task names should be granular and correspond to top-level checklist items, not the entire user request as one task name. If you decide to use the task boundary tool, you must do so concurrently with other tools.
Since you are NOT in an active task section, DO NOT call the `notify_user` tool unless you are requesting review of files.
</no_active_task_reminder>"""

# Bridges the gap between `active_task` (the in-memory state that drives this
# message) and task.md (the visible checklist). The agent can close its active
# task while task.md still has open items — then the reminder above wrongly reads
# "it's fine to have no task", nobody nudges the agent to finish, and it either
# leaves todos dangling or spins. Surfacing the open items keeps it honest.
UNFINISHED_TODOS_REMINDER = """\
<unfinished_todos_reminder>
You have no active task, but task.md still has UNFINISHED checklist items:
{items}
The work is NOT complete yet. For each item: finish it and mark it [x] in task.md,
or — if it genuinely cannot or should not be done — mark it [x] with a short
"(skipped: reason)" note. Do not give your final wrap-up to the user until task.md
has no remaining [ ] or [/] items. Take a concrete action now; do not just think.
</unfinished_todos_reminder>"""

# =============================================================================
# Artifact Reminders
# =============================================================================

ARTIFACT_REMINDER_HAS_ARTIFACTS = """\
<artifact_reminder>
You have created the following artifacts in this conversation so far, here are the artifact paths:
{artifacts}
CRITICAL REMINDER: remember that user-facing artifacts should be AS CONCISE AS POSSIBLE. Keep this in mind when editing artifacts
</artifact_reminder>"""

ARTIFACT_REMINDER_NO_ARTIFACTS = """\
<artifact_reminder>
You have not yet created any artifacts. Please follow the artifact guidelines and create them as needed based on the task.
CRITICAL REMINDER: remember that user-facing artifacts should be AS CONCISE AS POSSIBLE. Keep this in mind when editing artifacts.
Artifacts should be written to: {brain_dir}
</artifact_reminder>"""

# =============================================================================
# Conditional Reminders
# =============================================================================

PLAN_ARTIFACT_MODIFIED_REMINDER = """\
<plan_artifact_modified_reminder>
You have modified {files} during this task in {ctx_mode} mode. Before you switch to execution/analysis mode, you should notify and request the user to review your plan changes via notify_user.
</plan_artifact_modified_reminder>"""

CONFIRM_OPEN_CHOICES_REMINDER = """\
<confirm_open_choices>
You are still PLANNING and have NOT yet asked the user anything. Before you commit to an approach or run anything expensive (downloading a dataset, long compute): did the request leave a CONSEQUENTIAL choice that is the user's to make — most often WHICH specific dataset/sample to analyze, but also which method/scope when it changes the outcome? Vague phrasing like "find some data" / "any dataset" does NOT waive this — it means "propose one and let me confirm", NOT "pick silently and run". If such a choice is open, use notify_user(blocked_on_user=true) to present your specific proposal + 1-2 alternatives and get a quick OK FIRST. If the request already pinned every consequential choice, just proceed — don't ask for pointless confirmation.
</confirm_open_choices>"""

ARTIFACTS_MODIFIED_REMINDER = """\
<artifacts_modified_reminder>
You have modified {count} artifact(s) in this task.
Consider updating them as you progress.
</artifacts_modified_reminder>"""

REQUESTED_REVIEW_NOT_IN_TASK_REMINDER = """\
<requested_review_not_in_task_reminder>
You have used notify_user with {reviewed_files} but haven't set a task boundary since. Based on user intent you should either: (1) Enter planning mode to update the plan (feel free to do additional research based on user feedback), OR (2) Enter execution mode and proceed to implement. Under no circumstances should you update plan artifacts when you're not in a task.
</requested_review_not_in_task_reminder>"""

ARTIFACT_FILE_REMINDER = """\
<artifact_file_reminder>
There are important artifacts that you should be continuously checking or updating as you work:
You have not accessed these files recently:
{files}
please view these files soon to remind yourself of its contents
</artifact_file_reminder>"""

# New: Excessive tools without task boundary reminder (matches Antigravity's emphatic style)
EXCESSIVE_TOOLS_WITHOUT_TASK_REMINDER = """You have called {count} tools in a row without calling the task_boundary tool. This is extremely unexpected. Since you are doing so much work without active engagement with the user, for the next response or tool call you do please concurrently set the task boundary in parallel before continuing any further."""

# Register-output reminder — fires while actively producing work with nothing
# registered yet. register_output is an easy tool to forget, and forgetting it
# leaves the user's Output panel empty even though deliverables exist on disk.
REGISTER_OUTPUT_REMINDER = """\
<register_output_reminder>
You have registered 0 outputs so far. If you have produced — or are about to
produce — any user-facing DELIVERABLE (a report, a figure, a data file/table,
or an output folder), you MUST call `register_output` for it. This is the ONLY
way deliverables appear in the user's Output panel; files written by running
code are tracked NOWHERE else. Register each deliverable as soon as it exists,
and before you finish the task make sure every deliverable has been registered.
(Sub-agents never register — if one reported files in a "Files Produced" list,
register the real deliverables among them yourself.)
</register_output_reminder>"""

# Think Tool Reminder
THINK_TOOL_REMINDER = """\
<think_tool_reminder>
You have called {tools_since_think} tools without using the think tool.
Before proceeding, consider using think() to:
- Analyze results from your recent tool calls
- Verify your approach aligns with requirements
- Check if you have all necessary information to proceed
- Identify any potential issues or edge cases

Using think() improves decision quality in complex tool chains.
</think_tool_reminder>"""

# Too Many Steps in One Task Reminder
TOO_MANY_STEPS_IN_TASK_REMINDER = """\
<too_many_steps_in_task_reminder>
WARNING: You have updated the current task "{task_name}" {task_update_count} times.
This suggests the task scope may be too broad. Consider:
1. Should you create a NEW task with a different task_name for the next phase of work?
2. Remember: Each task should correspond to ONE top-level checklist item in task.md
3. If you've completed a distinct phase, start a new task instead of continuing to update this one

Consider whether the current work belongs in a new task boundary.
</too_many_steps_in_task_reminder>"""


def _read_incomplete_todos(brain_dir: str) -> list[str]:
    """Open items in the chat's task.md checklist — lines marked ``[ ]`` (todo) or
    ``[/]`` (in progress). ``[x]`` / ``[-]`` (done / dropped) are excluded.

    Read straight from task.md (the same source the user sees in Plan & Todo) so
    the reminder can't drift from the persisted checklist."""
    import re

    path = os.path.join(brain_dir, "task.md")
    try:
        with open(path, encoding="utf-8") as fh:
            text = fh.read()
    except Exception:
        return []
    items: list[str] = []
    for line in text.splitlines():
        m = re.match(r"\s*[-*]\s*\[([ /xX\-])\]\s*(.+)", line)
        if m and m.group(1) in (" ", "/"):
            items.append(m.group(2).strip())
    return items


# Headless (-i / one-shot) — no user is present to answer. Counters the agent's
# instinct (reinforced by the reminders below) to stop and ask: decide for itself.
HEADLESS_AUTONOMOUS_REMINDER = """\
<headless_autonomous_reminder>
You are running HEADLESS: there is NO user present. You cannot ask questions,
request approval, or offer choices — nobody will answer. Do NOT call
notify_user(blocked_on_user=true) and do NOT pose questions to the user. For any
consequential or under-specified decision (which dataset/method/scope, an
expensive or irreversible step), choose the most reasonable default yourself,
state your assumption briefly in your work, and proceed to completion
autonomously. Use notify_user only to register final deliverables.
</headless_autonomous_reminder>"""


def generate_ephemeral_message(state: ConversationState, brain_dir: str) -> str:
    """Generate EPHEMERAL_MESSAGE based on current state.

    Args:
        state: Current conversation state
        brain_dir: Path to the brain directory for artifacts

    Returns:
        XML-formatted ephemeral message content
    """
    parts = []

    # Headless (-i / one-shot): no user to ask. Lead with a reminder to decide
    # autonomously instead of trying to block on approvals/choices it can't get.
    headless = os.environ.get("PANTHEON_HEADLESS") == "1"
    if headless:
        parts.append(HEADLESS_AUTONOMOUS_REMINDER)

    # 1. artifact_reminder (always included)
    if state.created_artifacts:
        parts.append(
            ARTIFACT_REMINDER_HAS_ARTIFACTS.format(
                artifacts=chr(10).join(state.created_artifacts)
            )
        )
    else:
        parts.append(ARTIFACT_REMINDER_NO_ARTIFACTS.format(brain_dir=brain_dir))

    # 2. Task state reminder (mutually exclusive)
    EXCESSIVE_TOOLS_THRESHOLD = 5
    TOO_MANY_STEPS_THRESHOLD = 5

    if state.active_task:
        t = state.active_task
        parts.append(
            ACTIVE_TASK_REMINDER.format(
                task_name=t.name,
                ctx_mode=t.mode,
                task_status=t.status,
                task_summary=t.summary,
                tools_since_update=state.tools_since_update,
                task_update_count=state.task_update_count,
            )
        )

        # Add too many steps reminder when threshold exceeded
        if state.task_update_count >= TOO_MANY_STEPS_THRESHOLD:
            parts.append(
                TOO_MANY_STEPS_IN_TASK_REMINDER.format(
                    task_update_count=state.task_update_count,
                    task_name=t.name
                )
            )
    else:
        parts.append(NO_ACTIVE_TASK_REMINDER.format(reason=state.task_boundary_reason))

        # No active task, but task.md may still have open items (the agent closed
        # the task prematurely). Surface them so it finishes/closes them instead of
        # leaving them dangling or spinning on a static "no task is fine" reminder.
        open_todos = _read_incomplete_todos(brain_dir)
        if open_todos:
            items = "\n".join(f"  - {t}" for t in open_todos[:15])
            parts.append(UNFINISHED_TODOS_REMINDER.format(items=items))

        # Add excessive tools reminder when not in task
        if state.tools_since_boundary >= EXCESSIVE_TOOLS_THRESHOLD:
            parts.append(
                EXCESSIVE_TOOLS_WITHOUT_TASK_REMINDER.format(
                    count=state.tools_since_boundary
                )
            )

    # 3. Plan artifact modified in plan phase reminder (semantic check)
    if state.active_task and state.active_task.is_plan_phase:
        if state.has_plan_artifacts_modified():
            plan_files = state.get_modified_artifacts_by_role("plan")
            files_str = ", ".join(os.path.basename(p) for p in plan_files)
            parts.append(
                PLAN_ARTIFACT_MODIFIED_REMINDER.format(
                    files=files_str, ctx_mode=state.active_task.mode
                )
            )

    # 3b. Confirm under-specified choices BEFORE committing — re-surfaced every
    # planning turn until the agent asks the user. This is the stronger lever for
    # "agent picked the dataset without asking": a static prompt rule got ignored
    # (it read "find some data" as "pick silently"), so we nudge it at decision
    # time. Self-limiting: stops once review is requested or it leaves plan phase.
    if (
        not headless  # headless: never nudge the agent to ask the user — nobody's there
        and state.active_task
        and state.active_task.is_plan_phase
        and not state.pending_review_paths
    ):
        parts.append(CONFIRM_OPEN_CHOICES_REMINDER)

    # 4. General artifact modification reminder (non-plan phases)
    if state.active_task and not state.active_task.is_plan_phase:
        all_modified = state.get_all_modified_artifacts()
        if all_modified:
            parts.append(ARTIFACTS_MODIFIED_REMINDER.format(count=len(all_modified)))

    # 4b. Register-output reminder — fire while actively producing work (not
    # planning) when nothing has been registered yet. Self-limiting: stops once
    # the first deliverable is registered, so it nudges exactly until the agent
    # remembers, then goes quiet.
    if (
        state.active_task
        and not state.active_task.is_plan_phase
        and not getattr(state, "outputs", None)
    ):
        parts.append(REGISTER_OUTPUT_REMINDER)

    # 5. Pending review reminder (after notify_user, not in task)
    if state.pending_review_paths and not state.active_task:
        reviewed_files = ", ".join(
            os.path.basename(p) for p in state.pending_review_paths
        )
        parts.append(
            REQUESTED_REVIEW_NOT_IN_TASK_REMINDER.format(reviewed_files=reviewed_files)
        )

    # 6. Think Tool reminder (conditional)
    if state.should_remind_think():
        parts.append(
            THINK_TOOL_REMINDER.format(
                tools_since_think=state.tools_since_think
            )
        )

    # 7. Artifact access reminder (stale artifacts)
    STALE_THRESHOLD = 10
    stale_artifacts = [
        path
        for path, last_step in state.artifact_last_access.items()
        if state.current_step - last_step > STALE_THRESHOLD
    ]
    if stale_artifacts:
        artifact_lines = [
            f"- {p} (last access: {state.current_step - state.artifact_last_access[p]} steps ago)"
            for p in stale_artifacts
        ]
        parts.append(ARTIFACT_FILE_REMINDER.format(files=chr(10).join(artifact_lines)))

    return f"<EPHEMERAL_MESSAGE>\n{chr(10).join(parts)}\n</EPHEMERAL_MESSAGE>"
