"""
多行用户输入框
- UserInput: Enter 提交，Shift+Enter 换行，支持历史记录、大文本粘贴占位符、命令选择器
"""

import re
import asyncio
from typing import Optional, TYPE_CHECKING

from textual.widgets import TextArea
from textual.widgets.text_area import TextAreaTheme, Selection
from textual.message import Message
from textual import events
from rich.style import Style

if TYPE_CHECKING:
    from textual.events import Key

from integrations.cli.widgets.selectable import _copy_to_clipboard
from integrations.cli.widgets.command_list import CommandList, COMMAND_SUB_MENUS, AUTO_OPTIONS, DIRECT_EXECUTE_COMMANDS


class UserInput(TextArea):
    """多行用户输入框，Enter 提交，Shift+Enter 换行，支持历史记录"""

    PROMPT = "›"

    # 透明主题：让 TextArea 内部渲染与 App 背景一致
    _TRANSPARENT_THEME = TextAreaTheme(
        name="transparent",
        base_style=Style(),
        cursor_style=Style(reverse=True),
        cursor_line_style=Style(),
        bracket_matching_style=Style(bold=True, underline=True),
        selection_style=Style(reverse=True),
        gutter_style=Style(),
    )

    MAX_HISTORY = 100

    DEFAULT_CSS = """
    UserInput {
        height: auto;
        min-height: 3;
        max-height: 8;
        border: none;
        padding: 0 1 0 0;
    }
    """

    class Submitted(Message):
        def __init__(self, value: str, data: Optional[dict] = None) -> None:
            super().__init__()
            self.value = value
            self.data = data or {}

    def __init__(self, **kwargs) -> None:
        super().__init__(
            show_line_numbers=False,
            soft_wrap=True,
            tab_behavior="focus",
            **kwargs,
        )
        self.register_theme(self._TRANSPARENT_THEME)
        self.theme = "transparent"

        self._history: list[str] = []
        self._history_index: int = -1
        self._draft: str = ""

        # 粘贴占位符系统
        self._paste_slots: dict[int, str] = {}
        self._paste_counter: int = 0
        self._PASTE_LINE_THRESHOLD = 5

        # 命令选择器
        self._command_list: Optional[CommandList] = None
        self._command_list_visible: bool = False
        self._current_command: str = ""
        self._server_url: str = ""
        self._sessions_cache: list = []

    def set_command_list(self, command_list: CommandList):
        """设置命令列表组件引用"""
        self._command_list = command_list

    def on_mount(self) -> None:
        pass

    # ==================== 粘贴占位符系统 ====================

    _PASTE_PLACEHOLDER_RE = re.compile(r"\[Pasted text #(\d+) \+\d+ lines\]")

    async def _on_paste(self, event: events.Paste) -> None:
        """拦截大文本粘贴，用占位符替代以避免输入框卡顿"""
        event.prevent_default()
        event.stop()

        if self.read_only:
            return

        pasted = event.text
        pasted = pasted.replace("\r\n", "\n").replace("\r", "\n")
        line_count = pasted.count("\n") + 1

        if line_count <= self._PASTE_LINE_THRESHOLD:
            if result := self._replace_via_keyboard(pasted, *self.selection):
                self.move_cursor(result.end_location)
                self.focus()
            return

        self._paste_counter += 1
        slot_id = self._paste_counter
        self._paste_slots[slot_id] = pasted

        placeholder = f"[Pasted text #{slot_id} +{line_count} lines]"

        if result := self._replace_via_keyboard(placeholder, *self.selection):
            self.move_cursor(result.end_location)
            self.focus()

    def _resolve_paste_placeholders(self, text: str) -> str:
        """将文本中的粘贴占位符替换为真实内容"""
        def _replacer(match: re.Match) -> str:
            slot_id = int(match.group(1))
            return self._paste_slots.get(slot_id, match.group(0))
        return self._PASTE_PLACEHOLDER_RE.sub(_replacer, text)

    def _clear_paste_slots(self) -> None:
        self._paste_slots.clear()

    def clear(self) -> None:
        """清空输入框并重置粘贴槽"""
        self._clear_paste_slots()
        super().clear()

    async def _on_mouse_up(self, event: events.MouseUp) -> None:
        """鼠标释放时，如果有选中文本则自动复制到系统剪贴板并取消选区"""
        self._end_mouse_selection()
        selected = self.selected_text
        if selected:
            _copy_to_clipboard(selected)
            end = self.selection.end
            self.selection = Selection(end, end)

    def on_mouse_scroll_up(self, event) -> None:
        event.stop()
        event.prevent_default()
        try:
            self.scroll_up(animate=False)
        except Exception:
            pass

    def on_mouse_scroll_down(self, event) -> None:
        event.stop()
        event.prevent_default()
        try:
            self.scroll_down(animate=False)
        except Exception:
            pass

    def _scroll_output(self, direction: str) -> None:
        """将滚动操作转发给主输出区 RichLog"""
        try:
            from textual.widgets import RichLog
            log = self.screen.query_one("#output-log", RichLog)
            if direction == "pageup":
                log.scroll_page_up(animate=False)
            elif direction == "pagedown":
                log.scroll_page_down(animate=False)
            elif direction == "up":
                log.scroll_up(animate=False)
            elif direction == "down":
                log.scroll_down(animate=False)
        except Exception:
            pass

    def _add_to_history(self, text: str) -> None:
        if not text or not text.strip():
            return
        if self._history and self._history[-1] == text:
            return
        self._history.append(text)
        if len(self._history) > self.MAX_HISTORY:
            self._history.pop(0)

    # ==================== 命令选择器 ====================

    async def _open_command_list(self, search: str = ""):
        """打开命令列表（优化：先显示，会话列表后台加载）"""
        if self._command_list is None:
            return
        try:
            from integrations.cli.commands import get_command_handler
            handler = get_command_handler()
            commands = []
            for name, info in handler.commands.items():
                if info.hidden or name != info.name:
                    continue
                commands.append((name, info.description, info.category.value))
            commands.sort(key=lambda x: x[0])

            models = getattr(self, '_available_models', [])
            agents = [
                ('build', '编码与构建'),
                ('plan', '规划与设计'),
                ('general', '通用对话'),
                ('explore', '代码探索'),
                ('web', '浏览器自动化'),
            ]

            sessions = self._sessions_cache or []

            self._command_list.set_extra_data({
                'models': models, 'agents': agents, 'sessions': sessions,
                'auto_options': AUTO_OPTIONS,
            })
            self._command_list.set_commands(commands, search)
            self._command_list.display = True
            self._command_list_visible = True

            try:
                panel = self.app.query_one("#panel-area")
                panel.display = True
            except Exception:
                pass

            asyncio.create_task(self._refresh_sessions_async())
        except Exception:
            pass

    def _hide_command_list(self):
        """隐藏命令列表"""
        if self._command_list:
            self._command_list.display = False
            self._command_list.clear()
        self._command_list_visible = False
        self._current_command = ""
        self._check_and_hide_panel()

    def _check_and_hide_panel(self):
        """检查功能区是否有可见子区域，如果没有则隐藏"""
        try:
            panel = self.app.query_one("#panel-area")
            cmd_list = self.app.query_one("#panel-command-list")
            perm = self.app.query_one("#panel-permission")
            clarify = self.app.query_one("#panel-clarify")
            if cmd_list.display == False:
                if perm.display == False and clarify.display == False:
                    panel.display = False
        except Exception:
            pass

    async def _update_command_list_state(self):
        """根据当前输入文本，自动显示/过滤/隐藏命令列表"""
        text = self.text
        if text.startswith("/") and "\n" not in text:
            search = text[1:]
            if not self._command_list_visible:
                await self._open_command_list(search)
            elif not self._command_list.is_sub_menu():
                self._command_list.filter(search)
        elif self._command_list_visible:
            self._hide_command_list()

    def _handle_command_selection(self):
        """处理命令列表中的选择确认"""
        if self._command_list.is_sub_menu():
            selected = self._command_list.get_selected()
            if selected:
                cmd = self._current_command
                if cmd == "session":
                    self._hide_command_list()
                    self.clear()
                    if isinstance(selected, tuple):
                        selected = selected[0]
                    self.post_message(
                        UserInput.Submitted(
                            f"/{cmd}",
                            data={"action": "switch_session", "session_id": selected},
                        )
                    )
                    return
                if isinstance(selected, tuple):
                    selected = selected[0]
                self.text = f"/{cmd} {selected}"
                self.cursor_location = (0, len(self.text))
            self._hide_command_list()
            self._submit_current()
            return

        if not self._command_list.display or not self._command_list._filtered:
            self._hide_command_list()
            self._submit_current()
            return

        selected = self._command_list.get_selected()
        if not selected:
            self._hide_command_list()
            self._submit_current()
            return

        cmd_name, _, _ = selected

        sub_menu_type = COMMAND_SUB_MENUS.get(cmd_name)
        if sub_menu_type:
            self._current_command = cmd_name
            self.text = f"/{cmd_name}"
            self.cursor_location = (0, len(self.text))
            if sub_menu_type == "sessions":
                self._command_list._extra_data["sessions"] = self._sessions_cache
            self._command_list.set_sub_menu(sub_menu_type,
                self._command_list._extra_data.get(sub_menu_type, []))
            return

        if cmd_name in DIRECT_EXECUTE_COMMANDS:
            self._hide_command_list()
            self.text = f"/{cmd_name}"
            self._submit_current()
            return

        self.text = f"/{cmd_name} "
        self.cursor_location = (0, len(self.text))
        self._hide_command_list()

    async def _fetch_sessions(self) -> list:
        """从服务端获取会话列表（前10条）"""
        sessions = []
        server_url = getattr(self, '_server_url', '')
        if not server_url:
            return sessions
        try:
            import aiohttp
            async with aiohttp.ClientSession() as http_session:
                async with http_session.get(f"{server_url}/sessions?limit=10", timeout=aiohttp.ClientTimeout(total=10.0)) as resp:
                    result = await resp.json()
                    for s in result.get("sessions", [])[:10]:
                        sid = s.get("id", "")
                        preview = s.get("preview", "")
                        time_str = ""
                        created = s.get("created_at", "")
                        if created:
                            try:
                                dt = created.split("T")
                                if len(dt) >= 2:
                                    time_str = dt[0][5:] + " " + dt[1][:5]
                            except Exception:
                                pass
                        parts = []
                        parts.append(f"[{sid[:8]}]")
                        if time_str:
                            parts.append(time_str)
                        title = s.get("title", "")
                        if title and title not in ("New Session", "新会话"):
                            parts.append(title[:50])
                        if preview:
                            preview_clean = preview.replace("\n", " ").replace("\r", "")
                            parts.append(preview_clean[:100])
                        desc = " ".join(parts)
                        sessions.append((sid, desc))
        except Exception as e:
            from core.logger import log_event
            log_event("fetch_sessions_error", error=str(e), server_url=server_url)
        self._sessions_cache = sessions
        return sessions

    async def _refresh_sessions_async(self):
        """后台刷新会话列表，不阻塞 UI"""
        try:
            sessions = await self._fetch_sessions()
            if sessions and self._command_list and self._command_list_visible:
                self._command_list._extra_data["sessions"] = sessions
                if self._command_list.is_sub_menu() and self._current_command == "session":
                    self._command_list.set_sub_menu("sessions", sessions)
        except Exception:
            pass

    def _submit_current(self):
        """提交当前输入框内容"""
        text = self.text.strip()
        if self._paste_slots:
            text = self._resolve_paste_placeholders(text)
        self._add_to_history(text)
        self._history_index = -1
        self._draft = ""
        self.clear()
        self.post_message(UserInput.Submitted(text))

    def set_available_models(self, models: list):
        self._available_models = models

    def set_available_agents(self, agents: list):
        self._available_agents = agents

    def _navigate_history(self, direction: int) -> None:
        if not self._history:
            return
        if self._history_index == -1:
            if direction == -1:
                self._draft = self.text
                self._history_index = len(self._history) - 1
            else:
                return
        else:
            self._history_index += direction
            if self._history_index < 0:
                self._history_index = 0
            elif self._history_index >= len(self._history):
                self._history_index = -1
                self.text = self._draft
                self.cursor_location = (0, len(self._draft))
                return
        self.text = self._history[self._history_index]
        self.cursor_location = (0, len(self.text))

    async def _on_key(self, event) -> None:
        if event.key == "pageup":
            event.stop()
            event.prevent_default()
            self._scroll_output("pageup")
            return
        if event.key == "pagedown":
            event.stop()
            event.prevent_default()
            self._scroll_output("pagedown")
            return

        if not self._command_list_visible:
            if event.key in ("escape+up", "ctrl+p"):
                event.stop()
                event.prevent_default()
                self._navigate_history(-1)
                return
            if event.key in ("escape+down", "ctrl+n"):
                event.stop()
                event.prevent_default()
                self._navigate_history(1)
                return

        if self._command_list_visible:
            if event.key in ("up", "down"):
                event.stop()
                event.prevent_default()
                if event.key == "up":
                    self._command_list.select_prev()
                else:
                    self._command_list.select_next()
                return

            if event.key in ("enter", "ctrl+m"):
                event.stop()
                event.prevent_default()
                self._handle_command_selection()
                return

            if event.key == "escape":
                event.stop()
                event.prevent_default()
                if self._command_list.is_sub_menu():
                    self._command_list.back_to_main()
                else:
                    self._hide_command_list()
                    self.text = ""
                    self._clear_paste_slots()
                return

            if event.key == "tab":
                event.stop()
                event.prevent_default()
                selected = self._command_list.get_selected()
                if selected and not self._command_list.is_sub_menu():
                    name, _, _ = selected
                    self.text = f"/{name}"
                    self.cursor_location = (0, len(self.text))
                    self._command_list.filter(name)
                return

            if event.key == "backspace" and self._command_list.is_sub_menu():
                event.stop()
                event.prevent_default()
                self._command_list.back_to_main()
                return

        if not self._command_list_visible and event.key in ("up", "down"):
            if self._history_index != -1:
                event.stop()
                event.prevent_default()
                self._navigate_history(-1 if event.key == "up" else 1)
                return

            cursor = self.cursor_location
            text = self.text
            current_line_start = text.rfind("\n", 0, cursor[1]) + 1
            line_end = text.find("\n", cursor[1])
            if line_end == -1:
                line_end = len(text)

            if event.key == "up" and cursor[1] == current_line_start:
                event.stop()
                event.prevent_default()
                self._navigate_history(-1)
                return
            if event.key == "down" and (cursor[1] == line_end or cursor[1] == line_end + 1):
                event.stop()
                event.prevent_default()
                self._navigate_history(1)
                return

        if event.key == "ctrl+d":
            event.stop()
            event.prevent_default()
            self._hide_command_list()
            self.clear()
            self.post_message(UserInput.Submitted("/exit"))
            return

        if event.key in ("shift+enter", "alt+enter", "meta+enter", "ctrl+enter"):
            event.stop()
            event.prevent_default()
            self.insert("\n")
            return

        if event.key in ("enter", "ctrl+m"):
            event.stop()
            event.prevent_default()
            self._hide_command_list()
            self._submit_current()
            return

        if self._history_index != -1 and event.key not in ("shift", "ctrl", "alt"):
            self._history_index = -1
            self._draft = ""

        await super()._on_key(event)

        text = self.text
        if text.startswith("/") and "\n" not in text:
            search = text[1:]
            if not self._command_list_visible:
                await self._open_command_list(search)
            elif not self._command_list.is_sub_menu():
                self._command_list.filter(search)
        elif self._command_list_visible:
            self._hide_command_list()
