"""Custom widgets for Fliiq TUI — Claude Code-style display."""

import ast
from pathlib import Path

from rich.style import Style
from textual import events
from textual._text_area_theme import TextAreaTheme
from textual.app import ComposeResult
from textual.containers import Horizontal, ScrollableContainer
from textual.message import Message as TextualMessage
from textual.reactive import reactive
from textual.widgets import Collapsible, Markdown, OptionList, Static, TextArea

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _shorten_path(path: str, max_len: int = 50) -> str:
    """Shorten a file path for display, keeping the filename and some context."""
    if len(path) <= max_len:
        return path
    p = Path(path)
    parts = p.parts
    if len(parts) <= 2:
        return path
    return f".../{'/'.join(parts[-2:])}"


def _count_lines(text: str) -> int:
    """Count lines in a string."""
    if not text:
        return 0
    return text.count("\n") + (1 if not text.endswith("\n") else 0)


def _parse_tool_result(result: str) -> dict | str:
    """Try to parse a tool result as a Python dict."""
    try:
        parsed = ast.literal_eval(result)
        if isinstance(parsed, dict):
            return parsed
    except (ValueError, SyntaxError):
        pass
    return result


def _format_tool_summary(name: str, tool_input: dict, result: str) -> str:
    """Build a one-line summary for a tool call (collapsed view)."""
    parsed = _parse_tool_result(result)

    # File edits — show path + added/removed counts
    if name == "edit_file":
        path = _shorten_path(tool_input.get("path", ""))
        old = tool_input.get("old_text", "")
        new = tool_input.get("new_text", "")
        added = _count_lines(new)
        removed = _count_lines(old)
        return f"Update({path}) \u00b7 +{added} -{removed}"

    # File reads — show path
    if name == "read_file":
        path = _shorten_path(tool_input.get("path", ""))
        if isinstance(parsed, dict) and "content" in parsed:
            lines = _count_lines(str(parsed["content"]))
            return f"Read({path}) \u00b7 {lines} lines"
        return f"Read({path})"

    # File writes — show path + line count
    if name == "write_file":
        path = _shorten_path(tool_input.get("path", ""))
        content = tool_input.get("content", "")
        lines = _count_lines(content)
        return f"Write({path}) \u00b7 {lines} lines"

    # Shell commands — show command + exit code
    if name in ("shell", "shell_exec", "subprocess_exec"):
        cmd = tool_input.get("command", "")
        if len(cmd) > 60:
            cmd = cmd[:57] + "..."
        if isinstance(parsed, dict) and "exit_code" in parsed:
            code = parsed["exit_code"]
            return f"Bash({cmd}) \u00b7 exit {code}"
        return f"Bash({cmd})"

    # Grep/Find — show pattern or path
    if name == "grep":
        pattern = tool_input.get("pattern", "")
        return f"Grep({pattern[:40]})"

    if name == "find":
        pattern = tool_input.get("pattern", tool_input.get("name", ""))
        return f"Find({pattern[:40]})"

    if name == "list_directory":
        path = _shorten_path(tool_input.get("path", "."))
        return f"ListDir({path})"

    # Web tools
    if name == "web_search":
        query = tool_input.get("query", "")
        return f"WebSearch({query[:40]})"

    if name == "web_fetch":
        url = tool_input.get("url", "")
        if len(url) > 50:
            url = url[:47] + "..."
        return f"WebFetch({url})"

    # Default — tool_name(first_param_value)
    first_val = ""
    for v in tool_input.values():
        first_val = str(v)[:40]
        break
    if first_val:
        return f"{name}({first_val})"
    return name


def _format_tool_detail(name: str, tool_input: dict, result: str) -> str:
    """Build expanded detail text for a tool call (inside collapsible)."""
    parsed = _parse_tool_result(result)

    # File edits — show diff
    if name == "edit_file":
        old = tool_input.get("old_text", "")
        new = tool_input.get("new_text", "")
        lines = []
        for line in old.splitlines():
            lines.append(f"  - {line}")
        for line in new.splitlines():
            lines.append(f"  + {line}")
        return "\n".join(lines) if lines else str(result)[:500]

    # Shell — show stdout/stderr
    if name in ("shell", "shell_exec", "subprocess_exec") and isinstance(parsed, dict):
        parts = []
        if parsed.get("stdout", "").strip():
            parts.append(parsed["stdout"].strip()[:500])
        if parsed.get("stderr", "").strip():
            parts.append(f"stderr: {parsed['stderr'].strip()[:200]}")
        return "\n".join(parts) if parts else "(no output)"

    # File reads — show first few lines
    if name == "read_file" and isinstance(parsed, dict) and "content" in parsed:
        content = str(parsed["content"])
        lines = content.splitlines()[:20]
        if len(content.splitlines()) > 20:
            lines.append(f"  ... ({len(content.splitlines()) - 20} more lines)")
        return "\n".join(lines)

    # Default — truncated result
    return str(result)[:500]


