"""Agent chat endpoint with SSE streaming."""

import asyncio
import sys
import uuid
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from typing import Optional

from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel

from ..config import get_config
from ..models.agent import AgentChatRequest, AgentMode
from ..sse.stream import SSEHandler, EventType

router = APIRouter(prefix="/agent", tags=["agent"])

# Thread pool for running blocking agent code
_executor = ThreadPoolExecutor(max_workers=4)

# Active sessions (in-memory for now)
_sessions: dict[str, dict] = {}


def _ensure_emdash_importable():
    """Ensure the emdash_core package is importable.

    This is now a no-op since we use emdash_core directly.
    """
    pass  # emdash_core is already in the package


def _run_agent_sync(
    message: str,
    model: str,
    max_iterations: int,
    sse_handler: SSEHandler,
    session_id: str,
    images: list = None,
    plan_mode: bool = False,
    history: list = None,
    use_worktree: bool = False,
):
    """Run the agent synchronously (in thread pool).

    This function runs in a background thread and emits events
    to the SSE handler for streaming to the client.

    Args:
        history: Optional list of previous messages to pre-populate conversation
        use_worktree: If True, creates a git worktree for isolated changes
    """
    try:
        _ensure_emdash_importable()

        # Import agent components from emdash_core
        from ..agent.runner import AgentRunner
        from ..agent.toolkit import AgentToolkit
        from ..agent.events import AgentEventEmitter

        # Create an emitter that forwards to SSE handler
        class SSEBridgeHandler:
            """Bridges AgentEventEmitter to SSEHandler."""

            def __init__(self, sse_handler: SSEHandler):
                self._sse = sse_handler

            def handle(self, event):
                """Forward event to SSE handler."""
                self._sse.handle(event)

        # Create agent with event emitter
        emitter = AgentEventEmitter(agent_name="Emdash Code")
        emitter.add_handler(SSEBridgeHandler(sse_handler))

        # Add hook handler for user-defined hooks
        from ..agent.hooks import HookHandler, get_hook_manager
        hook_manager = get_hook_manager()
        hook_manager.set_session_id(session_id)
        emitter.add_handler(HookHandler(hook_manager))
        # Get repo_root from config (set by server on startup)
        from pathlib import Path
        from ..config import get_config
        from ..utils.logger import log
        config = get_config()
        repo_root = Path(config.repo_root) if config.repo_root else Path.cwd()
        log.info(f"Agent API: config.repo_root={config.repo_root}, resolved repo_root={repo_root}")

        # Create worktree for isolated changes if requested
        worktree_info = None
        if use_worktree and not plan_mode:
            from ..agent.worktree import WorktreeManager
            try:
                worktree_manager = WorktreeManager(repo_root)
                # Use session_id as task slug (truncated for safety)
                task_slug = session_id[:20] if len(session_id) > 20 else session_id
                worktree_info = worktree_manager.create_worktree(task_slug, force=True)
                repo_root = worktree_info.path
                log.info(f"Created worktree at {repo_root} on branch {worktree_info.branch}")
            except Exception as e:
                log.warning(f"Failed to create worktree: {e}. Running in main repo.")
                worktree_info = None

        # Create toolkit with plan_mode if requested
        # When in plan mode, generate a session-specific plan file path in ~/.emdash/sessions/
        # This ensures plans don't persist between sessions and don't pollute the repo
        plan_file_path = None
        if plan_mode:
            session_plan_dir = Path.home() / ".emdash" / "sessions" / session_id
            session_plan_dir.mkdir(parents=True, exist_ok=True)
            plan_file_path = str(session_plan_dir / "plan.md")

        toolkit = AgentToolkit(repo_root=repo_root, plan_mode=plan_mode, plan_file_path=plan_file_path)

        runner = AgentRunner(
            toolkit=toolkit,
            model=model,
            verbose=True,
            max_iterations=max_iterations,
            emitter=emitter,
            session_id=session_id,
        )

        # Inject pre-loaded conversation history if provided
        if history:
            runner._messages = list(history)
            log.info(f"Injected {len(history)} messages from saved session")

        # Store session state BEFORE running (so it exists even if interrupted)
        _sessions[session_id] = {
            "runner": runner,
            "message_count": 1,
            "model": model,
            "plan_mode": plan_mode,
            "worktree_info": worktree_info,  # Will be None if not using worktree
        }

        # Set up autosave callback if enabled via env var
        import os
        import json
        from datetime import datetime
        if os.environ.get("EMDASH_SESSION_AUTOSAVE", "").lower() == "true":
            sessions_dir = repo_root / ".emdash" / "sessions"
            sessions_dir.mkdir(parents=True, exist_ok=True)
            autosave_path = sessions_dir / "_autosave.json"
            index_path = sessions_dir / "index.json"

            def autosave_callback(messages):
                """Save messages to autosave file after each iteration."""
                try:
                    # Limit to last 10 messages
                    trimmed = messages[-10:] if len(messages) > 10 else messages
                    now = datetime.utcnow().isoformat() + "Z"
                    autosave_data = {
                        "name": "_autosave",
                        "messages": trimmed,
                        "model": model,
                        "mode": "plan" if plan_mode else "code",
                        "session_id": session_id,
                    }
                    with open(autosave_path, "w") as f:
                        json.dump(autosave_data, f, indent=2, default=str)

                    # Update index.json so /session list shows the autosave
                    index = {"sessions": [], "active": None}
                    if index_path.exists():
                        try:
                            index = json.loads(index_path.read_text())
                        except (json.JSONDecodeError, IOError):
                            pass

                    # Update or add autosave to index
                    autosave_meta = {
                        "name": "_autosave",
                        "created_at": index["sessions"][0]["created_at"] if any(s.get("name") == "_autosave" for s in index.get("sessions", [])) else now,
                        "updated_at": now,
                        "message_count": len(trimmed),
                        "model": model,
                        "mode": "plan" if plan_mode else "code",
                        "summary": trimmed[0].get("content", "")[:100] if trimmed else "Autosaved session",
                    }

                    # Update existing or append
                    sessions = index.get("sessions", [])
                    found = False
                    for i, s in enumerate(sessions):
                        if s.get("name") == "_autosave":
                            sessions[i] = autosave_meta
                            found = True
                            break
                    if not found:
                        sessions.append(autosave_meta)

                    index["sessions"] = sessions
                    index_path.write_text(json.dumps(index, indent=2))

                    log.debug(f"Autosaved {len(trimmed)} messages to {autosave_path}")
                except Exception as e:
                    log.debug(f"Autosave failed: {e}")

            runner._on_iteration_callback = autosave_callback
            log.info("Session autosave enabled")

        # Convert image data if provided
        agent_images = None
        if images:
            import base64
            from ..agent.providers.base import ImageContent
            agent_images = [
                ImageContent(
                    image_data=base64.b64decode(img.data),
                    format=img.format
                )
                for img in images
            ]

        # Run the agent
        response = runner.run(message, images=agent_images)

        return response

    except Exception as e:
        # Emit error event
        sse_handler.emit(EventType.ERROR, {
            "message": str(e),
            "details": None,
        })
        raise


