"""
scenes/game/__init__.py — Game scene package for Powernet.

The Game class is assembled from mixin components:
  GameRendererMixin   — all draw_* methods
  GameInputMixin      — input handling (_upd_play, _on_lmb, etc.)
  GameWorldGenMixin   — _world_gen, _try_place_near, _expand_world_for_level
  GameCameraMixin     — _w2s, _s2g, _clamp_cam, _game_h

``from scenes.game import Game`` continues to work for main.py.
"""

from __future__ import annotations

import time
from typing import Optional

import pygame

from helpers.game_data import GameState, PlacedBuilding, Wire
from helpers.gui import UITheme
from helpers.sfx import init_sfx  # type: ignore[import]
from helpers.world_data import WorldMeta

from .camera       import GameCameraMixin        # type: ignore[import]
from .constants    import (                       # type: ignore[import]
    TILE_SIZE, TOOLBAR_H, PICKER_SPEED,
    PICKER_TABS, PICKER_PAD,
    CARD_GAP, CARD_STRIDE, ARROW_W,
    TAB_W, TAB_H, TAB_GAP, TOOL_W,
    _S,
)
from .input_handler import GameInputMixin         # type: ignore[import]
from .power         import compute_powered        # type: ignore[import]
from .renderer      import GameRendererMixin      # type: ignore[import]
from .world_gen     import GameWorldGenMixin      # type: ignore[import]