# ---------------------------------------------------------------------------
# Welcome Screen
# ---------------------------------------------------------------------------

class WelcomeScreen(Static):
    """Split-panel welcome screen shown on startup."""

    DEFAULT_CSS = """
    WelcomeScreen {
        width: 100%;
        height: auto;
        margin: 1 2;
        padding: 1 2;
        border: round $primary;
    }
    """

    def __init__(self, version: str, cwd: str, model: str = "") -> None:
        lines = []
        lines.append(' \\(")/')
        lines.append(" -( )-")
        lines.append(" /(*)\\")
        lines.append("")
        lines.append(f"[bold]Fliiq v{version}[/bold]")
        lines.append("")
        if model:
            lines.append(f"[dim]{model}[/dim]")
        lines.append(f"[dim]{cwd}[/dim]")
        lines.append("")
        lines.append("[dim]\u2500\u2500\u2500[/dim]")
        lines.append("")
        lines.append("[bold]Tips[/bold]")
        lines.append("  /help \u2014 commands")
        lines.append("  /mode \u2014 cycle modes")
        lines.append("  shift+tab \u2014 quick mode switch")
        lines.append("  esc \u2014 cancel running agent")
        lines.append("  /copy \u2014 copy last response to clipboard")
        super().__init__("\n".join(lines))


# ---------------------------------------------------------------------------
# Messages
# ---------------------------------------------------------------------------

class UserMessage(Static):
    """User message with prompt prefix."""

    DEFAULT_CSS = """
    UserMessage {
        padding: 0 1;
        margin: 0 0 1 0;
        width: 100%;
        color: $primary;
    }
    """

    def __init__(self, content: str) -> None:
        # Escape brackets for Rich markup
        escaped = content.replace("[", "\\[")
        super().__init__(f"[bold]\u276f[/bold] {escaped}")


class AgentMessage(Static):
    """Agent message container with bullet prefix and markdown content."""

    DEFAULT_CSS = """
    AgentMessage {
        width: 100%;
        margin: 0 0 1 0;
        padding: 0 1;
    }

    AgentMessage .agent-prefix {
        width: auto;
        color: $text;
    }

    AgentMessage Markdown {
        margin: 0;
        padding: 0;
    }
    """

    def __init__(self, content: str) -> None:
        super().__init__()
        self._content = content

    def compose(self) -> ComposeResult:
        yield Static("\u23fa", classes="agent-prefix")
        yield Markdown(self._content)

    def update_content(self, content: str) -> None:
        """Update the markdown content (for streaming or updates)."""
        self._content = content
        try:
            md = self.query_one(Markdown)
            md.update(content)
        except Exception:
            pass


class CompletionMessage(Static):
    """Completion indicator with iteration count — lightweight, no heavy border."""

    DEFAULT_CSS = """
    CompletionMessage {
        padding: 0 1;
        margin: 0 0 1 0;
        width: 100%;
    }

    CompletionMessage .completion-prefix {
        color: $success;
    }

    CompletionMessage .completion-meta {
        color: $text-muted;
    }
    """

    def __init__(self, content: str, iterations: str = "") -> None:
        super().__init__()
        self._content = content
        self._iterations = iterations

    def compose(self) -> ComposeResult:
        yield Static("\u23fa", classes="completion-prefix")
        yield Markdown(self._content)
        if self._iterations:
            yield Static(f"[dim]{self._iterations}[/dim]", classes="completion-meta")


# ---------------------------------------------------------------------------
# Tool Call Display
# ---------------------------------------------------------------------------

class ToolCallWidget(Static):
    """Single tool call with context-aware summary."""

    DEFAULT_CSS = """
    ToolCallWidget {
        padding: 0 1;
        width: 100%;
        color: $text-muted;
    }
    """

    def __init__(self, name: str, tool_input: dict, result: str, connector: str = "\u251c\u2500") -> None:
        summary = _format_tool_summary(name, tool_input, result)
        detail = _format_tool_detail(name, tool_input, result)
        detail_lines = [line for line in detail.splitlines() if line.strip()] if detail else []

        lines = [f"  {connector} {summary}"]
        if detail_lines:
            lines.append(f"     \u23bf  {detail_lines[0][:120]}")
            for dl in detail_lines[1:3]:
                lines.append(f"        {dl[:120]}")
            remaining = len(detail_lines) - 3
            if remaining > 0:
                lines.append(f"        ... +{remaining} more lines")

        super().__init__("\n".join(lines))
        self._name = name
        self._tool_input = tool_input
        self._result = result


