"""Configurable prompts for Chad's coding and verification agents.

Edit these prompts to customize agent behavior.
"""

from dataclasses import dataclass
from pathlib import Path


# =============================================================================
# CODING AGENT SYSTEM PROMPT
# =============================================================================
# The coding agent receives this prompt with:
# - {project_docs} replaced with references to on-disk docs (AGENTS.md, ARCHITECTURE.md, etc.)
# - {verification_instructions} replaced with project-specific verification commands
# - {task} replaced with the user's task description

# =============================================================================
# CODING AGENT PROMPT
# =============================================================================
# Single prompt that combines exploration and implementation. The agent emits
# EXPLORATION_RESULT: markers as it discovers things, then outputs a completion
# JSON when done.

CODING_AGENT_PROMPT = """\
{project_docs}

{verification_instructions}

# Task

{task}

---

## Instructions

Do not run `git commit` or `git add`. Leave all changes as uncommitted files in the working tree so the orchestration 
system can handle committing and merging.

As you explore the codebase, each time you discover something relevant, output a line starting with \
`EXPLORATION_RESULT:` followed by a brief description of what you found. For example:
`EXPLORATION_RESULT: The authentication logic is in src/auth.py, using JWT tokens with a 24h expiry`

Then complete these steps:

1. Write test(s) that should fail until the fix/feature is implemented
2. Make the changes, adjusting tests as needed. If no changes are required, skip to step 4.
3. Run verification commands (lint and tests) and fix ALL failures.
4. End your response with a JSON summary:
```json
{{
  "change_summary": "One sentence describing what was changed",
  "files_changed": ["src/auth.py", "tests/test_auth.py"],
  "completion_status": "success"
}}
```
Fields: change_summary (required), files_changed (required, or "info_only"), completion_status (success/partial/blocked/error), hypothesis (optional, for bugs)
"""


# =============================================================================
# VERIFICATION AGENT PROMPTS (Two-Phase)
# =============================================================================
# The verification agent reviews the coding agent's work in two phases:
# 1. Exploration - Free-form analysis of the changes
# 2. Conclusion - Structured JSON output
#
# IMPORTANT: The verification agent should NOT make any changes to files.

# Phase 1: Exploration - Agent can freely explore and analyze
VERIFICATION_EXPLORATION_PROMPT = """\
You are a code review agent verifying that another agent completed a coding task correctly.

IMPORTANT RULE: DO NOT modify or create any files in this codebase. Your only job is to verify the work.

## Task that was assigned:
---
{task}
---

## Coding agent's output:
---
{coding_output}
---

Please verify the work by:
1. Using Read, Glob, and Grep tools to check that what was actually modified on disk matches the coding agent's output
2. Checking that the coding agent's changes on disk address everything the user asked for
3. Checking that the coding agent's changes on disk do not include unnecessary changes
4. Reviewing the changes for correctness and completeness

If the coding agent already ran tests and they passed, you do NOT need to re-run them. Trust the coding agent's test
output unless you have specific concerns about the implementation.

Explore the codebase and provide your analysis. After you're done exploring, I'll ask you for your final verdict.
"""

# Extra criteria appended on retry (attempt >= 2) to raise the verification bar
ELEVATED_VERIFICATION_CRITERIA = """

## Elevated Review (Attempt {attempt})

This is retry attempt {attempt}. The previous verification failed and the coding agent revised its work. \
Apply stricter scrutiny by also evaluating:

5. **Durability**: Will this fix hold up under edge cases and future changes, or is it brittle/superficial?
6. **Root cause**: Did the coding agent address the actual root cause, or just patch the symptom?
7. **Structural improvement**: Are there structural improvements that would prevent this class of issue from recurring?
8. **Dead code**: Did the failed attempt leave behind any dead code, unused imports, or orphaned test fixtures?
"""

# Phase 2: Conclusion - Request structured JSON output
VERIFICATION_CONCLUSION_PROMPT = """\
Based on your analysis, provide your final verdict.

You MUST respond with ONLY valid JSON and nothing else:
```json
{{
  "passed": true,
  "summary": "Brief explanation of what was checked and why it looks correct"
}}
```
Or if issues were found:

```json
{{
  "passed": false,
  "summary": "Brief summary of what needs to be fixed",
  "issues": [
    "First issue that needs to be addressed",
    "Second issue that needs to be addressed"
  ]
}}
```
Output ONLY the JSON block, no other text.
"""