async def _run_agent_async(
    request: AgentChatRequest,
    sse_handler: SSEHandler,
    session_id: str,
):
    """Run agent in thread pool and stream events."""
    config = get_config()

    # Get model from request or config
    model = request.model or config.default_model
    max_iterations = request.options.max_iterations
    plan_mode = request.options.mode == AgentMode.PLAN
    use_worktree = request.options.use_worktree

    # Emit session start
    sse_handler.emit(EventType.SESSION_START, {
        "agent_name": "Emdash Code",
        "model": model,
        "session_id": session_id,
        "query": request.message,
        "mode": request.options.mode.value,
        "use_worktree": use_worktree,
    })

    loop = asyncio.get_event_loop()

    try:
        # Run agent in thread pool
        await loop.run_in_executor(
            _executor,
            _run_agent_sync,
            request.message,
            model,
            max_iterations,
            sse_handler,
            session_id,
            request.images,
            plan_mode,
            request.history,  # Pre-loaded conversation history
            use_worktree,
        )

        # Emit session end
        sse_handler.emit(EventType.SESSION_END, {
            "success": True,
            "session_id": session_id,
        })

    except Exception as e:
        sse_handler.emit(EventType.SESSION_END, {
            "success": False,
            "error": str(e),
            "session_id": session_id,
        })

    finally:
        sse_handler.close()


@router.post("/chat")
async def agent_chat(request: AgentChatRequest):
    """Start an agent chat session with SSE streaming.

    The response is a Server-Sent Events stream containing:
    - session_start: Initial session info
    - tool_start: When a tool begins execution
    - tool_result: When a tool completes
    - thinking: Agent reasoning messages
    - response/partial_response: Agent text output
    - clarification: When agent needs user input
    - error/warning: Error messages
    - session_end: Session completion

    Example:
        curl -N -X POST http://localhost:8765/api/agent/chat \\
            -H "Content-Type: application/json" \\
            -d '{"message": "Find authentication code"}'
    """
    # Generate or use provided session ID
    session_id = request.session_id or str(uuid.uuid4())

    # Create SSE handler
    sse_handler = SSEHandler(agent_name="Emdash Code")

    # Start agent in background
    asyncio.create_task(_run_agent_async(request, sse_handler, session_id))

    # Return SSE stream
    return StreamingResponse(
        sse_handler,
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Session-ID": session_id,
        },
    )