class ToolCallGroup(Collapsible):
    """Collapsible group of consecutive tool calls with tree connectors."""

    DEFAULT_CSS = """
    ToolCallGroup {
        padding: 0;
        margin: 0 0 1 0;
        width: 100%;
    }

    ToolCallGroup CollapsibleTitle {
        color: $text-muted;
        padding: 0 1;
    }
    """

    def __init__(self) -> None:
        super().__init__(title="\u23fa 1 tool use (ctrl+o to expand)", collapsed=True)
        self._count = 0
        self._tools: list[tuple[str, dict, str]] = []

    def add_tool_call(self, name: str, tool_input: dict, result: str) -> None:
        """Add a tool call and update tree connectors."""
        self._tools.append((name, tool_input, result))
        self._count = len(self._tools)

        # Rebuild all connectors — last item gets └─, others get ├─
        for child in list(self.query(ToolCallWidget)):
            child.remove()

        for i, (n, ti, r) in enumerate(self._tools):
            is_last = i == len(self._tools) - 1
            connector = "\u2514\u2500" if is_last else "\u251c\u2500"
            widget = ToolCallWidget(n, ti, r, connector=connector)
            self.mount(widget)

        # Update title
        uses = "use" if self._count == 1 else "uses"
        self.title = f"\u23fa {self._count} tool {uses} (ctrl+o to expand)"


# ---------------------------------------------------------------------------
# Plan Approval
# ---------------------------------------------------------------------------

class PlanApprovalMessage(Static):
    """Styled plan summary — signals action required."""

    DEFAULT_CSS = """
    PlanApprovalMessage {
        padding: 1 2;
        margin: 1 0;
        width: 100%;
        border: round $warning;
        color: $text;
    }
    """

    def __init__(self, summary: str) -> None:
        escaped = summary.replace("[", "\\[")
        super().__init__(f"Plan Summary:\n\n{escaped}")


class PlanApprovalSelector(OptionList):
    """Arrow-key option list for plan approval next steps."""

    DEFAULT_CSS = """
    PlanApprovalSelector {
        height: auto;
        max-height: 8;
        margin: 0 0 1 0;
        width: 100%;
        border: round $warning;
    }
    """

    def __init__(self, options: list[dict], recommended: int = 1) -> None:
        self.option_data = options
        labels = []
        for i, opt in enumerate(options, 1):
            rec = " (recommended)" if i == recommended else ""
            labels.append(f"{opt['label']}{rec} \u2014 {opt['description']}")
        labels.append("Type your own response")
        super().__init__(*labels)
        if 1 <= recommended <= len(options):
            self.highlighted = recommended - 1


# ---------------------------------------------------------------------------
# Input
# ---------------------------------------------------------------------------

class FliiqInput(TextArea):
    """Multi-line input: Enter submits, Shift+Enter adds newline."""

    class Submitted(TextualMessage):
        """Fired when user presses Enter to submit."""

        def __init__(self, value: str) -> None:
            super().__init__()
            self.value = value

    DEFAULT_CSS = """
    FliiqInput {
        height: auto;
        min-height: 1;
        max-height: 5;
        width: 1fr;
        border: none;
        background: $background;
    }

    FliiqInput:focus {
        border: none;
    }
    """

    def __init__(self) -> None:
        super().__init__(language=None, show_line_numbers=False)
        self.tab_behavior = "focus"
        self.register_theme(
            TextAreaTheme(
                name="fliiq-input",
                base_style=Style(color="#0C0F2C", bgcolor="#F0FAFB"),
            )
        )
        self.theme = "fliiq-input"

    def _on_key(self, event: events.Key) -> None:
        """Enter = submit, Shift+Enter = newline."""
        if event.key == "enter":
            event.prevent_default()
            event.stop()
            text = self.text.strip()
            if text:
                self.clear()
                self.post_message(self.Submitted(text))


# ---------------------------------------------------------------------------
# Status Bar / Mode Input
# ---------------------------------------------------------------------------