# Legacy single-phase prompt (kept for backwards compatibility)
VERIFICATION_AGENT_PROMPT = """\
You are a code review agent and follow this IMPORTANT RULE - DO NOT modify or create any files in this codebase, instead
your only job is to verify that another agent completed the following coding task correctly:
----
{task}
----
Here is the coding agents output for that task:
---
{coding_output}
---

Please verify the work by:
1. Checking that what was actually modified on disk matches the coding agents output
2. Checking that the coding agents output addresses everything the user asked for
3. Reviewing the changes for correctness and completeness

If the coding agent already ran tests and they passed, you do NOT need to re-run them. Trust the coding agent's test
output unless you have specific concerns about the implementation.

You MUST respond with only valid JSON and nothing else, for example:
```json
{{
  "passed": true,
  "summary": "Brief explanation of what was checked and why it looks correct"
}}
```
Or if issues were found:

```json
{{
  "passed": false,
  "summary": "Brief summary of what needs to be fixed",
  "issues": [
    "First issue that needs to be addressed",
    "Second issue that needs to be addressed"
  ]
}}
```
Output ONLY the JSON block, no other text.
"""


@dataclass
class PromptPreviews:
    """Pre-filled prompt templates with project docs but task placeholders."""

    coding: str
    verification: str
    # Legacy aliases for backwards compatibility in tests
    exploration: str = ""
    implementation: str = ""

    def __post_init__(self):
        if not self.exploration:
            self.exploration = self.coding
        if not self.implementation:
            self.implementation = self.coding


def build_prompt_previews(project_path: str | Path | None) -> PromptPreviews:
    """Build prompt previews with project docs filled in but {task} as placeholder.

    Call this when the project path changes to show users what the prompts
    will look like before a task is started.

    Args:
        project_path: Path to the project directory

    Returns:
        PromptPreviews with coding and verification prompts partially filled
    """
    from chad.util.project_setup import build_doc_reference_text

    project_docs = None
    if project_path:
        project_docs = build_doc_reference_text(Path(project_path))

    docs_section, verification_section = _build_docs_and_verification(project_docs, project_path)

    coding = CODING_AGENT_PROMPT.format(
        project_docs=docs_section,
        verification_instructions=verification_section,
        task="{task}",
    )
    verification = VERIFICATION_EXPLORATION_PROMPT.format(
        task="{task}",
        coding_output="{coding_output}",
    )

    return PromptPreviews(
        coding=coding,
        verification=verification,
    )


def _build_task_with_screenshots(task: str, screenshots: list[str] | None) -> str:
    """Build task section with screenshots if provided."""
    if not screenshots:
        return task
    screenshot_section = "\n\nThe user has attached the following screenshots for reference. " \
        "Use the Read tool to view them:\n"
    for screenshot_path in screenshots:
        screenshot_section += f"- {screenshot_path}\n"
    return task + screenshot_section


def _build_docs_and_verification(
    project_docs: str | None,
    project_path: str | Path | None,
) -> tuple[str, str]:
    """Build the docs and verification sections for prompts."""
    docs_section = ""
    if project_docs:
        docs_section = f"# Project Documentation\n\n{project_docs}\n\n"

    verification_section = ""
    if project_path:
        from chad.util.project_setup import build_verification_instructions
        verification_section = build_verification_instructions(Path(project_path))

    return docs_section, verification_section


def build_prompt(
    task: str,
    project_docs: str | None = None,
    project_path: str | Path | None = None,
    screenshots: list[str] | None = None,
) -> str:
    """Build the prompt for the coding agent.

    Single combined prompt that handles exploration and implementation.

    Args:
        task: The user's task description
        project_docs: Optional project documentation references (paths to read)
        project_path: Optional project path for detecting verification commands
        screenshots: Optional list of screenshot file paths to include

    Returns:
        Complete prompt for the coding agent including the task
    """
    docs_section, verification_section = _build_docs_and_verification(project_docs, project_path)
    task_with_screenshots = _build_task_with_screenshots(task, screenshots)

    return CODING_AGENT_PROMPT.format(
        project_docs=docs_section,
        verification_instructions=verification_section,
        task=task_with_screenshots,
    )