@router.post("/chat/{session_id}/continue")
async def continue_chat(session_id: str, request: AgentChatRequest):
    """Continue an existing chat session.

    This allows multi-turn conversations by reusing the session state.
    """
    if session_id not in _sessions:
        raise HTTPException(
            status_code=404,
            detail=f"Session {session_id} not found"
        )

    session = _sessions[session_id]
    runner = session.get("runner")
    if not runner:
        raise HTTPException(
            status_code=400,
            detail="Session has no active runner"
        )

    # Create SSE handler
    sse_handler = SSEHandler(agent_name="Emdash Code")

    async def _continue_session():
        sse_handler.emit(EventType.SESSION_START, {
            "agent_name": "Emdash Code",
            "model": session["model"],
            "session_id": session_id,
            "query": request.message,
            "continued": True,
        })

        loop = asyncio.get_event_loop()

        try:
            # Wire up SSE handler to runner's emitter for this request
            from ..agent.events import AgentEventEmitter

            class SSEBridgeHandler:
                def __init__(self, sse_handler: SSEHandler):
                    self._sse = sse_handler

                def handle(self, event):
                    self._sse.handle(event)

            # Create fresh emitter with new SSE handler
            emitter = AgentEventEmitter(agent_name="Emdash Code")
            emitter.add_handler(SSEBridgeHandler(sse_handler))
            runner.emitter = emitter

            # Continue conversation in thread pool
            await loop.run_in_executor(
                _executor,
                lambda: runner.chat(request.message),
            )

            session["message_count"] += 1

            sse_handler.emit(EventType.SESSION_END, {
                "success": True,
                "session_id": session_id,
            })

        except Exception as e:
            sse_handler.emit(EventType.ERROR, {
                "message": str(e),
            })
            sse_handler.emit(EventType.SESSION_END, {
                "success": False,
                "error": str(e),
                "session_id": session_id,
            })

        finally:
            sse_handler.close()

    asyncio.create_task(_continue_session())

    return StreamingResponse(
        sse_handler,
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Session-ID": session_id,
        },
    )


@router.get("/sessions")
async def list_sessions():
    """List active chat sessions."""
    return {
        "sessions": [
            {
                "session_id": sid,
                "model": data.get("model"),
                "message_count": data.get("message_count", 0),
            }
            for sid, data in _sessions.items()
        ]
    }


@router.delete("/sessions/{session_id}")
async def delete_session(session_id: str):
    """Delete a chat session and cleanup associated files."""
    if session_id in _sessions:
        del _sessions[session_id]

        # Cleanup session-specific plan directory
        import shutil
        session_plan_dir = Path.home() / ".emdash" / "sessions" / session_id
        if session_plan_dir.exists():
            try:
                shutil.rmtree(session_plan_dir)
            except OSError:
                pass  # Ignore cleanup errors

        return {"deleted": True}
    raise HTTPException(status_code=404, detail="Session not found")


@router.get("/chat/{session_id}/export")
async def export_session(session_id: str, limit: int = 10):
    """Export session messages for persistence.

    Args:
        session_id: The session ID
        limit: Maximum number of messages to return (default 10)

    Returns:
        JSON with messages array and metadata
    """
    if session_id not in _sessions:
        raise HTTPException(status_code=404, detail="Session not found")

    session = _sessions[session_id]
    runner = session.get("runner")

    if not runner:
        return {
            "session_id": session_id,
            "messages": [],
            "message_count": 0,
            "model": session.get("model"),
            "mode": "plan" if session.get("plan_mode") else "code",
        }

    # Get messages from runner
    messages = getattr(runner, "_messages", [])

    # Trim to limit (most recent)
    if len(messages) > limit:
        messages = messages[-limit:]

    return {
        "session_id": session_id,
        "messages": messages,
        "message_count": len(messages),
        "model": session.get("model"),
        "mode": "plan" if session.get("plan_mode") else "code",
    }