class StatusBar(Static):
    """Bottom status bar: full-width input on top, info row below."""

    _MODE_COLORS = {
        "autonomous": "#3AC9C2",
        "supervised": "#C4A8F2",
        "plan": "#FF6F61",
    }

    DEFAULT_CSS = """
    StatusBar {
        height: auto;
        min-height: 3;
        background: $surface;
        layout: vertical;
        padding: 0 1 1 1;
        border-top: solid $primary;
    }

    StatusBar .input-row {
        width: 100%;
        height: auto;
        border-bottom: solid $primary;
    }

    StatusBar .input-prompt {
        width: 2;
        height: 1;
        color: $primary;
    }

    StatusBar FliiqInput {
        width: 1fr;
    }

    StatusBar .info-bar {
        width: 100%;
        height: 1;
        color: $text-muted;
    }
    """

    mode = reactive("autonomous")
    agent_running = reactive(False)
    _thinking_text = reactive("")

    def __init__(self, model_name: str = "", **kwargs) -> None:
        super().__init__(**kwargs)
        self._model_name = model_name

    def compose(self) -> ComposeResult:
        with Horizontal(classes="input-row"):
            yield Static("\u276f ", classes="input-prompt")
            yield FliiqInput()
        yield Static(self._build_info_text(), id="info-bar", classes="info-bar")

    def show_thinking(self, elapsed: str = "0s") -> None:
        self._thinking_text = f"\u23fa Thinking... {elapsed}"
        self._refresh_info()

    def update_thinking(self, elapsed: str) -> None:
        self._thinking_text = f"\u23fa Thinking... {elapsed}"
        self._refresh_info()

    def hide_thinking(self) -> None:
        self._thinking_text = ""
        self._refresh_info()

    def _build_info_text(self) -> str:
        color = self._MODE_COLORS.get(self.mode, "white")
        parts = [f"[bold {color}]{self.mode}[/bold {color}] [dim](shift+tab)[/dim]"]
        if self._model_name:
            parts.append(f"[dim]{self._model_name}[/dim]")
        if self._thinking_text:
            parts.append(f"[bold #ffa62b]{self._thinking_text}[/bold #ffa62b]")
        elif self.agent_running:
            parts.append("[dim]esc: cancel[/dim]")
        return " \u00b7 ".join(parts)

    def _refresh_info(self) -> None:
        try:
            self.query_one("#info-bar", Static).update(self._build_info_text())
        except Exception:
            pass  # Not yet composed

    def watch_agent_running(self, _running: bool) -> None:
        self._refresh_info()

    def watch_mode(self, _new_mode: str) -> None:
        self._refresh_info()

    def focus_input(self) -> None:
        self.query_one(FliiqInput).focus()

    @property
    def input_widget(self) -> FliiqInput:
        return self.query_one(FliiqInput)

    def on_key(self, event: events.Key) -> None:
        if event.key == "shift+tab":
            self.app.action_cycle_mode()
            event.stop()


# ---------------------------------------------------------------------------
# Message Log
# ---------------------------------------------------------------------------

class MessageLog(ScrollableContainer):
    """Scrollable container for conversation messages."""

    DEFAULT_CSS = """
    MessageLog {
        height: 1fr;
        padding: 1;
        scrollbar-gutter: stable;
    }
    """

    def add_user_message(self, content: str) -> None:
        widget = UserMessage(content)
        self.mount(widget)
        self.scroll_end(animate=False)

    def add_agent_message(self, content: str) -> AgentMessage:
        widget = AgentMessage(content)
        self.mount(widget)
        self.scroll_end(animate=False)
        return widget

    def add_tool_call(self, name: str, tool_input: dict, result: str) -> None:
        """Add tool call to a collapsible group with context-aware display."""
        children = list(self.children)
        if children and isinstance(children[-1], ToolCallGroup):
            group = children[-1]
        else:
            group = ToolCallGroup()
            self.mount(group)
        group.add_tool_call(name, tool_input, result)
        self.scroll_end(animate=False)

    def add_completion(self, content: str, iterations: str = "") -> None:
        widget = CompletionMessage(content, iterations)
        self.mount(widget)
        self.scroll_end(animate=False)

    def add_plan_approval(self, summary: str) -> None:
        widget = PlanApprovalMessage(summary)
        self.mount(widget)
        self.scroll_end(animate=False)

    def add_system_message(self, content: str) -> None:
        """Add a system/info message (muted color)."""
        escaped = content.replace("[", "\\[")
        widget = Static(f"[dim]{escaped}[/dim]")
        widget.styles.padding = (0, 1)
        widget.styles.margin = (0, 0, 1, 0)
        self.mount(widget)
        self.scroll_end(animate=False)

    def clear_messages(self) -> None:
        for child in list(self.children):
            child.remove()