# Legacy aliases for backwards compatibility
build_coding_prompt = build_prompt


def build_exploration_prompt(
    task: str,
    project_docs: str | None = None,
    project_path: str | Path | None = None,
    screenshots: list[str] | None = None,
) -> str:
    """Legacy alias for build_prompt."""
    return build_prompt(task, project_docs, project_path, screenshots)


def build_implementation_prompt(
    task: str,
    exploration_output: str = "",
    project_docs: str | None = None,
    project_path: str | Path | None = None,
) -> str:
    """Legacy alias for build_prompt (exploration_output ignored)."""
    return build_prompt(task, project_docs, project_path)


def get_verification_prompt(coding_output: str, task: str = "", change_summary: str | None = None) -> str:
    """Build the prompt for the verification agent (legacy single-phase).

    Args:
        coding_output: The output from the coding agent
        task: The original task description
        change_summary: Optional extracted change summary to prepend

    Returns:
        Complete prompt for the verification agent
    """
    coding_block = coding_output
    if change_summary:
        coding_block = f"Summary from coding agent: {change_summary}\n\nFull response:\n{coding_output}"

    return VERIFICATION_AGENT_PROMPT.format(coding_output=coding_block, task=task or "(no task provided)")


def get_verification_exploration_prompt(
    coding_output: str,
    task: str = "",
    change_summary: str | None = None,
    attempt: int = 1,
) -> str:
    """Build the exploration prompt for two-phase verification.

    This is Phase 1 where the verification agent can freely explore the codebase.

    Args:
        coding_output: The output from the coding agent
        task: The original task description
        change_summary: Optional extracted change summary to prepend
        attempt: Verification attempt number (1-indexed). On attempt >= 2,
                 elevated criteria are appended to raise the review bar.

    Returns:
        Exploration prompt for the verification agent
    """
    coding_block = coding_output
    if change_summary:
        coding_block = f"Summary from coding agent: {change_summary}\n\nFull response:\n{coding_output}"

    prompt = VERIFICATION_EXPLORATION_PROMPT.format(
        coding_output=coding_block,
        task=task or "(no task provided)",
    )

    if attempt >= 2:
        prompt += ELEVATED_VERIFICATION_CRITERIA.format(attempt=attempt)

    return prompt


def get_verification_conclusion_prompt() -> str:
    """Get the conclusion prompt for two-phase verification.

    This is Phase 2 where the verification agent must output structured JSON.

    Returns:
        Conclusion prompt requesting JSON output
    """
    return VERIFICATION_CONCLUSION_PROMPT


class VerificationParseError(Exception):
    """Raised when verification response cannot be parsed."""

    pass


@dataclass
class CodingSummary:
    """Structured summary extracted from coding agent response."""

    change_summary: str
    # files_changed is a list of file paths, or "info_only" string for information requests
    files_changed: list[str] | str | None = None
    # completion_status: "success", "partial", "blocked", "error", or None if not specified
    completion_status: str | None = None
    hypothesis: str | None = None
    before_screenshot: str | None = None
    before_description: str | None = None
    after_screenshot: str | None = None
    after_description: str | None = None


@dataclass
class ProgressUpdate:
    """Intermediate progress update from coding agent."""

    summary: str
    location: str
    next_step: str | None = None
    before_screenshot: str | None = None
    before_description: str | None = None


# Error patterns that indicate a provider failure rather than a parse issue
_PROVIDER_ERROR_PATTERNS = [
    (r"error:\s*.+execution stalled(?:\s*\([^)]*\))?", "Verification agent stalled (no output)"),
    (r"error:\s*.+execution timed out(?:\s*\([^)]*\))?", "Verification agent timed out"),
    (r"failed to run.+command not found(?:\s+install with:.+)?", "Verification agent CLI not installed"),
    (r"no response from.+", "Verification agent returned no response"),
    (r"failed to run.+:.+", "Verification agent execution error"),
]