@router.post("/chat/{session_id}/compact")
async def compact_session(session_id: str):
    """Compact the session's message history using LLM summarization.

    This manually triggers the same compaction that happens automatically
    when context reaches 80% capacity.

    Returns:
        JSON with the summary text and stats
    """
    if session_id not in _sessions:
        raise HTTPException(status_code=404, detail="Session not found")

    session = _sessions[session_id]
    runner = session.get("runner")

    if not runner:
        raise HTTPException(status_code=400, detail="Session has no active runner")

    # Get current messages
    messages = getattr(runner, "_messages", [])
    if len(messages) <= 5:
        return {
            "compacted": False,
            "reason": "Not enough messages to compact (need more than 5)",
            "message_count": len(messages),
        }

    # Import compaction utilities
    from ..agent.runner.context import compact_messages_with_llm, estimate_context_tokens
    from ..agent.events import AgentEventEmitter

    # Create a simple emitter that captures the summary
    class SummaryCapture:
        def __init__(self):
            self.summary = None

        def emit_thinking(self, text):
            pass  # Ignore thinking events

    emitter = SummaryCapture()

    # Estimate current tokens
    original_tokens = estimate_context_tokens(messages)

    # Get toolkit from runner for file tracking reset
    toolkit = getattr(runner, "toolkit", None)

    # Compact messages
    compacted_messages = compact_messages_with_llm(
        messages,
        emitter,
        target_tokens=int(original_tokens * 0.5),
        toolkit=toolkit,
    )

    # Extract the summary from the compacted messages
    summary_text = None
    for msg in compacted_messages:
        content = str(msg.get("content", ""))
        if msg.get("role") == "assistant" and "[CONTEXT COMPACTION" in content:
            # Extract text between the header and [END SUMMARY]
            start_marker = "[CONTEXT COMPACTION - Working State Summary]"
            end_marker = "[END SUMMARY]"
            start = content.find(start_marker)
            if start >= 0:
                start += len(start_marker)
            else:
                # Fallback for old format
                start = content.find("[Context Summary]")
                if start >= 0:
                    start += len("[Context Summary]")
            end = content.find(end_marker)
            if end < 0:
                end = content.find("[End Summary]")
            if start >= 0 and end > start:
                summary_text = content[start:end].strip()
            break

    # Update runner's messages
    runner._messages = compacted_messages

    # Estimate new tokens
    new_tokens = estimate_context_tokens(compacted_messages)

    return {
        "compacted": True,
        "summary": summary_text,
        "original_message_count": len(messages),
        "new_message_count": len(compacted_messages),
        "original_tokens": original_tokens,
        "new_tokens": new_tokens,
        "reduction_percent": round((1 - new_tokens / original_tokens) * 100, 1) if original_tokens > 0 else 0,
    }


@router.get("/chat/{session_id}/plan")
async def get_pending_plan(session_id: str):
    """Get the pending plan for a session, if any.

    Returns 404 if session not found, 204 if no pending plan.
    """
    if session_id not in _sessions:
        raise HTTPException(status_code=404, detail="Session not found")

    session = _sessions[session_id]
    runner = session.get("runner")

    if not runner:
        raise HTTPException(status_code=400, detail="Session has no active runner")

    pending_plan = runner.get_pending_plan()
    if not pending_plan:
        return {"has_plan": False, "plan": None}

    return {
        "has_plan": True,
        "session_id": session_id,
        "plan": pending_plan,
    }


@router.post("/chat/{session_id}/plan/approve")
async def approve_plan(session_id: str):
    """Approve the pending plan and transition to code mode.

    Returns SSE stream for the implementation phase.
    """
    if session_id not in _sessions:
        raise HTTPException(status_code=404, detail="Session not found")

    session = _sessions[session_id]
    runner = session.get("runner")

    if not runner:
        raise HTTPException(status_code=400, detail="Session has no active runner")

    if not runner.has_pending_plan():
        raise HTTPException(status_code=400, detail="No pending plan to approve")

    # Create SSE handler for streaming the implementation
    sse_handler = SSEHandler(agent_name="Emdash Code")

    async def _run_approval():
        loop = asyncio.get_event_loop()

        try:
            # Wire up SSE handler
            from ..agent.events import AgentEventEmitter

            class SSEBridgeHandler:
                def __init__(self, sse_handler: SSEHandler):
                    self._sse = sse_handler

                def handle(self, event):
                    self._sse.handle(event)

            emitter = AgentEventEmitter(agent_name="Emdash Code")
            emitter.add_handler(SSEBridgeHandler(sse_handler))
            runner.emitter = emitter

            sse_handler.emit(EventType.SESSION_START, {
                "agent_name": "Emdash Code",
                "model": session.get("model", "unknown"),
                "session_id": session_id,
                "query": "Plan approved - implementing...",
                "plan_approved": True,
            })

            # Reset cycle state for new mode
            from ..agent.tools.modes import ModeState
            ModeState.get_instance().reset_cycle()

            # Approve and run implementation
            await loop.run_in_executor(
                _executor,
                runner.approve_plan,
            )

            session["plan_mode"] = False  # Now in code mode

            sse_handler.emit(EventType.SESSION_END, {
                "success": True,
                "session_id": session_id,
            })

        except Exception as e:
            sse_handler.emit(EventType.ERROR, {"message": str(e)})
            sse_handler.emit(EventType.SESSION_END, {
                "success": False,
                "error": str(e),
                "session_id": session_id,
            })

        finally:
            sse_handler.close()

    asyncio.create_task(_run_approval())

    return StreamingResponse(
        sse_handler,
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Session-ID": session_id,
        },
    )


