"""
渲染器模块 - 统一入口

StreamingRenderer: 流式渲染管理器，处理所有服务端事件的渲染
全局实例: get_tui() / get_streaming_renderer()
统一导出: 所有子模块的公共 API
"""

import asyncio
from typing import Optional, Dict, Any

from rich.text import Text
from textual.widgets import RichLog
from textual.css.query import NoMatches

# 子模块导入
from interface.styles import Styles, Icons
from interface.widgets import LeftAlignedMarkdown, PermissionBar, UserInput
from interface.tools_display import ToolExecution, ToolDisplayManager
from interface.app import DundunApp
from interface.tui import EnhancedTUI


# ============================================================================
# 流式渲染管理器
# ============================================================================

class StreamingRenderer:
    """
    流式渲染管理器

    职责：管理文本流式渲染、工具执行展示、Thinking 动画、渲染生命周期
    设计：CLI 层只调用 on_xxx 事件方法，所有渲染逻辑封装在此类中
    """

    CODE_THEME = "monokai"

    def __init__(self, tui: EnhancedTUI):
        self.tui = tui
        self._tool_display = ToolDisplayManager()

        # clarify 工具 ID 集合（不走 ToolDisplayManager）
        self._clarify_tool_ids: set = set()

        # 子任务（子 Agent）固定展示区域：使用 RichLog 的 live 行刷新
        # key: subtask_session_id
        self._subtask_states: Dict[str, Dict[str, Any]] = {}

        # 子任务 spinner 动画
        self._subtask_spinner_frame: int = 0
        self._subtask_spinner_timer = None

        # 工具 Running spinner 动画
        self._tool_spinner_timer = None

        # 上下文压缩 spinner 动画
        self._compact_spinner_frame: int = 0
        self._compact_spinner_timer = None

        self._is_streaming: bool = False
        self._stream_buffer: str = ""

        # Markdown 渲染累积器
        self._full_stream_text: str = ""
        self._stream_start_line: int = 0

        self._thinking_active: bool = False
        self._tool_live_active: bool = False

        self._input_ready: asyncio.Event = asyncio.Event()
        self._input_ready.set()

        self._processing: bool = False

    # ========== 公共属性 ==========

    @property
    def input_ready(self) -> asyncio.Event:
        return self._input_ready

    @property
    def is_processing(self) -> bool:
        return self._processing

    # ========== 事件处理 API ==========

    def on_text_chunk(self, content: str):
        """处理文本块 - 完整行写入 RichLog，不完整行作为瞬态行"""
        self._stop_thinking()
        self._stop_tool_live()

        if not self._is_streaming:
            self._is_streaming = True
            if self.tui._app is not None:
                try:
                    log = self.tui._app.query_one("#output-log", RichLog)
                    self._stream_start_line = len(log.lines)
                except NoMatches:
                    self._stream_start_line = 0

        self._full_stream_text += content
        self._stream_buffer += content

        if self.tui._app is not None:
            while '\n' in self._stream_buffer:
                line, self._stream_buffer = self._stream_buffer.split('\n', 1)
                self.tui._app.clear_live()
                self.tui._write_safe(Text(line))
            if self._stream_buffer:
                self.tui._app.update_live(Text(self._stream_buffer))
        else:
            self.tui._fallback_console.print(content, end="")
            self._stream_buffer = ""

    def on_text_chunk_complete(self):
        """文本流结束 - 用 Markdown 渲染替换纯文本预览"""
        if self._is_streaming:
            if self.tui._app is not None:
                if self._stream_buffer:
                    self.tui._write_safe(Text(self._stream_buffer))
                    self._stream_buffer = ""
                self.tui._app.clear_live()
                self._replace_stream_with_markdown()
                self.tui._write_safe(Text(""))
            else:
                if self._stream_buffer:
                    self.tui._fallback_console.print(self._stream_buffer, end="")
                    self._stream_buffer = ""
                self.tui._fallback_console.print()
            self._full_stream_text = ""
            self._is_streaming = False

    def on_tool_start(self, tool_id: str, tool_name: str, args: dict):
        """工具开始 - Running 状态作为瞬态行显示（带 spinner 动画）"""
        if tool_name == "task":
            return
        if tool_name == "clarify":
            # clarify 工具：在输出区展示澄清内容，不显示 Running spinner
            self._finalize_markdown()
            self._clarify_tool_ids.add(tool_id)
            context = args.get("context", "")
            question = args.get("question", "")
            lines: list[str] = []
            if context:
                for line in str(context).splitlines():
                    lines.append(f"> {line}")
                lines.append("")
            if question:
                lines.append(f"**问题：** {question}")
            if lines:
                self.tui._write_safe(LeftAlignedMarkdown("\n".join(lines), code_theme=self.CODE_THEME))
            return
        self._finalize_markdown()
        self._tool_display.add_tool(tool_id, tool_name, args)
        self._tool_live_active = True
        self._start_tool_spinner()
        all_renderable = self._tool_display.render()
        if self.tui._app is not None:
            self.tui._app.update_live(all_renderable)
        else:
            self.tui._write_safe(all_renderable)

    def on_tool_end(self, tool_id: str, success: bool, result: str = "", args: dict = None):
        """工具结束 - 更新瞬态行或写入最终结果"""
        tool = self._tool_display.tools.get(tool_id)
        if tool and tool.tool_name == "task":
            return
        if tool_id in self._clarify_tool_ids:
            # clarify 工具：在输出区展示用户选择结果
            self._clarify_tool_ids.discard(tool_id)
            if success and result:
                self.tui._write_safe(
                    LeftAlignedMarkdown(f"**回答：** {result}", code_theme=self.CODE_THEME)
                )
                self.tui._write_safe(
                   "\n"
                )
            return
        self._tool_display.complete_tool(tool_id, success, result, args)
        if not self._tool_display.tools:
            # 所有工具都已完成 - 停止 spinner，清除瞬态行，写入最终内容
            self._stop_tool_spinner()
            if self.tui._app is not None:
                self.tui._app.clear_live()
            final_content = self._tool_display.render()
            self.tui._write_safe(final_content)
            self.tui._write_safe(Text(""))
            self._tool_live_active = False
            self._tool_display.clear_completed()
        else:
            # 还有工具在运行 - 更新瞬态行显示（已完成的 + 仍在运行的）
            if self.tui._app is not None:
                self.tui._app.update_live(self._tool_display.render())

    def on_thinking_start(self):
        self._stop_thinking()
        self._stop_tool_live()
        self._finalize_markdown()
        self._thinking_active = True
        if self.tui._app is not None:
            self.tui._app.start_thinking()
        else:
            thinking_text = Text()
            thinking_text.append("⠋ ", style="bold dark_red")
            thinking_text.append("Thinking...", style="bold dark_red")
            self.tui._write_safe(thinking_text)

    def on_thinking_end(self):
        self._stop_thinking()

    def on_response_end(self, interrupted: bool = False):
        self._finalize_markdown()
        self._cleanup_all()
        if interrupted:
            self.tui._write_safe(Text(""))
            self.tui.print_info("已中断")
        self.tui._write_safe(Text(""))
        self._processing = False
        self._input_ready.set()
        if self.tui._app is not None:
            self.tui._app.enable_input()

    def on_error(self, message: str):
        self._cleanup_all()
        self.tui._write_safe(Text(""))
        self.tui.print_error(message)
        self._processing = False
        self._input_ready.set()
        if self.tui._app is not None:
            self.tui._app.enable_input()

    def on_error_warning(self, message: str):
        """显示错误警告（不终止处理，用于重试场景）"""
        self.tui._write_safe(Text(f"⚠ {message}", style=Styles.WARNING))

    def on_subtask_started(self, data: dict):
        """子任务开始：创建/刷新子任务固定区域"""
        subtask_id = data.get("subtask_session_id") or data.get("session_id")
        if not subtask_id:
            return

        subagent_type = data.get("subagent_type", "unknown").capitalize()
        title = data.get("description") or data.get("title") or ""

        self._subtask_states[subtask_id] = {
            "subagent_type": subagent_type,
            "title": title,
            "status": "running",
            "last": "",
        }
        self._start_subtask_spinner()
        self._render_subtasks_live()

    def on_subtask_completed(self, data: dict):
        """子任务结束：写入最终状态到 RichLog，从 _subtask_states 中移除"""
        subtask_id = data.get("subtask_session_id") or data.get("session_id")
        if not subtask_id:
            return

        state = self._subtask_states.pop(subtask_id, None)
        subagent_type = data.get("subagent_type", "unknown").capitalize()
        title = data.get("description") or data.get("title") or ""
        success = data.get("success", True)
        output = data.get("output", "")

        if state is None:
            state = {"subagent_type": subagent_type, "title": title, "last": ""}

        # 如果没有其他运行中的子任务，先停止 spinner 定时器
        # 防止定时器在 clear_live 后又触发 update_live
        if not self._subtask_states:
            self._stop_subtask_spinner()

        # 先清除瞬态 live 行，再写入持久化内容
        # 否则后续 clear_live 会误删刚写入的持久化行
        if self.tui._app is not None:
            self.tui._app.clear_live()

        # 写入最终结果到 RichLog（持久化）
        header_text = Text()
        subagent_label = state.get('subagent_type', subagent_type)
        title_str = state.get("title") or title
        if success:
            header_text.append("⏺", style="bold green")
            header_text.append(f" {subagent_label}任务", style=Styles.DIM)
            if title_str:
                header_text.append(f"：{title_str}", style=Styles.DIM)
        else:
            header_text.append(f"⏺ {subagent_label}任务", style=Styles.DIM)
            if title_str:
                header_text.append(f"：{title_str}", style=Styles.DIM)

        if success:
            status_line = Text("  ⎿ ", style=Styles.DIM)
            status_line.append("✔ ", style="bold green")
            output_s = str(output).replace("\n", " ").strip() if output else ""
            if output_s:
                status_line.append(f" Done ({output_s})", style=Styles.DIM)
            else:
                status_line.append(" Done", style=Styles.DIM)
        else:
            status_line = Text(f"  ⎿ {state.get('last') or '✖ Failed'}", style=Styles.DIM)

        self.tui._write_safe(header_text)
        self.tui._write_safe(status_line)
        self.tui._write_safe(Text(""))

        # 如果还有其他子任务在运行，刷新 live 区域
        if self._subtask_states:
            self._render_subtasks_live()
        else:
            # 确保彻底清理（双重保险）
            if self.tui._app is not None:
                self.tui._app.clear_live()

    def on_subtask_process(self, subtask_session_id: str, event: dict):
        """子任务过程事件：接收完整事件，根据事件类型更新展示。

        规则：只处理 tool_call_start；其它事件忽略。
        """
        state = self._subtask_states.get(subtask_session_id)
        if state is None:
            # 未收到 started 也允许创建（容错），但不打印 title
            state = {
                "subagent_type": "Unknown",
                "title": "",
                "status": "running",
                "last": "",
            }
            self._subtask_states[subtask_session_id] = state

        event_type = event.get("type", "")
        data = event.get("data", {}) or {}

        # 只关注 tool_call_start
        if event_type == "tool_call_start":
            tool_name = data.get("tool", data.get("tool_name", "unknown"))
            args = data.get("args", {})
            args_s = str(args)
            detail = f"{tool_name} {args_s}" if args_s else tool_name
            if len(detail) > 80:
                detail = detail[:80] + "..."
            state["last"] = f"Running ({detail})"
            self._render_subtasks_live()

    def on_context_compress_start(self):
        """上下文压缩开始（服务端自动触发）"""
        self.tui._write_safe(Text("⏺ 上下文压缩中...", style=Styles.DIM))

    def on_context_compress_end(self, success: bool):
        """上下文压缩结束（服务端自动触发）"""
        msg = "⏺ 上下文压缩完成" if success else "⏺ 上下文压缩失败"
        self.tui._write_safe(Text(msg, style=Styles.DIM))

    # ========== 手动压缩（用户主动触发）==========

    def on_manual_compact_start(self):
        """手动压缩开始 - 启动 spinner 动画"""
        self._start_compact_spinner()
        self._render_compact_live()

    def on_manual_compact_end(self, success: bool, message: str = None):
        """手动压缩结束 - 停止 spinner，显示完成状态"""
        self._stop_compact_spinner()

        # 清除 live 行
        if self.tui._app is not None:
            self.tui._app.clear_live()

        # 自定义消息（如 skipped）或默认消息
        if message:
            msg = f"⏺ {message}"
        else:
            msg = "⏺ 上下文压缩完成" if success else "⏺ 上下文压缩失败"
        style = "bold green" if success else "bold red"
        self.tui._write_safe(Text(msg, style=style))

    def start_processing(self):
        self._processing = True
        self._input_ready.clear()
        if self.tui._app is not None:
            self.tui._app.disable_input()

    def pause_for_interaction(self):
        """暂停渲染以进行交互 - 停止 spinner，清除 live 行"""
        self._finalize_markdown()
        self._stop_tool_spinner()
        if self.tui._app is not None:
            self.tui._app.clear_live()
            self.tui._app.enable_input()

    def resume_after_interaction(self):
        """恢复渲染 - 交互结束后恢复状态"""
        if self.tui._app is not None:
            self.tui._app.disable_input()
        # 重新显示仍在运行的工具（如果有）
        if self._tool_display.tools and self._tool_live_active:
            self._start_tool_spinner()
            all_renderable = self._tool_display.render()
            if self.tui._app is not None:
                self.tui._app.update_live(all_renderable)

    # ========== 内部方法 ==========

    def _start_subtask_spinner(self):
        """启动子任务 spinner 动画定时器"""
        if self._subtask_spinner_timer is not None:
            return  # 已经在运行
        if self.tui._app is not None:
            self._subtask_spinner_frame = 0
            self._subtask_spinner_timer = self.tui._app.set_interval(
                0.08, self._update_subtask_spinner_frame
            )

    def _stop_subtask_spinner(self):
        """停止子任务 spinner 动画定时器"""
        if self._subtask_spinner_timer is not None:
            self._subtask_spinner_timer.stop()
            self._subtask_spinner_timer = None

    def _update_subtask_spinner_frame(self):
        """更新子任务 spinner 帧并刷新显示"""
        self._subtask_spinner_frame = (self._subtask_spinner_frame + 1) % len(Icons.SPINNER_FRAMES)
        # 只有在有运行中的子任务时才刷新
        has_running = any(
            st.get("status") == "running" for st in self._subtask_states.values()
        )
        if has_running:
            self._render_subtasks_live()
        else:
            self._stop_subtask_spinner()

    def _start_tool_spinner(self):
        """启动工具 Running spinner 动画定时器"""
        if self._tool_spinner_timer is not None:
            return  # 已经在运行
        if self.tui._app is not None:
            self._tool_display._spinner_frame = 0
            self._tool_spinner_timer = self.tui._app.set_interval(
                0.08, self._update_tool_spinner_frame
            )

    def _stop_tool_spinner(self):
        """停止工具 Running spinner 动画定时器"""
        if self._tool_spinner_timer is not None:
            self._tool_spinner_timer.stop()
            self._tool_spinner_timer = None

    def _update_tool_spinner_frame(self):
        """更新工具 spinner 帧并刷新 live 显示"""
        self._tool_display.advance_spinner()
        # 只有在有运行中的工具且 live 激活时才刷新
        if self._tool_live_active and self._tool_display.tools:
            if self.tui._app is not None:
                self.tui._app.update_live(self._tool_display.render())
        else:
            self._stop_tool_spinner()

    def _start_compact_spinner(self):
        """启动上下文压缩 spinner 动画定时器"""
        if self._compact_spinner_timer is not None:
            return  # 已经在运行
        if self.tui._app is not None:
            self._compact_spinner_frame = 0
            self._compact_spinner_timer = self.tui._app.set_interval(
                0.08, self._update_compact_spinner_frame
            )

    def _update_compact_spinner_frame(self):
        """更新上下文压缩 spinner 帧并刷新 live 显示"""
        self._compact_spinner_frame = (self._compact_spinner_frame + 1) % len(Icons.SPINNER_FRAMES)
        self._render_compact_live()

    def _render_compact_live(self):
        """渲染上下文压缩 live 行"""
        if self.tui._app is None:
            return
        spinner_char = Icons.SPINNER_FRAMES[self._compact_spinner_frame % len(Icons.SPINNER_FRAMES)]
        text = Text(f"{spinner_char} 上下文压缩中...", style=Styles.DIM)
        self.tui._app.update_live(text)

    def _stop_compact_spinner(self):
        """停止上下文压缩 spinner 动画定时器"""
        if self._compact_spinner_timer is not None:
            self._compact_spinner_timer.stop()
            self._compact_spinner_timer = None

    def _stop_thinking(self):
        if self._thinking_active:
            if self.tui._app is not None:
                self.tui._app.stop_thinking()
            self._thinking_active = False

    def _stop_tool_live(self):
        if self._tool_live_active:
            self._stop_tool_spinner()
            if self.tui._app is not None:
                self.tui._app.clear_live()
            if self._tool_display.completed_tools or self._tool_display.tools:
                self.tui._write_safe(self._tool_display.render())
            self.tui._write_safe(Text(""))
            self._tool_live_active = False
            self._tool_display.clear_completed()

    def _finalize_markdown(self):
        if not self._is_streaming:
            return
        if self._stream_buffer:
            if self.tui._app is not None:
                self.tui._write_safe(Text(self._stream_buffer))
            else:
                self.tui._fallback_console.print(self._stream_buffer, end="")
            self._stream_buffer = ""
        self._is_streaming = False
        if self.tui._app is not None:
            self.tui._app.clear_live()
            self._replace_stream_with_markdown()
            self.tui._write_safe(Text(""))
        else:
            self.tui._fallback_console.print()
        self._full_stream_text = ""

    def _render_subtasks_live(self) -> None:
        """刷新子任务固定区域（在 RichLog 底部 live 区域替换显示）。"""
        if self.tui._app is None:
            return

        if not self._subtask_states:
            # 不负责清理 live：避免误删 tool/thinking 的 live。
            return

        # 用纯文本拼接，避免 Text(str(Text)) 的样式丢失问题
        out_lines: list[str] = []
        for subtask_id, st in self._subtask_states.items():
            subagent_type = st.get("subagent_type", "Unknown")
            title = st.get("title") or ""
            status = st.get("status", "running")
            last = st.get("last", "")

            header = f"⏺ {subagent_type}任务开始"
            if title:
                header += f"：{title}"
            out_lines.append(header)

            if status == "running":
                spinner_char = Icons.SPINNER_FRAMES[self._subtask_spinner_frame % len(Icons.SPINNER_FRAMES)]
                detail = f"{spinner_char} {last}" if last else f"{spinner_char} Running"
            elif status == "done":
                detail = "✔ Done"
            else:
                detail = last or "✖ Failed"

            out_lines.append(f"  ⎿ {detail}  ({subtask_id})")
            out_lines.append("")

        while out_lines and out_lines[-1] == "":
            out_lines.pop()

        self.tui._app.update_live(Text("\n".join(out_lines), style=Styles.DIM))

    def _replace_stream_with_markdown(self):
        """将流式纯文本预览行替换为 Markdown 渲染"""
        if not self._full_stream_text or self.tui._app is None:
            return
        try:
            log = self.tui._app.query_one("#output-log", RichLog)
            log.lines = log.lines[:self._stream_start_line]
            log._line_cache.clear()
            from textual.geometry import Size
            log.virtual_size = Size(log.virtual_size.width, len(log.lines))
            log.refresh()
            md = LeftAlignedMarkdown(self._full_stream_text, code_theme=self.CODE_THEME)
            log.write(md)
        except Exception:
            pass

    def _cleanup_all(self):
        self._stop_thinking()
        self._stop_subtask_spinner()
        self._stop_tool_spinner()
        self._stop_compact_spinner()
        self._is_streaming = False
        self._stream_buffer = ""
        self._full_stream_text = ""
        if self.tui._app is not None:
            self.tui._app.clear_live()
        if self._tool_live_active:
            self._tool_live_active = False
        self._tool_display.tools.clear()
        self._tool_display.completed_tools.clear()


# ============================================================================
# 全局实例
# ============================================================================

_tui: Optional[EnhancedTUI] = None
_streaming_renderer: Optional[StreamingRenderer] = None


def get_tui() -> EnhancedTUI:
    global _tui
    if _tui is None:
        _tui = EnhancedTUI()
    return _tui


def get_streaming_renderer() -> StreamingRenderer:
    global _streaming_renderer
    if _streaming_renderer is None:
        _streaming_renderer = StreamingRenderer(get_tui())
    return _streaming_renderer