def parse_verification_response(response: str) -> tuple[bool, str, list[str]]:
    """Parse the JSON response from the verification agent.

    Args:
        response: Raw response from the verification agent

    Returns:
        Tuple of (passed: bool, summary: str, issues: list[str])

    Raises:
        VerificationParseError: If response is not valid JSON with required fields
    """
    import json
    import re

    # Strip thinking/reasoning prefixes that some models add before JSON
    # e.g., "*Thinking: **Ensuring valid JSON output***\n\n{..."
    cleaned = re.sub(r"^\s*\*+[Tt]hinking:.*?\*+\s*", "", response, flags=re.DOTALL)

    def _extract_balanced_json_objects(text: str) -> list[str]:
        objects: list[str] = []
        depth = 0
        in_string = False
        escape_next = False
        start_idx: int | None = None

        for idx, char in enumerate(text):
            if escape_next:
                escape_next = False
                continue

            if in_string:
                if char == "\\":
                    escape_next = True
                elif char == '"':
                    in_string = False
                continue

            if char == '"':
                in_string = True
                continue

            if char == "{":
                if depth == 0:
                    start_idx = idx
                depth += 1
                continue

            if char == "}":
                if depth == 0:
                    continue
                depth -= 1
                if depth == 0 and start_idx is not None:
                    objects.append(text[start_idx: idx + 1])
                    start_idx = None

        return objects

    # Collect candidate JSON objects from fenced and raw content.
    candidates: list[str] = []
    seen: set[str] = set()

    for match in re.finditer(r"```(?:json)?\s*(\{.*?\})\s*```", cleaned, re.DOTALL | re.IGNORECASE):
        candidate = match.group(1).strip()
        if candidate and candidate not in seen:
            seen.add(candidate)
            candidates.append(candidate)

    for candidate in _extract_balanced_json_objects(cleaned):
        normalized = candidate.strip()
        if normalized and normalized not in seen:
            seen.add(normalized)
            candidates.append(normalized)

    # If multiple JSON objects exist, prefer one that includes `passed`.
    prioritized = sorted(candidates, key=lambda text: '"passed"' not in text)
    data = None
    parse_error = None
    missing_passed_seen = False
    for candidate in prioritized:
        try:
            parsed = json.loads(candidate)
        except json.JSONDecodeError as e:
            parse_error = e
            continue

        if not isinstance(parsed, dict):
            continue

        if "passed" in parsed:
            data = parsed
            break

        missing_passed_seen = True

    if data is None:
        # Fall back to provider-error matching only when the whole response
        # looks like an execution error payload. This avoids false positives
        # from snippets like: response = "Error: ... timed out ...".
        ansi_stripped = re.sub(r"\x1b\[[0-?]*[ -/]*[@-~]", "", cleaned).strip()
        fence_match = re.fullmatch(r"```(?:\w+)?\s*(.*?)\s*```", ansi_stripped, re.DOTALL)
        if fence_match:
            ansi_stripped = fence_match.group(1).strip()
        error_candidate_lines = [line.strip() for line in ansi_stripped.splitlines() if line.strip()]
        if error_candidate_lines and len(error_candidate_lines) <= 4:
            error_candidate = "\n".join(error_candidate_lines)
            for pattern, error_msg in _PROVIDER_ERROR_PATTERNS:
                if re.fullmatch(pattern, error_candidate, re.IGNORECASE | re.DOTALL):
                    return False, error_msg, [error_candidate[:500]]

        if not candidates:
            raise VerificationParseError(f"No JSON found in response: {response[:200]}")
        if missing_passed_seen:
            raise VerificationParseError("Missing required field 'passed' in JSON response")
        if parse_error is not None:
            raise VerificationParseError(f"Invalid JSON: {parse_error}")
        raise VerificationParseError(f"No valid JSON found in response: {response[:200]}")

    if "passed" not in data:
        raise VerificationParseError("Missing required field 'passed' in JSON response")

    if not isinstance(data["passed"], bool):
        raise VerificationParseError(f"Field 'passed' must be boolean, got {type(data['passed']).__name__}")

    passed = data["passed"]
    summary = data.get("summary", "")
    issues = data.get("issues", [])

    return passed, summary, issues