@router.post("/chat/{session_id}/plan/reject")
async def reject_plan(session_id: str, feedback: str = ""):
    """Reject the pending plan with feedback.

    Returns SSE stream for the revised planning phase.
    """
    if session_id not in _sessions:
        raise HTTPException(status_code=404, detail="Session not found")

    session = _sessions[session_id]
    runner = session.get("runner")

    if not runner:
        raise HTTPException(status_code=400, detail="Session has no active runner")

    if not runner.has_pending_plan():
        raise HTTPException(status_code=400, detail="No pending plan to reject")

    sse_handler = SSEHandler(agent_name="Emdash Code")

    async def _run_rejection():
        loop = asyncio.get_event_loop()

        try:
            from ..agent.events import AgentEventEmitter

            class SSEBridgeHandler:
                def __init__(self, sse_handler: SSEHandler):
                    self._sse = sse_handler

                def handle(self, event):
                    self._sse.handle(event)

            emitter = AgentEventEmitter(agent_name="Emdash Code")
            emitter.add_handler(SSEBridgeHandler(sse_handler))
            runner.emitter = emitter

            sse_handler.emit(EventType.SESSION_START, {
                "agent_name": "Emdash Code",
                "model": session.get("model", "unknown"),
                "session_id": session_id,
                "query": f"Plan rejected - revising... {feedback}",
                "plan_rejected": True,
            })

            # Reset cycle state for revision
            from ..agent.tools.modes import ModeState
            ModeState.get_instance().reset_cycle()

            # Reject and continue planning
            await loop.run_in_executor(
                _executor,
                lambda: runner.reject_plan(feedback),
            )

            sse_handler.emit(EventType.SESSION_END, {
                "success": True,
                "session_id": session_id,
            })

        except Exception as e:
            sse_handler.emit(EventType.ERROR, {"message": str(e)})
            sse_handler.emit(EventType.SESSION_END, {
                "success": False,
                "error": str(e),
                "session_id": session_id,
            })

        finally:
            sse_handler.close()

    asyncio.create_task(_run_rejection())

    return StreamingResponse(
        sse_handler,
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Session-ID": session_id,
        },
    )


@router.post("/chat/{session_id}/planmode/approve")
async def approve_plan_mode(session_id: str):
    """Approve entering plan mode.

    Called when user approves the agent's request to enter plan mode.
    Returns SSE stream for the planning phase.
    """
    if session_id not in _sessions:
        raise HTTPException(status_code=404, detail="Session not found")

    session = _sessions[session_id]
    runner = session.get("runner")

    if not runner:
        raise HTTPException(status_code=400, detail="Session has no active runner")

    # Check if plan mode was actually requested
    from ..agent.tools.modes import ModeState
    state = ModeState.get_instance()
    if not state.plan_mode_requested:
        raise HTTPException(status_code=400, detail="No pending plan mode request")

    sse_handler = SSEHandler(agent_name="Emdash Code")

    async def _run_approval():
        loop = asyncio.get_event_loop()

        try:
            from ..agent.events import AgentEventEmitter

            class SSEBridgeHandler:
                def __init__(self, sse_handler: SSEHandler):
                    self._sse = sse_handler

                def handle(self, event):
                    self._sse.handle(event)

            emitter = AgentEventEmitter(agent_name="Emdash Code")
            emitter.add_handler(SSEBridgeHandler(sse_handler))
            runner.emitter = emitter

            sse_handler.emit(EventType.SESSION_START, {
                "agent_name": "Emdash Code",
                "model": session.get("model", "unknown"),
                "session_id": session_id,
                "query": "Plan mode approved - entering plan mode...",
                "plan_mode_approved": True,
            })

            # Approve and enter plan mode
            await loop.run_in_executor(
                _executor,
                runner.approve_plan_mode,
            )

            sse_handler.emit(EventType.SESSION_END, {
                "success": True,
                "session_id": session_id,
            })

        except Exception as e:
            sse_handler.emit(EventType.ERROR, {"message": str(e)})
            sse_handler.emit(EventType.SESSION_END, {
                "success": False,
                "error": str(e),
                "session_id": session_id,
            })

        finally:
            sse_handler.close()

    asyncio.create_task(_run_approval())

    return StreamingResponse(
        sse_handler,
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Session-ID": session_id,
        },
    )


