"""终端 UI 模块：使用 rich 库实现彩色输出、spinner、diff 预览、交互菜单。"""

from __future__ import annotations

import queue
import time
from dataclasses import dataclass
from threading import Thread
from typing import Generator, Optional, Union

import click
from rich.console import Console
from rich.live import Live
from rich.panel import Panel
from rich.text import Text
from acm.git import DiffResult, FileChange
from acm.splitter import CommitGroup

console = Console()
err_console = Console(stderr=True)


# ---------------------------------------------------------------------------
# 错误与信息输出
# ---------------------------------------------------------------------------

def print_error(message: str) -> None:
    err_console.print(f"[bold red]x[/bold red] {message}")


def print_warning(message: str) -> None:
    console.print(f"[bold yellow]![/bold yellow] {message}")


def print_success(message: str) -> None:
    console.print(f"[bold green]v[/bold green] {message}")


def print_info(message: str) -> None:
    console.print(f"[bold blue]>[/bold blue] {message}")


def print_verbose(message: str) -> None:
    console.print(f"[dim]{message}[/dim]")


# ---------------------------------------------------------------------------
# Diff 预览
# ---------------------------------------------------------------------------

def _status_color(status: str) -> str:
    colors = {"A": "green", "M": "yellow", "D": "red", "R": "cyan"}
    return colors.get(status, "white")


def _status_label(status: str) -> str:
    labels = {"A": "新增", "M": "修改", "D": "删除", "R": "重命名"}
    return labels.get(status, status)


def show_diff_summary(diff_result: DiffResult) -> None:
    """展示变更摘要（文件树形式）。"""
    total_ins = sum(f.insertions for f in diff_result.files)
    total_del = sum(f.deletions for f in diff_result.files)

    console.print()
    print_info(f"分析暂存区变更...")
    console.print()

    header = f"  修改了 {len(diff_result.files)} 个文件 ([green]+{total_ins}[/green], [red]-{total_del}[/red])"
    console.print(header)

    for i, f in enumerate(diff_result.files):
        color = _status_color(f.status)
        is_last = i == len(diff_result.files) - 1
        prefix = "  └── " if is_last else "  ├── "
        console.print(f"{prefix}[{color}]{f.path}[/{color}]    ([green]+{f.insertions}[/green], [red]-{f.deletions}[/red])")

    console.print()


# ---------------------------------------------------------------------------
# Commit Message 展示
# ---------------------------------------------------------------------------

def show_commit_message(message: str) -> None:
    """以面板形式展示 commit message。"""
    panel = Panel(
        Text(message),
        title="[bold]Commit Message[/bold]",
        border_style="cyan",
        padding=(1, 2),
    )
    console.print(panel)


# ---------------------------------------------------------------------------
# 交互菜单
# ---------------------------------------------------------------------------

def prompt_action() -> str:
    """展示操作菜单并获取用户选择。

    Returns:
        用户选择: 'y' | 'e' | 'r' | 'q'
    """
    console.print(
        "[bold][green]确认提交(y)[/green]  "
        "[yellow]编辑(e)[/yellow]  "
        "[blue]反馈重新生成(r)[/blue]  "
        "[red]放弃(q)[/red][/bold]"
    )

    while True:
        choice = click.prompt("", prompt_suffix="> ", type=str).strip().lower()
        if choice in ("y", "e", "r", "q"):
            return choice
        console.print("[dim]请输入 y/e/r/q[/dim]")


def prompt_feedback() -> str:
    """获取用户的反馈意见。"""
    console.print("[bold blue]>[/bold blue] 请输入你的反馈（告诉AI如何改进）:")
    return click.prompt("", prompt_suffix="> ", type=str).strip()


def prompt_edit(message: str) -> str:
    """让用户编辑 commit message。使用 click.edit 打开编辑器。"""
    edited = click.edit(message)
    if edited is not None:
        return edited.strip()
    return message


def prompt_confirm(message: str) -> bool:
    """简单的确认提示。"""
    return click.confirm(message, default=True)