def extract_coding_summary(response: str) -> CodingSummary | None:
    """Extract the structured summary from a coding agent response.

    Args:
        response: Raw response from the coding agent

    Returns:
        CodingSummary with change_summary and optional hypothesis/screenshot paths, or None
    """
    import json
    import re

    # Look for JSON block with change_summary
    json_match = re.search(r'```json\s*(\{[^`]*"change_summary"[^`]*\})\s*```', response, re.DOTALL)
    if json_match:
        try:
            data = json.loads(json_match.group(1))
            if "change_summary" in data:
                # Parse files_changed - can be array or "info_only" string
                files_changed = data.get("files_changed")
                if isinstance(files_changed, list):
                    files_changed = [str(f) for f in files_changed]
                elif isinstance(files_changed, str):
                    pass  # Keep as string (e.g., "info_only")
                else:
                    files_changed = None
                return CodingSummary(
                    change_summary=data["change_summary"],
                    files_changed=files_changed,
                    completion_status=data.get("completion_status"),
                    hypothesis=data.get("hypothesis"),
                    before_screenshot=data.get("before_screenshot"),
                    before_description=data.get("before_description"),
                    after_screenshot=data.get("after_screenshot"),
                    after_description=data.get("after_description"),
                )
        except json.JSONDecodeError:
            pass

    # Try to find raw JSON with change_summary (fallback - only gets change_summary)
    json_match = re.search(r'\{\s*"change_summary"\s*:\s*"([^"]+)"\s*\}', response)
    if json_match:
        return CodingSummary(change_summary=json_match.group(1))

    return None


# Placeholder patterns that indicate the agent copied the example verbatim
_PLACEHOLDER_PATTERNS = [
    "one line describing",
    "brief description of",
    "src/file.py:123",
    "/path/to/before.png",
    # The exact example from the prompt - filter if agent copies it verbatim
    "adding retry logic to handle api rate limits",
    "src/api/client.py",
    "writing tests to verify the retry behavior",
]


def _is_placeholder_text(text: str) -> bool:
    """Check if text looks like placeholder from the prompt example."""
    if not text:
        return True
    lower = text.lower()
    return any(pattern in lower for pattern in _PLACEHOLDER_PATTERNS)


def extract_progress_update(response: str) -> ProgressUpdate | None:
    """Extract a progress update from coding agent streaming output.

    Supports two formats:
    1. JSON (preferred):
       {"type": "progress", "summary": "...", "location": "...", "next_step": "..."}
    2. Markdown (legacy fallback):
       ```
       **Progress:** summary text
       **Location:** file:line
       **Next:** next step
       ```

    Args:
        response: Raw response chunk from the coding agent

    Returns:
        ProgressUpdate if found, None otherwise. Returns None if the summary
        or next_step appears to be placeholder text copied from the prompt example.
    """
    import json
    import re

    def _parse_progress_json(json_text: str) -> ProgressUpdate | None:
        """Parse progress JSON, retrying with newline normalization."""
        candidates = [json_text, json_text.replace("\r", " ").replace("\n", " ")]
        for candidate in candidates:
            try:
                data = json.loads(candidate)
            except json.JSONDecodeError:
                continue
            if data.get("type") != "progress":
                return None
            summary = data.get("summary", "")
            next_step = data.get("next_step")
            # Filter if summary or next_step looks like placeholder text
            if _is_placeholder_text(summary):
                return None
            if next_step and _is_placeholder_text(next_step):
                return None
            return ProgressUpdate(
                summary=summary,
                location=data.get("location", ""),
                next_step=next_step,
                before_screenshot=data.get("before_screenshot"),
                before_description=data.get("before_description"),
            )
        return None

    # Try JSON format first (preferred)
    # Look for JSON block with type: "progress"
    json_match = re.search(r'```json\s*(\{[^`]*"type"\s*:\s*"progress"[^`]*\})\s*```', response, re.DOTALL)
    if json_match:
        parsed = _parse_progress_json(json_match.group(1))
        if parsed:
            return parsed

    # Try to find raw JSON with type: progress
    raw_match = re.search(r'\{\s*"type"\s*:\s*"progress"[^}]+\}', response, re.DOTALL)
    if raw_match:
        parsed = _parse_progress_json(raw_match.group(0))
        if parsed:
            return parsed

    # Fall back to markdown format for backwards compatibility
    # Match code block with **Progress:**, **Location:**, **Next:** lines
    md_block = re.search(r'```\s*\n(.*?\*\*Progress:\*\*.*?)```', response, re.DOTALL | re.IGNORECASE)
    if md_block:
        block_content = md_block.group(1)
    else:
        # Also try without code block - just the **Progress:** pattern
        block_content = response

    # Extract markdown fields
    progress_match = re.search(r'\*\*Progress:\*\*\s*(.+?)(?:\n|$)', block_content, re.IGNORECASE)
    location_match = re.search(r'\*\*Location:\*\*\s*(.+?)(?:\n|$)', block_content, re.IGNORECASE)
    next_match = re.search(r'\*\*Next:\*\*\s*(.+?)(?:\n|$)', block_content, re.IGNORECASE)

    if progress_match:
        summary = progress_match.group(1).strip()
        location = location_match.group(1).strip() if location_match else ""
        next_step = next_match.group(1).strip() if next_match else None

        # Filter placeholder text
        if _is_placeholder_text(summary):
            return None
        if next_step and _is_placeholder_text(next_step):
            return None

        return ProgressUpdate(summary=summary, location=location, next_step=next_step)

    # Last-resort manual extraction (handles malformed JSON with embedded newlines)
    manual_match = re.search(
        r'"type"\\s*:\\s*"progress"(?P<body>[^}]*)}',
        response,
        re.DOTALL,
    )
    if manual_match:
        body = manual_match.group("body")
        summary_match = re.search(r'"summary"\\s*:\\s*"(?P<summary>.*?)"', body, re.DOTALL)
        location_match_manual = re.search(r'"location"\\s*:\\s*"(?P<loc>[^"]+)"', body)
        next_step_match = re.search(r'"next_step"\\s*:\\s*"(?P<ns>[^"]+)"', body)
        if summary_match:
            summary = " ".join(summary_match.group("summary").splitlines()).strip()
            next_step = next_step_match.group("ns") if next_step_match else None
            if not _is_placeholder_text(summary):
                if next_step and _is_placeholder_text(next_step):
                    return None
                location = location_match_manual.group("loc") if location_match_manual else ""
                return ProgressUpdate(summary=summary, location=location, next_step=next_step)

    return None