@router.post("/chat/{session_id}/clarification/answer")
async def answer_clarification(session_id: str, answer: str):
    """Answer a pending clarification question.

    Called when the user responds to a clarification question asked by the agent
    via ask_choice_questions tool. This resumes the agent with the user's answer.

    Args:
        session_id: The session ID
        answer: The user's answer to the clarification question

    Returns:
        SSE stream for the agent's continued execution
    """
    if session_id not in _sessions:
        raise HTTPException(status_code=404, detail="Session not found")

    session = _sessions[session_id]
    runner = session.get("runner")

    if not runner:
        raise HTTPException(status_code=400, detail="Session has no active runner")

    sse_handler = SSEHandler(agent_name="Emdash Code")

    async def _run_answer():
        loop = asyncio.get_event_loop()

        try:
            from ..agent.events import AgentEventEmitter

            class SSEBridgeHandler:
                def __init__(self, sse_handler: SSEHandler):
                    self._sse = sse_handler

                def handle(self, event):
                    self._sse.handle(event)

            emitter = AgentEventEmitter(agent_name="Emdash Code")
            emitter.add_handler(SSEBridgeHandler(sse_handler))
            runner.emitter = emitter

            sse_handler.emit(EventType.SESSION_START, {
                "agent_name": "Emdash Code",
                "model": session.get("model", "unknown"),
                "session_id": session_id,
                "query": f"Clarification answered: {answer[:100]}...",
                "clarification_answered": True,
            })

            # Answer the clarification and resume the agent
            await loop.run_in_executor(
                _executor,
                lambda: runner.answer_clarification(answer),
            )

            sse_handler.emit(EventType.SESSION_END, {
                "success": True,
                "session_id": session_id,
            })

        except Exception as e:
            sse_handler.emit(EventType.ERROR, {"message": str(e)})
            sse_handler.emit(EventType.SESSION_END, {
                "success": False,
                "error": str(e),
                "session_id": session_id,
            })

        finally:
            sse_handler.close()

    asyncio.create_task(_run_answer())

    return StreamingResponse(
        sse_handler,
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Session-ID": session_id,
        },
    )


class ChoiceAnswersRequest(BaseModel):
    """Request body for choice answers."""
    responses: list[dict]


@router.post("/chat/{session_id}/choices/answer")
async def answer_choice_questions(session_id: str, request: ChoiceAnswersRequest):
    """Answer pending choice questions.

    Called when the user responds to choice questions asked by the agent
    via ask_choice_questions tool. This resumes the agent with the user's selections.

    Args:
        session_id: The session ID
        request: Request body with list of responses (question + answer)

    Returns:
        SSE stream for the agent's continued execution
    """
    if session_id not in _sessions:
        raise HTTPException(status_code=404, detail="Session not found")

    session = _sessions[session_id]
    runner = session.get("runner")

    if not runner:
        raise HTTPException(status_code=400, detail="Session has no active runner")

    sse_handler = SSEHandler(agent_name="Emdash Code")
    responses = request.responses

    async def _run_answer():
        loop = asyncio.get_event_loop()

        try:
            from ..agent.events import AgentEventEmitter

            class SSEBridgeHandler:
                def __init__(self, sse_handler: SSEHandler):
                    self._sse = sse_handler

                def handle(self, event):
                    self._sse.handle(event)

            emitter = AgentEventEmitter(agent_name="Emdash Code")
            emitter.add_handler(SSEBridgeHandler(sse_handler))
            runner.emitter = emitter

            sse_handler.emit(EventType.SESSION_START, {
                "agent_name": "Emdash Code",
                "model": session.get("model", "unknown"),
                "session_id": session_id,
                "query": f"Choice questions answered: {len(responses)} selections",
                "choices_answered": True,
            })

            # Answer the choice questions and resume the agent
            await loop.run_in_executor(
                _executor,
                lambda: runner.answer_choice_questions(responses),
            )

            sse_handler.emit(EventType.SESSION_END, {
                "success": True,
                "session_id": session_id,
            })

        except Exception as e:
            sse_handler.emit(EventType.ERROR, {"message": str(e)})
            sse_handler.emit(EventType.SESSION_END, {
                "success": False,
                "error": str(e),
                "session_id": session_id,
            })

        finally:
            sse_handler.close()

    asyncio.create_task(_run_answer())

    return StreamingResponse(
        sse_handler,
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Session-ID": session_id,
        },
    )


