"""
可选择文本组件（支持鼠标拖拽选择 + 自动复制）
- SelectableStatic: 用于状态栏、功能区等非滚动区域
- SelectableRichLog: 主输出区，支持滚动和边缘自动滚动
"""

import sys
import subprocess
from typing import Optional

from textual.widgets import Static, RichLog
from textual.strip import Strip
from rich.style import Style


# ============================================================================
# 公共工具函数
# ============================================================================

def _normalize_selection(sel_start, sel_end):
    """归一化选区坐标，确保 start <= end"""
    if not sel_start or not sel_end:
        return None, None, None, None
    s_line, s_col = sel_start
    e_line, e_col = sel_end
    if s_line > e_line or (s_line == e_line and s_col > e_col):
        s_line, s_col, e_line, e_col = e_line, e_col, s_line, s_col
    return s_line, s_col, e_line, e_col


def _highlight_strip(strip, abs_line, s_line, s_col, e_line, e_col, scroll_x=0):
    """对 Strip 施加选区反色高亮"""
    if abs_line < s_line or abs_line > e_line:
        return strip
    line_width = strip.cell_length
    if line_width == 0:
        return strip
    if abs_line == s_line == e_line:
        sel_start = s_col - scroll_x
        sel_end = e_col - scroll_x
    elif abs_line == s_line:
        sel_start = s_col - scroll_x
        sel_end = line_width
    elif abs_line == e_line:
        sel_start = 0
        sel_end = e_col - scroll_x
    else:
        sel_start = 0
        sel_end = line_width
    sel_start = max(0, min(sel_start, line_width))
    sel_end = max(0, min(sel_end, line_width))
    if sel_start >= sel_end:
        return strip
    selection_style = Style(reverse=True)
    parts = []
    if sel_start > 0:
        parts.append(strip.crop(0, sel_start))
    parts.append(strip.crop(sel_start, sel_end).apply_style(selection_style))
    if sel_end < line_width:
        parts.append(strip.crop(sel_end, line_width))
    return Strip.join(parts)


def _copy_to_clipboard(text: str) -> None:
    """写入系统剪贴板（macOS: pbcopy, Linux: xclip）"""
    try:
        if sys.platform == "darwin":
            subprocess.Popen(
                ["pbcopy"], stdin=subprocess.PIPE,
                stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
            ).communicate(text.encode("utf-8"))
        else:
            subprocess.Popen(
                ["xclip", "-selection", "clipboard"], stdin=subprocess.PIPE,
                stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
            ).communicate(text.encode("utf-8"))
    except Exception:
        pass


# ============================================================================
# SelectableStatic
# ============================================================================

