"""Particle system utilities for SpritePro."""

from __future__ import annotations

import random
import time
from pathlib import Path
from dataclasses import dataclass, field, replace
from typing import Callable, Optional, Sequence, Tuple, Type, List, Union

import pygame
from pygame.math import Vector2

from .constants import Anchor

import spritePro
from .resources import resource_cache

VectorRange = Tuple[float, float]
Color = Tuple[int, int, int]


@dataclass
class ParticleConfig:
    """Конфигурация для `ParticleEmitter.emit`.

    Attributes:
        amount (int): Количество частиц для создания при каждом вызове emit. По умолчанию 30.
        size_range (Tuple[int, int]): Диапазон размеров (в пикселях), используется только для частиц-кругов по умолчанию. По умолчанию (4, 6).
        speed_range (VectorRange): Диапазон начальной скорости (величина вектора скорости). По умолчанию (120.0, 260.0).
        lifetime (Optional[float]): Фиксированное время жизни в секундах (переопределяет lifetime_range). По умолчанию None.
        lifetime_range (Optional[Tuple[float, float]]): Диапазон времени жизни в секундах. По умолчанию (0.6, 1.2).
        angle_range (VectorRange): Диапазон угла эмиссии в градусах (0 = вправо, 90 = вниз). По умолчанию (0.0, 360.0).
        colors (Sequence[Color]): Палитра цветов для частиц-кругов по умолчанию. По умолчанию [(255, 255, 255)].
        fade_speed (float): Скорость затухания альфа-канала в секунду. По умолчанию 220.0.
        gravity (Vector2): Вектор ускорения, применяемый каждый кадр. По умолчанию (0, 0).
        screen_space (bool): Если True, частицы игнорируют смещение камеры. По умолчанию False.
        custom_factory (Optional[Callable]): Хук для изменения частицы после создания. По умолчанию None.
        image (Union[Optional[pygame.Surface], Path]): Готовая поверхность для дублирования для каждой частицы. По умолчанию None.
        image_factory (Optional[Callable[[int], pygame.Surface]]): Фабрика для создания изображения для каждой частицы по индексу. По умолчанию None.
        particle_class (Optional[Type[Particle]]): Пользовательский подкласс Particle для создания. По умолчанию None.
        particle_template (Optional[Particle]): Готовый экземпляр Particle как шаблон для создания новых частиц. Копируются свойства шаблона, но позиция, скорость и время жизни берутся из конфига. По умолчанию None.
        factory (Optional[Callable]): Полное переопределение для создания частицы самостоятельно. По умолчанию None.
        sorting_order (Optional[int]): Порядок отрисовки (слой), большие значения рисуются спереди. По умолчанию None.
        image_scale_range (Optional[Tuple[float, float]]): Диапазон равномерного масштабирования для изображения. По умолчанию None.
        spawn_rect (Optional[pygame.Rect]): Прямоугольник области спавна (частицы создаются внутри этого прямоугольника). По умолчанию None.
        spawn_circle_radius (Optional[float]): Радиус круговой области спавна с центром в позиции эмиттера. По умолчанию None.
        align_rotation_to_velocity (bool): Поворачивать изображение по направлению движения. По умолчанию False.
        image_rotation_range (Optional[Tuple[float, float]]): Диапазон случайного начального поворота (градусы). По умолчанию None.
        angular_velocity_range (Optional[Tuple[float, float]]): Диапазон непрерывного вращения (град/сек). По умолчанию None.
        scale_velocity_range (Optional[Tuple[float, float]]): Диапазон скорости изменения масштаба (множитель в секунду). По умолчанию None.
    """

    amount: int = 30
    size_range: Tuple[int, int] = (4, 6)
    speed_range: VectorRange = (120.0, 260.0)
    lifetime: Optional[float] = None  # lifetime in seconds
    lifetime_range: Optional[Tuple[float, float]] = (0.6, 1.2)  # lifetime in seconds
    angle_range: VectorRange = (0.0, 360.0)
    colors: Sequence[Color] = field(default_factory=lambda: [(255, 255, 255)])
    fade_speed: float = 220.0
    gravity: Vector2 = field(default_factory=lambda: Vector2(0, 0))
    screen_space: bool = False
    # Optional hook to mutate particle after creation
    custom_factory: Optional[Callable[["Particle", int], None]] = None
    # Provide a ready image for all particles (copied internally)
    image: Union[Optional[pygame.Surface], Path] = None
    # Or provide a factory to build images per index
    image_factory: Optional[Callable[[int], pygame.Surface]] = None
    # Provide a custom Particle subclass to instantiate
    particle_class: Optional[Type["Particle"]] = None
    # Provide a ready Particle instance as template (properties are copied, but pos/velocity/lifetime are set from config)
    particle_template: Optional["Particle"] = None
    # Full factory override: create and return a Particle yourself
    factory: Optional[Callable[[Vector2, Vector2, int, "ParticleConfig", int], "Particle"]] = None
    # Sorting order for created particles (higher draws in front); None keeps default
    sorting_order: Optional[int] = None
    # Optional uniform scaling range for provided image/image_factory results
    image_scale_range: Optional[Tuple[float, float]] = None
    # Spawn region (relative to emit position). If both are None → spawn at a point
    spawn_rect: Optional[pygame.Rect] = None
    spawn_circle_radius: Optional[float] = None
    # Image rotation options (degrees)
    align_rotation_to_velocity: bool = False
    image_rotation_range: Optional[Tuple[float, float]] = None
    angular_velocity_range: Optional[Tuple[float, float]] = (None,)  # deg/sec
    scale_velocity_range: Optional[Tuple[float, float]] = (None,)  # scale factor per second