def check_verification_mentioned(response: str) -> bool:
    """Check if the coding agent mentioned running verification.

    Args:
        response: Raw response from the coding agent

    Returns:
        True if verification was mentioned, False otherwise
    """
    import re

    verification_patterns = [
        r"flake8",
        r"pytest",
        r"verification.*passed",
        r"all tests pass",
        r"linting.*pass",
        r"\d+ passed",
    ]

    response_lower = response.lower()
    for pattern in verification_patterns:
        if re.search(pattern, response_lower):
            return True

    return False


# =============================================================================
# CODING CONTINUATION PROMPT
# =============================================================================
# Used when agent exits without completing (only progress update, no completion JSON)

CONTINUATION_PROMPT = """\
Your previous response ended with a progress update but you did not complete the task.

Continue the SAME task below. Do not start a different task or rediscover scope from repo history.

Original task:
---
{task}
---

Most recent output from your previous attempt:
---
{previous_output}
---

1. Write test(s) that should fail until the fix/feature is implemented
2. Make the changes, adjusting tests as needed
3. Run verification commands (lint and tests)
4. Fix ALL failures and retest if required
5. End with a JSON summary block:
```json
{{
  "change_summary": "One sentence describing what was changed",
  "files_changed": ["src/file.py", "tests/test_file.py"],
  "completion_status": "success"
}}
```

Do NOT output another progress update - continue directly with implementation.
"""


MAX_CONTINUATION_OUTPUT_CHARS = 6000


def _compact_continuation_output(previous_output: str) -> str:
    """Keep continuation context bounded while preserving recent details."""
    text = previous_output.strip()
    if not text:
        return "(no previous output captured)"
    if len(text) <= MAX_CONTINUATION_OUTPUT_CHARS:
        return text
    return (
        f"(truncated to last {MAX_CONTINUATION_OUTPUT_CHARS} chars)\n"
        + text[-MAX_CONTINUATION_OUTPUT_CHARS:]
    )