@router.post("/chat/{session_id}/planmode/reject")
async def reject_plan_mode(session_id: str, feedback: str = ""):
    """Reject entering plan mode.

    Called when user rejects the agent's request to enter plan mode.
    Returns SSE stream for continued code mode execution.
    """
    if session_id not in _sessions:
        raise HTTPException(status_code=404, detail="Session not found")

    session = _sessions[session_id]
    runner = session.get("runner")

    if not runner:
        raise HTTPException(status_code=400, detail="Session has no active runner")

    # Check if plan mode was actually requested
    from ..agent.tools.modes import ModeState
    state = ModeState.get_instance()
    if not state.plan_mode_requested:
        raise HTTPException(status_code=400, detail="No pending plan mode request")

    sse_handler = SSEHandler(agent_name="Emdash Code")

    async def _run_rejection():
        loop = asyncio.get_event_loop()

        try:
            from ..agent.events import AgentEventEmitter

            class SSEBridgeHandler:
                def __init__(self, sse_handler: SSEHandler):
                    self._sse = sse_handler

                def handle(self, event):
                    self._sse.handle(event)

            emitter = AgentEventEmitter(agent_name="Emdash Code")
            emitter.add_handler(SSEBridgeHandler(sse_handler))
            runner.emitter = emitter

            sse_handler.emit(EventType.SESSION_START, {
                "agent_name": "Emdash Code",
                "model": session.get("model", "unknown"),
                "session_id": session_id,
                "query": f"Plan mode rejected - continuing in code mode... {feedback}",
                "plan_mode_rejected": True,
            })

            # Reject and stay in code mode
            await loop.run_in_executor(
                _executor,
                lambda: runner.reject_plan_mode(feedback),
            )

            sse_handler.emit(EventType.SESSION_END, {
                "success": True,
                "session_id": session_id,
            })

        except Exception as e:
            sse_handler.emit(EventType.ERROR, {"message": str(e)})
            sse_handler.emit(EventType.SESSION_END, {
                "success": False,
                "error": str(e),
                "session_id": session_id,
            })

        finally:
            sse_handler.close()

    asyncio.create_task(_run_rejection())

    return StreamingResponse(
        sse_handler,
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Session-ID": session_id,
        },
    )


@router.get("/chat/{session_id}/todos")
async def get_todos(session_id: str):
    """Get the current todo list for a session.

    Returns the agent's task list including status of each item.
    """
    if session_id not in _sessions:
        raise HTTPException(status_code=404, detail="Session not found")

    # Get todos from TaskState singleton
    from ..agent.tools.tasks import TaskState
    state = TaskState.get_instance()

    todos = state.get_all_tasks()

    # Count by status
    pending = sum(1 for t in todos if t["status"] == "pending")
    in_progress = sum(1 for t in todos if t["status"] == "in_progress")
    completed = sum(1 for t in todos if t["status"] == "completed")

    return {
        "session_id": session_id,
        "todos": todos,
        "summary": {
            "total": len(todos),
            "pending": pending,
            "in_progress": in_progress,
            "completed": completed,
        },
    }


@router.post("/chat/{session_id}/todos")
async def add_todo(session_id: str, title: str, description: str = ""):
    """Add a new todo item to the agent's task list.

    This allows users to inject tasks for the agent to work on.
    """
    if session_id not in _sessions:
        raise HTTPException(status_code=404, detail="Session not found")

    if not title or not title.strip():
        raise HTTPException(status_code=400, detail="Title is required")

    # Add todo via TaskState singleton
    from ..agent.tools.tasks import TaskState
    state = TaskState.get_instance()

    task = state.add_task(title=title.strip(), description=description.strip())

    return {
        "session_id": session_id,
        "task": task.to_dict(),
        "total_tasks": len(state.tasks),
    }


# ==================== Worktree Management ====================