class Particle(spritePro.Sprite):
    """Одиночная частица-спрайт с скоростью, гравитацией, затуханием и вращением.

    Attributes:
        velocity (Vector2): Текущая скорость в пикселях в секунду.
        spawn_time (int): Время создания в тиках pygame (мс).
        lifetime (int): Время жизни в миллисекундах; частица исчезает после этого.
        fade_speed (float): Скорость затухания альфа-канала в секунду.
        gravity (Vector2): Вектор ускорения, применяемый каждый кадр.
        screen_space (bool): Если True, игнорирует смещение камеры при отрисовке.
        angular_velocity (float): Скорость вращения в градусах в секунду.
        scale_velocity (float): Скорость изменения масштаба (множитель в секунду).
    """

    def __init__(
        self,
        image: pygame.Surface,
        pos: Union[Tuple[float, float], Vector2],
        velocity: Vector2,
        lifetime_ms: int,
        fade_speed: float,
        gravity: Vector2,
        screen_space: bool,
        sorting_order: Optional[int] = None,
    ) -> None:
        """Инициализирует новую частицу.

        Args:
            image (pygame.Surface): Изображение частицы.
            pos (Union[Tuple[float, float], Vector2]): Начальная позиция частицы.
            velocity (Vector2): Начальная скорость частицы.
            lifetime_ms (int): Время жизни в миллисекундах.
            fade_speed (float): Скорость затухания альфа-канала в секунду.
            gravity (Vector2): Вектор гравитации (ускорения).
            screen_space (bool): Игнорировать ли смещение камеры.
            sorting_order (Optional[int], optional): Порядок отрисовки (слой). По умолчанию None.
        """
        super().__init__(
            image, size=image.get_size(), pos=Vector2(pos), sorting_order=sorting_order
        )
        self.velocity = velocity
        self.spawn_time = pygame.time.get_ticks()
        self.lifetime = lifetime_ms
        self.fade_speed = fade_speed
        self.gravity = gravity
        self.set_screen_space(screen_space)
        self.angular_velocity: float = 0.0  # degrees per second
        self.scale_velocity: float = 0.0  # scale factor per second
        self._pool_release = None

    def set_pool_release(self, release_fn) -> None:
        """Назначает обработчик возврата частицы в пул."""
        self._pool_release = release_fn

    def reset(
        self,
        image: pygame.Surface,
        pos: Union[Tuple[float, float], Vector2],
        velocity: Vector2,
        lifetime_ms: int,
        fade_speed: float,
        gravity: Vector2,
        screen_space: bool,
        sorting_order: Optional[int] = None,
    ) -> None:
        """Сбрасывает параметры частицы для повторного использования."""
        self.set_image(image)
        if sorting_order is not None:
            self.set_sorting_order(sorting_order)
        self.rect.center = (int(pos[0]), int(pos[1]))
        self.velocity = velocity
        self.spawn_time = pygame.time.get_ticks()
        self.lifetime = lifetime_ms
        self.fade_speed = fade_speed
        self.gravity = gravity
        self.set_screen_space(screen_space)
        self.angular_velocity = 0.0
        self.scale_velocity = 0.0
        self.scale = 1.0
        self.angle = 0.0
        self.alpha = 255
        self.scene = None
        self.active = True

    def update(self, screen: Optional[pygame.Surface] = None) -> None:
        """Обновляет состояние частицы и отрисовывает её на экране.

        Применяет гравитацию, обновляет позицию, вращение, масштаб и затухание.
        Удаляет частицу, если истекло время жизни или альфа-канал достиг нуля.

        Args:
            screen (Optional[pygame.Surface], optional): Поверхность для отрисовки. Если None, используется глобальный экран.
        """
        dt = spritePro.dt
        self.velocity += self.gravity * dt
        self.rect.centerx += self.velocity.x * dt
        self.rect.centery += self.velocity.y * dt
        # Apply continuous rotation if set
        if self.angular_velocity != 0.0:
            self.rotate_by(self.angular_velocity * dt)
        if self.scale_velocity != 0.0:
            self.scale_by(self.scale_velocity * dt)
        self.fade_by(-self.fade_speed * dt)

        self._update_image()

        if pygame.time.get_ticks() - self.spawn_time > self.lifetime or self.alpha <= 0:
            if self._pool_release is not None:
                handled = self._pool_release(self)
                if handled:
                    return
            self.kill()
            return

        if screen is None:
            screen = spritePro.screen
        if not screen:
            return

        if self.screen_space:
            screen.blit(self.image, self.rect)
        else:
            camera = spritePro.get_camera_position()
            draw_rect = self.rect.move(-int(camera.x), -int(camera.y))
            screen.blit(self.image, draw_rect)