class SelectableStatic(Static):
    """支持拖拽选择复制的 Static - 用于状态栏、功能区等"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._sel_start: Optional[tuple] = None
        self._sel_end: Optional[tuple] = None
        self._selecting: bool = False
        self._has_selection: bool = False

    def on_mouse_down(self, event) -> None:
        if event.button != 1:
            return
        self._sel_start = (event.y, event.x)
        self._sel_end = self._sel_start
        self._selecting = True
        self._has_selection = False
        self.capture_mouse()
        event.stop()
        self.refresh()

    def on_mouse_move(self, event) -> None:
        if not self._selecting:
            return
        self._sel_end = (max(0, event.y), max(0, event.x))
        self._has_selection = True
        event.stop()
        self.refresh()

    def on_mouse_up(self, event) -> None:
        if not self._selecting:
            return
        self._selecting = False
        self.release_mouse()
        if self._has_selection and self._sel_start and self._sel_end:
            text = self._extract_text()
            if text.strip():
                _copy_to_clipboard(text)
        self._sel_start = None
        self._sel_end = None
        self._has_selection = False
        self.refresh()
        event.stop()

    def render_line(self, y: int) -> Strip:
        strip = super().render_line(y)
        if not self._has_selection and not self._selecting:
            return strip
        s_line, s_col, e_line, e_col = _normalize_selection(self._sel_start, self._sel_end)
        if s_line is None:
            return strip
        return _highlight_strip(strip, y, s_line, s_col, e_line, e_col)

    def _extract_text(self) -> str:
        s_line, s_col, e_line, e_col = _normalize_selection(self._sel_start, self._sel_end)
        if s_line is None:
            return ""
        result = []
        h = self.size.height
        for y in range(s_line, min(e_line + 1, h)):
            strip = Static.render_line(self, y)
            cl = strip.cell_length
            if y == s_line == e_line:
                text = strip.crop(s_col, min(e_col, cl)).text
            elif y == s_line:
                text = strip.crop(s_col, cl).text
            elif y == e_line:
                text = strip.crop(0, min(e_col, cl)).text
            else:
                text = strip.text
            result.append(text.rstrip())
        return "\n".join(result)


# ============================================================================
# SelectableRichLog
# ============================================================================

class SelectableRichLog(RichLog):
    """支持鼠标拖拽选择文本的 RichLog

    - MouseDown 开始选区
    - MouseMove（拖拽）更新选区，反色高亮
    - MouseUp 提取文本并自动复制到剪贴板（选中即复制）
    - 点击或按键自动清除选区
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._sel_start: Optional[tuple] = None
        self._sel_end: Optional[tuple] = None
        self._selecting: bool = False
        self._has_selection: bool = False
        # 边缘自动滚动
        self._scroll_timer = None
        self._scroll_direction: int = 0
        self._last_mouse_x: int = 0

    def on_scroll(self, event) -> None:
        """滚动事件 - 更新 App 的底部状态（延迟调用确保 scroll_offset 已更新）"""
        self.call_later(self._update_bottom_status)

    def _update_bottom_status(self) -> None:
        """检查是否在底部（延迟调用）"""
        try:
            app = self.app
            if hasattr(app, '_output_at_bottom'):
                scroll_x, scroll_y = self.scroll_offset
                virtual_height = self.virtual_size.height
                visible_height = self.size.height if self.size else 0
                app._output_at_bottom = scroll_y + visible_height >= virtual_height - 1
        except Exception:
            pass

    # ========== 鼠标事件 ==========

    def on_mouse_down(self, event) -> None:
        if event.button != 1:
            return
        scroll_x, scroll_y = self.scroll_offset
        self._sel_start = (scroll_y + event.y, scroll_x + event.x)
        self._sel_end = self._sel_start
        self._selecting = True
        self._has_selection = False
        self.capture_mouse()
        event.stop()
        self.refresh()

    def on_mouse_move(self, event) -> None:
        if not self._selecting:
            return
        scroll_x, scroll_y = self.scroll_offset
        line = max(0, scroll_y + event.y)
        if len(self.lines) > 0:
            line = min(line, len(self.lines) - 1)
        col = max(0, scroll_x + event.x)
        self._sel_end = (line, col)
        self._has_selection = True
        self._last_mouse_x = event.x
        event.stop()
        self.refresh()

        # 边缘自动滚动检测
        try:
            h = self.size.height
            if h > 0:
                if event.y <= 0 and scroll_y > 0:
                    self._start_auto_scroll(-1)
                elif event.y >= h - 1 and scroll_y + h < len(self.lines):
                    self._start_auto_scroll(1)
                else:
                    self._stop_auto_scroll()
        except Exception:
            pass

    def on_mouse_up(self, event) -> None:
        if not self._selecting:
            return
        self._selecting = False
        self._stop_auto_scroll()
        self.release_mouse()
        if self._has_selection and self._sel_start and self._sel_end:
            text = self._extract_text()
            if text.strip():
                _copy_to_clipboard(text)
        self._sel_start = None
        self._sel_end = None
        self._has_selection = False
        self.refresh()
        event.stop()

    # ========== 渲染高亮 ==========

    def render_line(self, y: int) -> Strip:
        strip = super().render_line(y)
        if not self._has_selection and not self._selecting:
            return strip
        s_line, s_col, e_line, e_col = _normalize_selection(self._sel_start, self._sel_end)
        if s_line is None:
            return strip
        scroll_x, scroll_y = self.scroll_offset
        return _highlight_strip(strip, scroll_y + y, s_line, s_col, e_line, e_col, scroll_x)

    # ========== 文本提取 ==========

    def _extract_text(self) -> str:
        s_line, s_col, e_line, e_col = _normalize_selection(self._sel_start, self._sel_end)
        if s_line is None:
            return ""
        result = []
        for i in range(s_line, min(e_line + 1, len(self.lines))):
            line = self.lines[i]
            cl = line.cell_length
            if i == s_line == e_line:
                text = line.crop(s_col, min(e_col, cl)).text
            elif i == s_line:
                text = line.crop(s_col, cl).text
            elif i == e_line:
                text = line.crop(0, min(e_col, cl)).text
            else:
                text = line.text
            result.append(text.rstrip())
        return "\n".join(result)

    # ========== 边缘自动滚动 ==========

    def _start_auto_scroll(self, direction: int) -> None:
        if self._scroll_direction == direction and self._scroll_timer is not None:
            return
        self._stop_auto_scroll()
        self._scroll_direction = direction
        self._scroll_timer = self.set_interval(0.05, self._auto_scroll_tick)

    def _stop_auto_scroll(self) -> None:
        if self._scroll_timer is not None:
            self._scroll_timer.stop()
            self._scroll_timer = None
        self._scroll_direction = 0

    def _auto_scroll_tick(self) -> None:
        if not self._selecting:
            self._stop_auto_scroll()
            return
        try:
            if self._scroll_direction == -1:
                self.scroll_up(animate=False)
            elif self._scroll_direction == 1:
                self.scroll_down(animate=False)
            scroll_x, scroll_y = self.scroll_offset
            if self._scroll_direction == -1:
                line = scroll_y
            else:
                line = scroll_y + self.size.height - 1
            if len(self.lines) > 0:
                line = max(0, min(line, len(self.lines) - 1))
            col = max(0, scroll_x + self._last_mouse_x)
            self._sel_end = (line, col)
            self.refresh()
        except Exception:
            self._stop_auto_scroll()