class Game(GameRendererMixin, GameInputMixin, GameWorldGenMixin, GameCameraMixin):
    """
    Main in-game scene.  Returns action strings via update() → 'back'.

    Inherits all draw/input/world-gen/camera logic from mixin classes.
    """

    def __init__(self, screen: pygame.Surface, meta: WorldMeta) -> None:
        self._screen = screen
        self._meta   = meta
        self._theme  = UITheme()

        # Initialise sound effects
        init_sfx()

        # Load or initialise game state
        raw = getattr(meta, "game_data", {}) or {}
        self._gs = GameState.from_dict(raw) if raw else GameState()

        # Run world generation if this is a new game
        if not raw:
            self._world_gen()

        # Camera
        self._cam_x: float = self._gs.cam_x
        self._cam_y: float = self._gs.cam_y
        self._zoom:  float = 1.0

        # Middle-mouse panning
        self._panning      = False
        self._pan_origin:  tuple[int, int]     = (0, 0)
        self._cam_origin:  tuple[float, float] = (0.0, 0.0)

        # LMB drag-to-pan state
        self._lmb_panning:    bool                            = False
        self._lmb_pan_start:  Optional[tuple[int, int]]       = None
        self._lmb_pan_moved:  bool                            = False
        self._lmb_cam_origin: tuple[float, float]             = (0.0, 0.0)

        # Interaction state
        self._state      = _S.EXPLORE
        self._prev_state = _S.EXPLORE
        self._active_tab: Optional[str] = None

        # Picker animation
        self._picker_t:   float = 0.0
        self._picker_tgt: float = 0.0

        # Per-tab picker scroll offset (pixels)
        self._picker_scroll: dict[str, int] = {}

        # Tool / placement
        self._sel_type:   Optional[str] = None
        self._wire_src:   Optional[str] = None
        self._wire_layer: int           = 0

        # Hover & selection
        self._hov_cell:     Optional[tuple[int, int]] = None
        self._hov_building: Optional[PlacedBuilding]  = None
        self._hov_wire:     Optional[Wire]             = None
        self._sel_building: Optional[PlacedBuilding]  = None

        # Power simulation results
        self._powered:    set[str]                  = set()
        self._overloaded: dict[str, float]          = {}   # generator build_id → overload_mw
        self._polluted:   set[tuple[int, int]]      = set()

        # Wire flow results
        self._wire_flows: dict[str, float] = {}

        # Emergency loan flag (softlock prevention)
        self._loan_pending: bool = False

        # Wire tier clickable button rects (tier_index, rect)
        self._wire_tier_btn_rects: list[tuple[int, pygame.Rect]] = []

        # Alert banner
        self._alert_text  = ""
        self._alert_timer = 0.0

        # Level-up notification
        self._levelup_timer: float = 0.0
        self._levelup_level: int   = self._gs.level()

        # Previous level (to detect level-up)
        self._prev_level: int = self._gs.level()

        # Time pause (SPACE key — freezes XP/economy, keeps camera/UI active)
        self._time_paused: bool = False

        # Quit flag
        self._want_quit = False

        # Animation timer
        self._anim_timer: float = 0.0

        # Caches
        self._tex_cache:    dict[str, list[pygame.Surface]]           = {}
        self._scaled_cache: dict[tuple[str, int, int], pygame.Surface] = {}
        self._ph_cache:     dict[tuple[int, int], pygame.Surface]     = {}

        # Picker scroll arrow rects (computed per draw)
        self._picker_left_r:  Optional[pygame.Rect] = None
        self._picker_right_r: Optional[pygame.Rect] = None

        # Layout rects (built in _rebuild)
        self._tab_rects:  dict[str, pygame.Rect] = {}
        self._tool_rects: dict[str, pygame.Rect] = {}
        self._pause_rect:        Optional[pygame.Rect] = None
        self._pause_resume_r:    Optional[pygame.Rect] = None
        self._pause_settings_r:  Optional[pygame.Rect] = None
        self._pause_quit_r:      Optional[pygame.Rect] = None

        # Debug menu
        self._debug_open: bool = False
        self._debug_btn_rects: list[tuple[str, pygame.Rect]] = []

        self._sw: int = 0
        self._sh: int = 0

        self._rebuild()

        # Centre camera on grid (first load only)
        if self._gs.cam_x == 0.0 and self._gs.cam_y == 0.0:
            sw, sh = screen.get_size()
            gah    = sh - TOOLBAR_H
            ww     = self._gs.grid_w * TILE_SIZE
            wh     = self._gs.grid_h * TILE_SIZE
            self._cam_x = max(0.0, (ww - sw) / 2)
            self._cam_y = max(0.0, (wh - gah) / 2)

    # ------------------------------------------------------------------
    # Layout rebuild
    # ------------------------------------------------------------------

    def _rebuild(self) -> None:
        sw, sh = self._screen.get_size()
        self._sw = sw
        self._sh = sh

        ty = sh - TOOLBAR_H
        self._tab_rects = {}
        for i, tab in enumerate(PICKER_TABS):
            x = PICKER_PAD + i * (TAB_W + TAB_GAP)
            self._tab_rects[tab] = pygame.Rect(x, ty + 7, TAB_W, TAB_H)

        sep_x = PICKER_PAD + len(PICKER_TABS) * (TAB_W + TAB_GAP) + 4
        self._tool_rects = {
            "bulldoze": pygame.Rect(sep_x + 8, ty + 7, TOOL_W, TAB_H),
        }

        pw, ph = 360, 284
        px = (sw - pw) // 2
        py = (sh - ph) // 2
        self._pause_rect        = pygame.Rect(px, py, pw, ph)
        self._pause_resume_r    = pygame.Rect(px + 40, py + 75,  pw - 80, 44)
        self._pause_settings_r  = pygame.Rect(px + 40, py + 129, pw - 80, 44)
        self._pause_quit_r      = pygame.Rect(px + 40, py + 183, pw - 80, 44)

    # ------------------------------------------------------------------
    # Picker helpers
    # ------------------------------------------------------------------

    def _cards_for_tab(self, tab: str) -> list[str]:
        if tab == "wires":
            return []
        from helpers.game_data import BUILDING_TYPES
        return [tid for tid, td in BUILDING_TYPES.items()
                if td.category == tab and td.player_buildable]

    def _picker_content_x(self) -> int:
        return PICKER_PAD + ARROW_W + 4

    def _picker_visible_w(self) -> int:
        return self._sw - self._picker_content_x() - ARROW_W - PICKER_PAD - 4

    def _scroll_for_tab(self, tab: str) -> int:
        return self._picker_scroll.get(tab, 0)

    def _clamp_scroll(self, tab: str) -> None:
        cards      = self._cards_for_tab(tab)
        total_w    = max(0, len(cards) * CARD_STRIDE - CARD_GAP)
        visible_w  = self._picker_visible_w()
        max_scroll = max(0, total_w - visible_w)
        cur        = self._picker_scroll.get(tab, 0)
        self._picker_scroll[tab] = max(0, min(cur, max_scroll))

    # ------------------------------------------------------------------
    # Save
    # ------------------------------------------------------------------

    def _save(self) -> None:
        self._gs.cam_x         = self._cam_x
        self._gs.cam_y         = self._cam_y
        self._meta.game_data   = self._gs.to_dict()
        self._meta.last_played = time.time()
        self._meta.save()

    # ------------------------------------------------------------------
    # Update (main entry point)
    # ------------------------------------------------------------------

    def update(self, dt: float, events: list[pygame.event.Event]) -> Optional[str]:
        # Resize check
        nsw, nsh = self._screen.get_size()
        if (nsw, nsh) != (self._sw, self._sh):
            self._rebuild()

        # Animation timer
        self._anim_timer += dt

        # Alert countdown
        if self._alert_timer > 0:
            self._alert_timer = max(0.0, self._alert_timer - dt)

        # Level-up notification countdown
        if self._levelup_timer > 0:
            self._levelup_timer = max(0.0, self._levelup_timer - dt)

        # Picker animation
        self._picker_t += (self._picker_tgt - self._picker_t) * min(1.0, PICKER_SPEED * dt)

        # Compute powered buildings and pollution each frame
        self._powered, self._overloaded, self._wire_flows = compute_powered(self._gs)
        self._polluted = self._gs.polluted_tiles()

        # Keyboard camera pan, XP and economy update
        if self._state != _S.PAUSED:
            self._pan_keys(dt)
            if not self._time_paused:
                self._upd_xp(dt)
                self._upd_economy(dt)

        if self._state == _S.PAUSED:
            return self._upd_pause(events)

        return self._upd_play(dt, events)


__all__ = ["Game"]