class ParticleEmitter:
    """Простой настраиваемый эмиттер частиц, аналогичный Unity bursts.

    Attributes:
        config (ParticleConfig): Конфигурация эмиттера частиц.
        _position (Optional[Tuple[float, float] | Vector2]): Сохраненная позиция эмиттера.
        _anchor (str): Якорь позиционирования.
    """

    def __init__(
        self,
        config: Optional[ParticleConfig] = None,
        auto_emit: bool = False,
        emit_interval: float | Tuple[float, float] = 0.1,
        emit_step: float = 0.0,
        use_dt: bool = True,
        auto_register: bool = False,
        use_pool: bool = False,
        max_pool_size: int = 0,
    ) -> None:
        """Инициализирует новый эмиттер частиц.

        Args:
            config (Optional[ParticleConfig], optional): Конфигурация эмиттера. Если None, используется конфигурация по умолчанию.
            auto_emit (bool, optional): Авто-эмиссия в update(). По умолчанию False.
            emit_interval (float | Tuple[float, float], optional): Интервал авто-эмиссии (сек) или диапазон. По умолчанию 0.1.
            emit_step (float, optional): Дистанция для эмиссии (каждый шаг). По умолчанию 0.0.
            use_dt (bool, optional): Использовать dt из update(). По умолчанию True.
            auto_register (bool, optional): Автоматически регистрировать в spritePro.update(). По умолчанию False.
        """
        self.config = config or ParticleConfig()
        self._resolve_config_image()
        self._position: Optional[Tuple[float, float] | Vector2] = None
        self._anchor: str = "center"
        self._parent = None
        self._parent_follow = True
        self._particle_follow_parent = False
        self.auto_emit = auto_emit
        self.emit_interval = emit_interval
        self.emit_step = float(emit_step)
        self.use_dt = use_dt
        self._emit_elapsed = 0.0
        self._next_emit_interval = self._resolve_interval(self.emit_interval)
        self._last_emit_position: Optional[Vector2] = None
        self._last_update_time = time.monotonic()
        self.use_pool = use_pool
        self.max_pool_size = max_pool_size
        self._pool: List[Particle] = []
        if auto_register:
            try:
                spritePro.register_update_object(self)
            except Exception:
                pass

    def set_config(self, config: ParticleConfig) -> None:
        """Полностью заменяет конфигурацию эмиттера на лету."""
        self.config = config
        self._resolve_config_image()
        if self.auto_emit:
            self._reset_auto_emit_state()

    def set_parent(
        self,
        parent,
        keep_world_position: bool = True,
        follow_parent: bool = True,
        particle_follow_parent: bool = False,
    ) -> None:
        """Назначает родителя для создаваемых частиц."""
        self._parent = parent
        self._parent_follow = follow_parent
        self._particle_follow_parent = particle_follow_parent
        if parent is not None and keep_world_position is False:
            self._position = Vector2(0, 0)

    def set_position(
        self,
        position: Union[Tuple[float, float], Vector2],
        anchor: Union[str, "Anchor", None] = None,
    ) -> None:
        """Устанавливает позицию эмиттера для последующих вызовов emit() без аргументов.

        Args:
            position (Tuple[float, float] | Vector2): Координаты позиции (x, y).
            anchor (str | Anchor, optional): Якорь для позиционирования. По умолчанию Anchor.CENTER.
        """
        # Import Anchor here to avoid circular imports
        from .constants import Anchor as AnchorConst

        # Handle anchor positioning same as Sprite
        if anchor is None:
            anchor = AnchorConst.CENTER

        if isinstance(anchor, AnchorConst):
            anchor_key = str(anchor)
        elif isinstance(anchor, str):
            anchor_key = anchor.lower()
        else:
            anchor_key = str(AnchorConst.CENTER)

        # Store position with anchor info for future use
        self._position = position
        self._anchor = anchor_key

    def get_position(self) -> Optional[Tuple[float, float] | Vector2]:
        """Получает текущую позицию эмиттера.

        Returns:
            Optional[Tuple[float, float] | Vector2]: Текущая позиция эмиттера или None.
        """
        return self._position

    def update_config(self, **kwargs):
        """Обновляет конфигурацию эмиттера указанными значениями.

        Args:
            **kwargs: Параметры для обновления конфигурации.
        """
        self.config = replace(self.config, **kwargs)
        self._resolve_config_image()
        if self.auto_emit:
            self._reset_auto_emit_state()

    def start_auto_emit(self) -> None:
        """Запускает авто-эмиссию."""
        self.auto_emit = True
        self._reset_auto_emit_state()

    def stop_auto_emit(self) -> None:
        """Останавливает авто-эмиссию."""
        self.auto_emit = False

    def update(self, dt: Optional[float] = None) -> None:
        """Обновляет авто-эмиссию (по времени и/или по шагам расстояния)."""
        if not self.auto_emit:
            return
        if self.use_dt:
            if dt is None:
                try:
                    dt = spritePro.dt
                except Exception:
                    dt = 0.0
            dt_value = float(dt)
        else:
            now = time.monotonic()
            dt_value = max(0.0, now - self._last_update_time)
            self._last_update_time = now

        if self.emit_interval and self.emit_interval != 0:
            self._emit_elapsed += dt_value
            while self._emit_elapsed >= self._next_emit_interval:
                self.emit()
                self._emit_elapsed -= self._next_emit_interval
                self._next_emit_interval = self._resolve_interval(self.emit_interval)

        if self.emit_step > 0 and self._position is not None:
            current = Vector2(self._position)
            if self._last_emit_position is None:
                self._last_emit_position = current
                return
            distance = current.distance_to(self._last_emit_position)
            if distance >= self.emit_step:
                count = int(distance // self.emit_step)
                for _ in range(max(1, count)):
                    self.emit()
                self._last_emit_position = current

    def _resolve_config_image(self) -> None:
        """Загружает image из пути, если указана строка."""
        if isinstance(self.config.image, str):
            try:
                self.config.image = resource_cache.load_texture(self.config.image)
            except pygame.error as e:
                spritePro.debug_log_warning(
                    f"Error loading particle image at path: {self.config.image} ({e})"
                )
                self.config.image = None

    def _resolve_interval(self, value: float | Tuple[float, float]) -> float:
        if isinstance(value, tuple):
            lo, hi = value
            lo_val = float(lo)
            hi_val = float(hi)
            if hi_val < lo_val:
                lo_val, hi_val = hi_val, lo_val
            return random.uniform(lo_val, hi_val)
        return float(value)

    def _reset_auto_emit_state(self) -> None:
        self._emit_elapsed = 0.0
        self._next_emit_interval = self._resolve_interval(self.emit_interval)
        self._last_emit_position = Vector2(self._position) if self._position is not None else None
        self._last_update_time = time.monotonic()

    def emit(
        self,
        position: Optional[Tuple[float, float] | Vector2] = None,
        overrides: Optional[ParticleConfig] = None,
    ) -> Sequence[Particle]:
        """Создает и возвращает последовательность частиц согласно конфигурации.

        Args:
            position (Optional[Tuple[float, float] | Vector2], optional): Позиция эмиссии. Если None, используется сохраненная позиция или область спавна из конфигурации.
            overrides (Optional[ParticleConfig], optional): Переопределения конфигурации для этого вызова. По умолчанию None.

        Returns:
            Sequence[Particle]: Последовательность созданных частиц.
        """
        cfg = overrides or self.config
        # If no position provided, use emitter's stored position or spawn area from config
        if position is None:
            if self._parent is not None:
                position_vec = Vector2(self._parent.get_world_position())
            elif self._position is not None:
                position_vec = Vector2(self._position)
            elif cfg.spawn_rect is not None:
                # Use spawn_rect as the base position (particles will spawn within it)
                position_vec = Vector2(cfg.spawn_rect.centerx, cfg.spawn_rect.centery)
            elif cfg.spawn_circle_radius is not None:
                # Use origin for circle spawn
                position_vec = Vector2(0, 0)
            else:
                # Default to origin if no spawn area defined
                position_vec = Vector2(0, 0)
        else:
            position_vec = Vector2(position)
        particles: List[Particle] = []

        for index in range(cfg.amount):
            angle = random.uniform(*cfg.angle_range)
            speed = random.uniform(*cfg.speed_range)
            direction = Vector2(speed, 0).rotate(angle)

            # Resolve spawn offset within shape (if provided)
            spawn_pos = position_vec
            if cfg.spawn_rect is not None:
                try:
                    r = cfg.spawn_rect
                    if position is None:
                        # Use spawn_rect directly when no position provided
                        x = random.uniform(float(r.left), float(r.right))
                        y = random.uniform(float(r.top), float(r.bottom))
                        spawn_pos = Vector2(x, y)
                    else:
                        # Offset from provided position using spawn_rect dimensions
                        ox = random.uniform(-float(r.width) * 0.5, float(r.width) * 0.5)
                        oy = random.uniform(-float(r.height) * 0.5, float(r.height) * 0.5)
                        spawn_pos = position_vec + Vector2(ox, oy)
                except Exception:
                    pass
            elif cfg.spawn_circle_radius is not None:
                try:
                    r = random.uniform(0.0, float(cfg.spawn_circle_radius))
                    a = random.uniform(0.0, 360.0)
                    offset = Vector2(r, 0).rotate(a)
                    spawn_pos = position_vec + offset
                except Exception:
                    pass

            # Resolve image
            if cfg.image_factory is not None:
                image = cfg.image_factory(index)
            elif cfg.image is not None:
                image = cfg.image.copy()
            else:
                size = random.randint(*cfg.size_range)
                color = random.choice(cfg.colors)
                image = pygame.Surface((size, size), pygame.SRCALPHA)
                pygame.draw.circle(image, color, (size // 2, size // 2), size // 2)

            # Resolve lifetime
            if cfg.lifetime is not None:
                lifetime = max(0, int(cfg.lifetime * 1000.0))
            elif cfg.lifetime_range is not None:
                lo, hi = cfg.lifetime_range
                secs = random.uniform(float(lo), float(hi))
                lifetime = max(0, int(secs * 1000.0))
            else:
                # Default lifetime if not specified
                lifetime = random.randint(600, 1200)

            # Create particle
            if cfg.factory is not None:
                particle = cfg.factory(spawn_pos, direction, lifetime, cfg, index)
            elif cfg.particle_template is not None:
                # Используем шаблон частицы: копируем его свойства, но обновляем позицию, скорость и время жизни
                template = cfg.particle_template
                # Создаем новую частицу того же класса с теми же параметрами
                particle_cls = type(template)
                # Используем изображение из шаблона или из конфига
                template_image = image if image is not None else template.image.copy()
                particle = particle_cls(
                    image=template_image,
                    pos=spawn_pos,
                    velocity=direction,
                    lifetime_ms=lifetime,
                    fade_speed=cfg.fade_speed,
                    gravity=cfg.gravity,
                    screen_space=cfg.screen_space,
                    sorting_order=cfg.sorting_order
                    if cfg.sorting_order is not None
                    else template.sorting_order,
                )
                # Копируем дополнительные свойства из шаблона
                particle.angular_velocity = template.angular_velocity
                particle.scale_velocity = template.scale_velocity
                particle.scale = template.scale
                particle.angle = template.angle
                particle.alpha = template.alpha
                particle.color = template.color
                # Копируем любые дополнительные атрибуты, которые могут быть в пользовательских подклассах
                for attr_name in dir(template):
                    if not attr_name.startswith("_") and attr_name not in [
                        "image",
                        "rect",
                        "velocity",
                        "spawn_time",
                        "lifetime",
                        "fade_speed",
                        "gravity",
                        "screen_space",
                        "sorting_order",
                        "angular_velocity",
                        "scale_velocity",
                        "scale",
                        "angle",
                        "alpha",
                        "color",
                        "update",
                        "kill",
                        "active",
                        "position",
                        "x",
                        "y",
                        "width",
                        "height",
                    ]:
                        try:
                            if hasattr(template, attr_name) and not callable(
                                getattr(template, attr_name, None)
                            ):
                                setattr(particle, attr_name, getattr(template, attr_name))
                        except (AttributeError, TypeError):
                            pass
            else:
                particle_cls: Type[Particle] = cfg.particle_class or Particle  # type: ignore[assignment]
                if self.use_pool and particle_cls is Particle and self._pool:
                    particle = self._pool.pop()
                    particle.reset(
                        image=image,
                        pos=spawn_pos,
                        velocity=direction,
                        lifetime_ms=lifetime,
                        fade_speed=cfg.fade_speed,
                        gravity=cfg.gravity,
                        screen_space=cfg.screen_space,
                        sorting_order=cfg.sorting_order,
                    )
                else:
                    particle = particle_cls(
                        image=image,
                        pos=spawn_pos,
                        velocity=direction,
                        lifetime_ms=lifetime,
                        fade_speed=cfg.fade_speed,
                        gravity=cfg.gravity,
                        screen_space=cfg.screen_space,
                        sorting_order=cfg.sorting_order,
                    )
                    if self.use_pool and particle_cls is Particle:
                        particle.set_pool_release(self._release_particle)
            # Initialize rotation
            if cfg.align_rotation_to_velocity:
                particle.rotate_to(angle)
            elif cfg.image_rotation_range is not None:
                try:
                    particle.rotate_to(random.uniform(*cfg.image_rotation_range))
                except Exception:
                    pass
            # Set angular velocity if requested
            if cfg.angular_velocity_range is not None:
                try:
                    particle.angular_velocity = random.uniform(*cfg.angular_velocity_range)
                except Exception:
                    particle.angular_velocity = 0.0
            if cfg.scale_velocity_range is not None:
                try:
                    particle.scale_velocity = random.uniform(*cfg.scale_velocity_range)
                except Exception:
                    particle.scale_velocity = 0.0
            # Set initial scale if requested
            if cfg.image_scale_range is not None:
                try:
                    scale_factor = random.uniform(*cfg.image_scale_range)
                    particle.set_scale(scale_factor)
                except Exception:
                    pass

            if cfg.custom_factory:
                cfg.custom_factory(particle, index)
            particles.append(particle)
            if self._parent is not None:
                particle.set_parent(self._parent, keep_world_position=not self._parent_follow)
                particle.follow_parent = self._particle_follow_parent

        return particles

    def _release_particle(self, particle: Particle) -> bool:
        if not self.use_pool:
            return False
        if self.max_pool_size > 0 and len(self._pool) >= self.max_pool_size:
            return False
        particle.active = False
        self._pool.append(particle)
        return True


# ------------------------------
# Ready-made configuration templates and helpers
# ------------------------------


def particle_config_copy(cfg: ParticleConfig) -> ParticleConfig:
    """Возвращает поверхностную копию ParticleConfig для удобной настройки.

    Args:
        cfg (ParticleConfig): Конфигурация для копирования.

    Returns:
        ParticleConfig: Копия конфигурации.

    Note:
        Поверхности внутри не копируются глубоко. Измените `image` в копии, если
        нужен отдельный экземпляр поверхности.
    """
    return replace(cfg)


def template_sparks() -> ParticleConfig:
    """Шаблон конфигурации: маленькие яркие искры во всех направлениях, короткое время жизни.

    Returns:
        ParticleConfig: Конфигурация для эффекта искр.
    """
    return ParticleConfig(
        amount=30,
        lifetime_range=(0.25, 0.6),
        speed_range=(220.0, 420.0),
        angle_range=(0.0, 360.0),
        fade_speed=400.0,
        gravity=Vector2(0, 380.0),
        colors=[(255, 240, 120), (255, 200, 80), (255, 255, 255)],
        image=None,
        image_scale_range=None,
        image_rotation_range=(0.0, 360.0),
        angular_velocity_range=(-360.0, 360.0),
        sorting_order=None,
    )


def template_smoke() -> ParticleConfig:
    """Шаблон конфигурации: мягкие серые клубы дыма, дрейфующие вверх, долгое время жизни.

    Returns:
        ParticleConfig: Конфигурация для эффекта дыма.
    """
    return ParticleConfig(
        amount=20,
        lifetime_range=(1.8, 3.0),
        speed_range=(20.0, 60.0),
        angle_range=(-100.0, -80.0),  # mostly upwards
        fade_speed=80.0,
        gravity=Vector2(0, -20.0),
        colors=[(170, 170, 170), (140, 140, 140), (110, 110, 110)],
        image=None,
        image_scale_range=None,
        image_rotation_range=(0.0, 360.0),
        angular_velocity_range=(-20.0, 20.0),
        sorting_order=None,
    )


def template_fire() -> ParticleConfig:
    """Шаблон конфигурации: огненный взрыв вверх с оранжево-красными тонами, среднее время жизни.

    Returns:
        ParticleConfig: Конфигурация для эффекта огня.
    """
    return ParticleConfig(
        amount=40,
        lifetime_range=(0.5, 1.2),
        speed_range=(160.0, 320.0),
        angle_range=(-120.0, -60.0),  # upward cone
        fade_speed=300.0,
        gravity=Vector2(0, -60.0),
        colors=[(255, 180, 60), (255, 120, 50), (255, 80, 40)],
        image=None,
        image_scale_range=(0.6, 1.2),
        image_rotation_range=(0.0, 360.0),
        angular_velocity_range=(-120.0, 120.0),
        sorting_order=None,
        align_rotation_to_velocity=True,
    )


def template_snowfall() -> ParticleConfig:
    """Шаблон конфигурации: мягкий снегопад, частицы создаются в широком прямоугольнике над экраном и падают вниз.

    Returns:
        ParticleConfig: Конфигурация для эффекта снегопада.
    """
    return ParticleConfig(
        amount=20,
        lifetime_range=(10, 10),
        speed_range=(40.0, 80.0),
        angle_range=(80.0, 100.0),  # mostly downward
        fade_speed=30.0,
        gravity=Vector2(0, 40.0),
        colors=[(255, 255, 255)],
        image=None,
        image_scale_range=(0.6, 1.0),
        image_rotation_range=None,
        angular_velocity_range=(-20.0, 20.0),
        sorting_order=None,
        spawn_rect=pygame.Rect(-600, -10, 1200, 10),  # wide line spawn above
        screen_space=False,
    )


def template_circular_burst() -> ParticleConfig:
    """Шаблон конфигурации: круговой взрыв, частицы создаются в круге радиусом 100px и взрываются наружу.

    Returns:
        ParticleConfig: Конфигурация для эффекта кругового взрыва.
    """
    return ParticleConfig(
        amount=50,
        lifetime_range=(0.8, 1.5),
        speed_range=(200.0, 400.0),
        angle_range=(0.0, 360.0),  # all directions
        fade_speed=300.0,
        gravity=Vector2(0, 0),  # no gravity for explosion
        colors=[(255, 200, 100), (255, 150, 50), (255, 100, 0)],
        image=None,
        image_scale_range=(0.4, 0.8),
        image_rotation_range=(0.0, 360.0),
        angular_velocity_range=(-200.0, 200.0),
        sorting_order=None,
        spawn_circle_radius=100.0,  # 100px radius spawn area
        screen_space=False,
    )


def template_trail() -> ParticleConfig:
    """Шаблон конфигурации: короткий шлейф из мелких частиц для быстрого объекта.

    Returns:
        ParticleConfig: Конфигурация для эффекта trail.
    """
    return ParticleConfig(
        amount=6,
        lifetime_range=(0.12, 0.28),
        speed_range=(20.0, 80.0),
        angle_range=(0.0, 360.0),
        fade_speed=320.0,
        gravity=Vector2(0, 0),
        colors=[(220, 220, 255), (180, 220, 255)],
        image=None,
        image_scale_range=None,
        sorting_order=900,
    )