# ---------------------------------------------------------------------------
# 智能拆分展示
# ---------------------------------------------------------------------------

def show_split_suggestion(groups: list[CommitGroup]) -> None:
    """展示拆分提交建议。"""
    console.print()
    print_info(f"建议拆分为 {len(groups)} 个原子提交：")
    console.print()

    for i, group in enumerate(groups, 1):
        console.print(f"  [bold cyan]提交 {i}[/bold cyan]: {group.message}")
        for f in group.files:
            console.print(f"    - {f}")
        console.print()


# ---------------------------------------------------------------------------
# Spinner 上下文管理
# ---------------------------------------------------------------------------

def spinner(message: str = "生成 commit message..."):
    """返回 rich 的 spinner 上下文管理器。"""
    return console.status(f"[bold blue]>[/bold blue] {message}", spinner="dots")


# ---------------------------------------------------------------------------
# 大提交警告
# ---------------------------------------------------------------------------

def warn_large_commit(file_count: int) -> bool:
    """对大提交发出警告，询问是否继续。返回 True 表示继续。"""
    print_warning(
        f"暂存区包含 {file_count} 个文件，变更较多，建议拆分提交以保持每次提交的原子性。"
    )
    return prompt_confirm("是否继续？")


# ---------------------------------------------------------------------------
# 流式输出
# ---------------------------------------------------------------------------

@dataclass
class _Done:
    """后台线程完成哨兵，携带 generator 的 return 值。"""
    value: str


@dataclass
class _Error:
    """后台线程异常哨兵，携带原始异常。"""
    exc: BaseException


def _consume_stream(
    chunks: Generator[str, None, str],
    q: queue.Queue,
) -> None:
    """在后台线程中消费 generator，将 chunk 放入队列。"""
    try:
        while True:
            chunk = next(chunks)
            q.put(chunk)
    except StopIteration as e:
        q.put(_Done(value=e.value or ""))
    except Exception as e:
        q.put(_Error(exc=e))


def stream_commit_message(chunks: Generator[str, None, str]) -> str:
    """流式展示 LLM 生成的 commit message，后台线程收集数据，主线程平滑渲染。

    通过将网络 I/O（generator 消费）放到后台线程，主线程专注于以固定节奏
    刷新 UI，避免因等待 LLM 响应而导致的画面冻结。

    Args:
        chunks: 生成器，逐块 yield 文本片段，最终 return 清理后的完整 message。

    Returns:
        最终清理后的 commit message。
    """
    chunk_queue: queue.Queue = queue.Queue()
    thread = Thread(target=_consume_stream, args=(chunks, chunk_queue), daemon=True)
    thread.start()

    parts: list[str] = []
    final_message = ""
    error: Optional[BaseException] = None
    done = False

    with Live(
        _make_stream_panel(""),
        console=console,
        refresh_per_second=30,
        transient=True,
    ) as live:
        while not done:
            got_new = False
            # 批量 drain：非阻塞取出所有已到达的 chunk
            while True:
                try:
                    item: Union[str, _Done, _Error] = chunk_queue.get_nowait()
                except queue.Empty:
                    break
                if isinstance(item, _Done):
                    final_message = item.value
                    done = True
                    break
                elif isinstance(item, _Error):
                    error = item.exc
                    done = True
                    break
                else:
                    parts.append(item)
                    got_new = True

            if got_new:
                live.update(_make_stream_panel("".join(parts)))

            if not done:
                time.sleep(1 / 60)

    thread.join(timeout=5)

    if error is not None:
        raise error

    return final_message if final_message else "".join(parts).strip()


def _make_stream_panel(text: str) -> Panel:
    """构建流式输出用的面板。"""
    display = text if text else " "
    return Panel(
        Text(display),
        title="[bold]Commit Message[/bold]",
        subtitle="[dim]生成中...[/dim]" if text else None,
        border_style="cyan",
        padding=(1, 2),
    )