def get_continuation_prompt(task: str, previous_output: str) -> str:
    """Build a continuation prompt when agent exits early (progress but no completion).

    Args:
        task: Original task description that must be preserved across retries
        previous_output: The output from the previous run (included for context)

    Returns:
        Continuation prompt to re-invoke the agent
    """
    task_text = task.strip() or "(no task provided)"
    output_text = _compact_continuation_output(previous_output)
    return CONTINUATION_PROMPT.format(task=task_text, previous_output=output_text)


# =============================================================================
# REVISION PROMPT
# =============================================================================
# Used when verification fails and the coding agent needs to fix issues.

REVISION_PROMPT = """\
Verification found issues with your changes. Please fix the following problems:

{feedback}

After fixing:
1. Run verification commands (lint and tests) and fix ALL failures.
2. End with a JSON summary block:
```json
{{
  "change_summary": "One sentence describing what was fixed",
  "files_changed": ["src/file.py", "tests/test_file.py"],
  "completion_status": "success"
}}
```
"""

# Escalated revision prompt for attempt >= 3: forces the coding agent to
# stop, audit everything it has done, and re-plan from scratch.
ESCALATED_REVISION_PROMPT = """\
Verification has failed {attempt} times. Previous fixes have not resolved the issues. \
Before writing any code, you MUST complete the following audit:

1. **Session history**: Summarise what you have tried so far in this session, in order.
2. **Current disk state**: List every change currently on disk relative to the original code and describe what each \
change does.
3. **Git history**: Check git history for any previous attempts to solve this issue and describe what each one tried.
4. **Assess**: Stop and compare your answers to steps 1-3. Note any inconsistencies, repeated mistakes, or directions \
you have not yet explored.
5. **New plan**: Based ONLY on your assessment in step 4, make a new plan to solve this issue and execute it.

## Latest verification feedback:

{feedback}

After fixing:
1. Run verification commands (lint and tests) and fix ALL failures.
2. End with a JSON summary block:
```json
{{
  "change_summary": "One sentence describing what was fixed",
  "files_changed": ["src/file.py", "tests/test_file.py"],
  "completion_status": "success"
}}
```
"""


def get_revision_prompt(feedback: str, attempt: int = 1) -> str:
    """Build a revision prompt with verification feedback.

    Args:
        feedback: The verification feedback describing issues to fix
        attempt: The verification attempt number (1-indexed). On attempt >= 3,
                 an escalated prompt forces the agent to audit and re-plan.

    Returns:
        Revision prompt to re-invoke the coding agent
    """
    if attempt >= 3:
        return ESCALATED_REVISION_PROMPT.format(feedback=feedback, attempt=attempt)
    return REVISION_PROMPT.format(feedback=feedback)


# =============================================================================
# CODING SUMMARY VALIDATION AND RE-INVOKE
# =============================================================================

SUMMARY_COMPLETION_PROMPT = """\
Your previous response is missing required fields in the JSON summary block. Please output ONLY a complete JSON \
summary block with all required fields:

```json
{{
  "change_summary": "One sentence describing what was done",
  "files_changed": ["list", "of", "files"] or "info_only" if no files were changed,
  "completion_status": "success" or "partial" or "blocked" or "error"
}}
```

Required fields:
- change_summary: What you accomplished
- files_changed: Array of modified file paths, OR the string "info_only" if this was just an information request
- completion_status: One of "success", "partial" (hit token/context limit), "blocked" (needs user input), or "error"

Output ONLY the JSON block, nothing else.
"""


def get_summary_completion_prompt(summary: CodingSummary | None) -> str | None:
    """Check if a coding summary is missing required fields and return a completion prompt.

    This function validates that the mandatory fields (files_changed, completion_status)
    are present in the coding summary. If they're missing, it returns a prompt to
    re-invoke the coding agent to complete the summary.

    Args:
        summary: The extracted coding summary, or None if extraction failed

    Returns:
        A prompt string to re-invoke the agent if fields are missing, or None if complete
    """
    if summary is None:
        # No summary at all - need to ask for one
        return SUMMARY_COMPLETION_PROMPT

    # Check for missing mandatory fields
    missing_files_changed = summary.files_changed is None
    missing_completion_status = summary.completion_status is None

    if missing_files_changed or missing_completion_status:
        return SUMMARY_COMPLETION_PROMPT

    return None
