"""Текстовое поле ввода на базе Button."""

from __future__ import annotations

from typing import Callable, Optional, Tuple, TYPE_CHECKING

import pygame

from .button import Button
from .input_validation import (
    InputType,
    can_add_char,
    filter_chars_for_paste,
)

if TYPE_CHECKING:
    from .sprite import SpriteSceneInput


class TextInput(Button):
    """Поле ввода текста на базе Button без анимаций.

    Поддерживает типы: text (любой печатный текст), int (целые), float (дробные).
    Для int/float некорректные символы не вводятся и отфильтровываются при вставке.
    Enter — подтверждение, Escape — отмена. Ctrl+V — вставка, Ctrl+C — копирование содержимого поля.
    """

    def __init__(
        self,
        size: Tuple[int, int] = (200, 36),
        pos: Tuple[int, int] = (100, 100),
        placeholder: str = "",
        value: str = "",
        max_length: int = 128,
        input_type: InputType = "text",
        min_val: Optional[float] = None,
        max_val: Optional[float] = None,
        on_change: Optional[Callable[[str], None]] = None,
        on_submit: Optional[Callable[[str], None]] = None,
        text_color: Tuple[int, int, int] = (200, 200, 200),
        bg_color: Tuple[int, int, int] = (45, 45, 52),
        active_bg_color: Tuple[int, int, int] = (55, 55, 62),
        font_size: int = 18,
        sorting_order: int = 1000,
        scene: "SpriteSceneInput" = None,
    ):
        super().__init__(
            sprite="",
            size=size,
            pos=pos,
            text="",
            text_size=font_size,
            text_color=text_color,
            base_color=bg_color,
            hover_color=bg_color,
            press_color=bg_color,
            animated=False,
            use_scale_fx=False,
            use_color_fx=True,
            on_click=self._on_click_activate,
            sorting_order=sorting_order,
            scene=scene,
        )
        self.placeholder = placeholder
        self.value = value
        self.max_length = max(1, int(max_length))
        self.input_type: InputType = input_type
        self.min_val = min_val
        self.max_val = max_val
        self.on_change = on_change
        self.on_submit = on_submit
        self.is_active = False
        self._cursor_timer = 0.0
        self._show_cursor = True
        self._base_bg = bg_color
        self._active_bg = active_bg_color
        self._backspace_held_since: Optional[float] = None
        self._backspace_repeat_acc = 0.0
        self._backspace_cleared_all = False
        self._apply_text()

    def _on_click_activate(self) -> None:
        self.is_active = True
        self._cursor_timer = 0.0
        self._show_cursor = True
        self._apply_text()

    def _apply_text(self) -> None:
        shown = self.value if self.value else self.placeholder
        if self.is_active and self._show_cursor:
            shown = f"{self.value}|"
        self.text_sprite.set_text(shown)
        color = self._active_bg if self.is_active else self._base_bg
        self.set_all_colors(color, color, color)
        self.current_color = color

    def activate(self) -> None:
        self.is_active = True
        self._cursor_timer = 0.0
        self._show_cursor = True
        self._apply_text()

    def deactivate(self) -> None:
        self.is_active = False
        self._apply_text()

    def _paste_from_clipboard(self) -> None:
        try:
            if not pygame.scrap.get_init():
                pygame.scrap.init()
        except Exception:
            return
        try:
            data = pygame.scrap.get(pygame.SCRAP_TEXT)
            if not data:
                return
            text = data.decode("utf-8", errors="replace")
            paste = filter_chars_for_paste(self.input_type, text)
            if not paste:
                return
            remain = self.max_length - len(self.value)
            if remain <= 0:
                return
            self.value = (self.value + paste)[: self.max_length]
            self._apply_text()
            if self.on_change:
                self.on_change(self.value)
        except Exception:
            pass

    def _copy_to_clipboard(self) -> None:
        if not self.value:
            return
        try:
            if not pygame.scrap.get_init():
                pygame.scrap.init()
            pygame.scrap.put(pygame.SCRAP_TEXT, self.value.encode("utf-8"))
        except Exception:
            pass

    def set_value(self, value: str) -> None:
        self.value = value[: self.max_length]
        self._apply_text()
        if self.on_change:
            self.on_change(self.value)

    def handle_event(self, event: pygame.event.Event) -> bool:
        if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
            if self.rect.collidepoint(event.pos):
                self.activate()
                return True
            if self.is_active:
                self.deactivate()
            return False
        if not self.is_active:
            return False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RETURN:
                self.deactivate()
                if self.on_submit:
                    self.on_submit(self.value)
                return True
            if event.key == pygame.K_ESCAPE:
                self.deactivate()
                return True
            if event.key == pygame.K_BACKSPACE:
                self.value = self.value[:-1]
                self._apply_text()
                if self.on_change:
                    self.on_change(self.value)
                self._backspace_held_since = 0.0
                self._backspace_repeat_acc = 0.0
                self._backspace_cleared_all = False
                return True
            mod = event.mod & (pygame.KMOD_CTRL | pygame.KMOD_META)
            if mod:
                if event.key == pygame.K_v:
                    self._paste_from_clipboard()
                    return True
                if event.key == pygame.K_c:
                    self._copy_to_clipboard()
                    return True
            return False
        if event.type == pygame.TEXTINPUT:
            text = event.text or ""
            buf = self.value
            for c in text:
                if len(buf) < self.max_length and can_add_char(self.input_type, buf, c):
                    buf += c
            if buf != self.value:
                self.value = buf
                self._apply_text()
                if self.on_change:
                    self.on_change(self.value)
            return True
        return False

    def update(self, screen: pygame.Surface = None):
        import spritePro as s

        for ev in getattr(s, "pygame_events", []):
            self.handle_event(ev)
        if self.is_active:
            self._cursor_timer += s.dt
            if self._cursor_timer >= 0.5:
                self._cursor_timer = 0.0
                self._show_cursor = not self._show_cursor
                self._apply_text()
            keys = pygame.key.get_pressed()
            if keys[pygame.K_BACKSPACE]:
                if self._backspace_held_since is None:
                    self._backspace_held_since = 0.0
                self._backspace_held_since += s.dt
                if self._backspace_held_since >= 3.0 and not self._backspace_cleared_all:
                    self.value = ""
                    self._apply_text()
                    if self.on_change:
                        self.on_change(self.value)
                    self._backspace_cleared_all = True
                elif self._backspace_held_since >= 1.0:
                    self._backspace_repeat_acc += s.dt
                    while self._backspace_repeat_acc >= 0.05 and self.value:
                        self.value = self.value[:-1]
                        self._backspace_repeat_acc -= 0.05
                        self._apply_text()
                        if self.on_change:
                            self.on_change(self.value)
            else:
                self._backspace_held_since = None
                self._backspace_repeat_acc = 0.0
                self._backspace_cleared_all = False
        super().update(screen)