@router.get("/chat/{session_id}/worktree")
async def get_worktree_status(session_id: str):
    """Get the worktree status for a session.

    Returns information about whether the session is using a worktree,
    the branch name, and any uncommitted changes.
    """
    if session_id not in _sessions:
        raise HTTPException(status_code=404, detail="Session not found")

    session = _sessions[session_id]
    worktree_info = session.get("worktree_info")

    if not worktree_info:
        return {
            "session_id": session_id,
            "has_worktree": False,
        }

    # Check for uncommitted changes in the worktree
    import subprocess
    try:
        result = subprocess.run(
            ["git", "status", "--porcelain"],
            cwd=str(worktree_info.path),
            capture_output=True,
            text=True,
        )
        has_changes = bool(result.stdout.strip())
        changes = result.stdout.strip().split("\n") if has_changes else []
    except Exception:
        has_changes = False
        changes = []

    return {
        "session_id": session_id,
        "has_worktree": True,
        "worktree_path": str(worktree_info.path),
        "branch": worktree_info.branch,
        "base_branch": worktree_info.base_branch,
        "has_changes": has_changes,
        "changes": changes,
    }


@router.post("/chat/{session_id}/worktree/apply")
async def apply_worktree_changes(session_id: str, commit_message: str = None):
    """Apply worktree changes to the main branch.

    This merges the worktree branch into the base branch and cleans up.
    """
    from ..utils.logger import log

    if session_id not in _sessions:
        raise HTTPException(status_code=404, detail="Session not found")

    session = _sessions[session_id]
    worktree_info = session.get("worktree_info")

    if not worktree_info:
        raise HTTPException(status_code=400, detail="Session is not using a worktree")

    import subprocess
    from pathlib import Path

    try:
        worktree_path = worktree_info.path
        branch = worktree_info.branch
        base_branch = worktree_info.base_branch

        # First, commit any uncommitted changes in the worktree
        result = subprocess.run(
            ["git", "status", "--porcelain"],
            cwd=str(worktree_path),
            capture_output=True,
            text=True,
        )
        if result.stdout.strip():
            # Stage all changes
            subprocess.run(["git", "add", "-A"], cwd=str(worktree_path), check=True)
            # Commit
            msg = commit_message or f"Agent session {session_id[:8]} changes"
            subprocess.run(
                ["git", "commit", "-m", msg],
                cwd=str(worktree_path),
                check=True,
            )
            log.info(f"Committed changes in worktree: {msg}")

        # Get the main repo root (parent of .emdash-worktrees)
        from ..config import get_config
        config = get_config()
        main_repo = Path(config.repo_root) if config.repo_root else Path.cwd()

        # Merge the worktree branch into base branch
        subprocess.run(
            ["git", "checkout", base_branch],
            cwd=str(main_repo),
            check=True,
        )
        subprocess.run(
            ["git", "merge", branch, "--no-ff", "-m", f"Merge {branch}"],
            cwd=str(main_repo),
            check=True,
        )
        log.info(f"Merged {branch} into {base_branch}")

        # Clean up the worktree
        from ..agent.worktree import WorktreeManager
        worktree_manager = WorktreeManager(main_repo)
        worktree_manager.remove_worktree(worktree_info.task_slug)
        log.info(f"Removed worktree {worktree_info.task_slug}")

        # Clear worktree info from session
        session["worktree_info"] = None

        return {
            "session_id": session_id,
            "success": True,
            "message": f"Changes from {branch} merged into {base_branch}",
        }

    except subprocess.CalledProcessError as e:
        log.error(f"Failed to apply worktree changes: {e}")
        raise HTTPException(
            status_code=500,
            detail=f"Failed to apply changes: {e.stderr if hasattr(e, 'stderr') else str(e)}"
        )
    except Exception as e:
        log.error(f"Error applying worktree changes: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@router.delete("/chat/{session_id}/worktree")
async def discard_worktree(session_id: str):
    """Discard worktree changes and clean up.

    This removes the worktree and branch without merging.
    """
    from ..utils.logger import log

    if session_id not in _sessions:
        raise HTTPException(status_code=404, detail="Session not found")

    session = _sessions[session_id]
    worktree_info = session.get("worktree_info")

    if not worktree_info:
        raise HTTPException(status_code=400, detail="Session is not using a worktree")

    try:
        from pathlib import Path
        from ..config import get_config
        from ..agent.worktree import WorktreeManager

        config = get_config()
        main_repo = Path(config.repo_root) if config.repo_root else Path.cwd()

        worktree_manager = WorktreeManager(main_repo)
        worktree_manager.remove_worktree(worktree_info.task_slug)
        log.info(f"Discarded worktree {worktree_info.task_slug}")

        # Clear worktree info from session
        session["worktree_info"] = None

        return {
            "session_id": session_id,
            "success": True,
            "message": f"Worktree {worktree_info.task_slug} discarded",
        }

    except Exception as e:
        log.error(f"Error discarding worktree: {e}")
        raise HTTPException(status_code=500, detail=str(e))
