from __future__ import annotations

from textual import events
from textual.app import ComposeResult
from textual.message import Message
from textual.widgets import TextArea

def _kb_matches(context: str, action: str, event) -> bool:
    try:
        from ui.keybindings import get_keybindings_manager, DEFAULT_BINDINGS, normalize_key
        manager = get_keybindings_manager()
        if manager:
            return manager.matches(context, action, event)

        default_key = DEFAULT_BINDINGS.get(context, {}).get(action, "")
        if not default_key:
            return False
        return normalize_key(default_key) == normalize_key(event.key)
    except Exception:
        return False

class Composer(TextArea):

    class Submitted(Message):
        def __init__(self, text: str):
            self.text = text
            super().__init__()

    class SlashCommandRequested(Message):
        def __init__(self, filter_text: str):
            self.filter_text = filter_text
            super().__init__()

    class SlashCommandFilterChanged(Message):
        def __init__(self, filter_text: str):
            self.filter_text = filter_text
            super().__init__()

    class SlashCommandDismissed(Message):
        pass

    class SlashCommandNavigate(Message):
        def __init__(self, direction: str):
            self.direction = direction
            super().__init__()

    def __init__(self, *a, **k):
        super().__init__(*a, **k)
        self.show_line_numbers = False
        self.wrap = True
        self.styles.height = 1
        self.styles.max_height = 10
        self._command_mode = False
        self._last_text = ""

        self._message_history: list[str] = []
        self._history_index: int = -1
        self._history_temp: str = ""
        self._max_history: int = 50

    def _on_key(self, event: events.Key) -> None:
        text = self.text or ""

        if _kb_matches("composer", "select_all", event):
            event.prevent_default()
            event.stop()
            end_loc = self.document.end
            self.selection = ((0, 0), end_loc)
            return

        if _kb_matches("composer", "copy", event):
            event.prevent_default()
            event.stop()
            self._copy_to_clipboard()
            return

        if _kb_matches("composer", "paste", event):
            event.prevent_default()
            event.stop()
            self._paste_from_clipboard()
            return

        if _kb_matches("composer", "cut", event):
            event.prevent_default()
            event.stop()
            self._cut_to_clipboard()
            return

        if _kb_matches("composer", "select_left", event):
            event.prevent_default()
            event.stop()
            self._extend_selection_left()
            return

        if _kb_matches("composer", "select_right", event):
            event.prevent_default()
            event.stop()
            self._extend_selection_right()
            return

        if _kb_matches("composer", "select_word_left", event):
            event.prevent_default()
            event.stop()
            self._extend_selection_word_left()
            return

        if _kb_matches("composer", "select_word_right", event):
            event.prevent_default()
            event.stop()
            self._extend_selection_word_right()
            return

        if _kb_matches("composer", "move_word_left", event):
            event.prevent_default()
            event.stop()
            self._move_word_left()
            return

        if _kb_matches("composer", "move_word_right", event):
            event.prevent_default()
            event.stop()
            self._move_word_right()
            return

        if _kb_matches("composer", "history_prev", event):
            event.prevent_default()
            event.stop()
            self._history_prev()
            return

        if _kb_matches("composer", "history_next", event):
            event.prevent_default()
            event.stop()
            self._history_next()
            return

        if self._command_mode:
            if event.key == "up":
                event.prevent_default()
                event.stop()
                self.post_message(self.SlashCommandNavigate("up"))
                return
            elif event.key == "down":
                event.prevent_default()
                event.stop()
                self.post_message(self.SlashCommandNavigate("down"))
                return
            elif event.key == "tab":
                event.prevent_default()
                event.stop()
                self.post_message(self.SlashCommandNavigate("tab"))
                return
            elif event.key == "escape":
                event.prevent_default()
                event.stop()
                self._command_mode = False
                self.post_message(self.SlashCommandDismissed())
                return
            elif event.key == "enter":
                if text.startswith("/") and " " not in text:
                    event.prevent_default()
                    event.stop()
                    self.post_message(self.SlashCommandNavigate("confirm"))
                    return

        if _kb_matches("composer", "send", event):
            event.prevent_default()
            event.stop()
            self._command_mode = False
            self.post_message(self.SlashCommandDismissed())
            self._do_submit()
        elif _kb_matches("composer", "newline", event):
            event.prevent_default()
            event.stop()
            self.insert("\n")
        else:
            super()._on_key(event)

    def _do_submit(self) -> None:
        val = (self.text or "").strip()
        if not val:
            return

        if not self._message_history or self._message_history[-1] != val:
            self._message_history.append(val)
            if len(self._message_history) > self._max_history:
                self._message_history.pop(0)
        self._history_index = -1
        self._history_temp = ""

        self.post_message(self.Submitted(val))
        self.clear()
        self.styles.height = 1
        self._command_mode = False

    def _history_prev(self) -> None:
        if not self._message_history:
            return

        if self._history_index == -1:

            self._history_temp = self.text or ""
            self._history_index = len(self._message_history) - 1
        elif self._history_index > 0:
            self._history_index -= 1
        else:
            return

        self.text = self._message_history[self._history_index]
        self.move_cursor((0, len(self.text)))

    def _history_next(self) -> None:
        if self._history_index == -1:
            return

        if self._history_index < len(self._message_history) - 1:
            self._history_index += 1
            self.text = self._message_history[self._history_index]
        else:

            self._history_index = -1
            self.text = self._history_temp

        self.move_cursor((0, len(self.text)))

    def on_text_area_changed(self, event: TextArea.Changed) -> None:
        lines = max(1, self.document.line_count)
        self.styles.height = min(lines, 10)

        text = self.text or ""

        if text.startswith("/"):

            parts = text.split(" ", 1)
            cmd_part = parts[0]

            if not self._command_mode:

                self._command_mode = True
                self.post_message(self.SlashCommandRequested(cmd_part))
            else:

                if len(parts) == 1:
                    self.post_message(self.SlashCommandFilterChanged(cmd_part))
                else:

                    pass
        else:

            if self._command_mode:
                self._command_mode = False
                self.post_message(self.SlashCommandDismissed())

        self._last_text = text

    def insert_command(self, command_name: str, add_space: bool = True) -> None:
        suffix = " " if add_space else ""
        self.text = f"/{command_name}{suffix}"
        self.move_cursor((0, len(self.text)))

    def set_command_mode(self, enabled: bool) -> None:
        self._command_mode = enabled
        if not enabled:
            self.post_message(self.SlashCommandDismissed())

    def _copy_to_clipboard(self) -> None:
        selected = self.selected_text
        if selected:
            try:
                import pyperclip
                pyperclip.copy(selected)
                if hasattr(self.app, 'notify') and hasattr(self.app, 't'):
                    self.app.notify(self.app.t("copied"), timeout=1)
            except ImportError:
                pass

    def _cut_to_clipboard(self) -> None:
        selected = self.selected_text
        if selected:
            try:
                import pyperclip
                pyperclip.copy(selected)

                self.delete(*self.selection)
                if hasattr(self.app, 'notify') and hasattr(self.app, 't'):
                    self.app.notify(self.app.t("cut_to_clipboard"), timeout=1)
            except ImportError:
                pass

    def _paste_from_clipboard(self) -> None:
        try:
            import pyperclip
            text = pyperclip.paste()
            if text:
                self.insert(text)
        except ImportError:
            pass

    def _extend_selection_left(self) -> None:
        start, end = self.selection
        cursor = self.cursor_location

        if start == end:
            start = cursor
            end = cursor

        row, col = start
        if col > 0:
            new_start = (row, col - 1)
        elif row > 0:

            prev_line = self.document.get_line(row - 1)
            new_start = (row - 1, len(prev_line))
        else:
            return

        self.selection = (new_start, end)

    def _extend_selection_right(self) -> None:
        start, end = self.selection
        cursor = self.cursor_location

        if start == end:
            start = cursor
            end = cursor

        row, col = end
        line = self.document.get_line(row)
        if col < len(line):
            new_end = (row, col + 1)
        elif row < self.document.line_count - 1:

            new_end = (row + 1, 0)
        else:
            return

        self.selection = (start, new_end)

    def _extend_selection_word_left(self) -> None:
        start, end = self.selection
        cursor = self.cursor_location

        if start == end:
            start = cursor
            end = cursor

        row, col = start
        line = self.document.get_line(row)

        while col > 0 and (col > len(line) or line[col - 1].isspace()):
            col -= 1

        while col > 0 and col <= len(line) and not line[col - 1].isspace():
            col -= 1

        self.selection = ((row, col), end)

    def _extend_selection_word_right(self) -> None:
        start, end = self.selection
        cursor = self.cursor_location

        if start == end:
            start = cursor
            end = cursor

        row, col = end
        line = self.document.get_line(row)

        while col < len(line) and not line[col].isspace():
            col += 1

        while col < len(line) and line[col].isspace():
            col += 1

        self.selection = (start, (row, col))

    def _move_word_left(self) -> None:
        row, col = self.cursor_location
        line = self.document.get_line(row)

        while col > 0 and line[col - 1].isspace():
            col -= 1

        while col > 0 and not line[col - 1].isspace():
            col -= 1

        self.move_cursor((row, col))

    def _move_word_right(self) -> None:
        row, col = self.cursor_location
        line = self.document.get_line(row)

        while col < len(line) and not line[col].isspace():
            col += 1

        while col < len(line) and line[col].isspace():
            col += 1

        self.move_cursor((row, col))
