"""
settings_menu.py — Settings screen for Powernet.

Options
-------
• Max Framerate    — 10–360 FPS; "Unlimited" when at maximum
• Music Volume     — 0–100 %
• Sounds Volume    — 0–100 %
• Buildings Volume — 0–100 %
• Language         — en / de / nl / fr  (list selector)
• Window Mode      — windowed / fullscreen
• Resolution       — 1280×720 / 1920×1080 / 2560×1440
• UI Scale         — 1–4
• Title Glow       — on / off
• Colorblind Mode  — on / off
• Controls         — rebindable keybindings

Changes are written to settings.json / keybindings.json on SAVE.

Returns from update()
---------------------
"back"  → return to caller (main menu)
"""

from __future__ import annotations

from typing import Optional

import pygame

from helpers.gui import (
    Alert, GUIHandler, ListSelector, Slider, theme,
)
from helpers.i18n import LANGUAGES, set_language, t
from helpers.icons import get_icon, ICO_BACK
from helpers.keybindings import ACTION_LABELS, get_keybindings, key_name  # type: ignore[import]
from helpers.music import music_manager
from helpers.settings import Settings, get_settings


def _fps_fmt(v: int) -> str:
    return t("st_fps_unlim") if v == 360 else t("st_fps_val", fps=str(v))


def _vol_fmt(v: int) -> str:
    return t("st_vol_off") if v == 0 else t("st_vol_pct", pct=str(v))


def _scale_fmt(v: int) -> str:
    labels = {1: "Small (0.75×)", 2: "Normal (1.0×)", 3: "Large (1.25×)", 4: "XL (1.5×)"}
    return labels.get(v, str(v))


# Ordered list of rebindable actions (defines display order in Controls section)
_ACTIONS = [
    "tab_generators",
    "tab_research",
    "tab_logistics",
    "tab_wires",
    "bulldoze",
    "pause_time",
    "debug",
]

# Row height for keybinding rows
_KB_ROW_H = 38
_KB_ROW_GAP = 6


class SettingsMenu:
    """
    Scene: Application settings.

    update() return values
    ----------------------
    "back"   Main menu
    None     No transition
    """

    def __init__(self, screen: pygame.Surface) -> None:
        self._pending: Optional[str] = None
        self._settings = get_settings()
        self._keybindings = get_keybindings()
        self._alert: Optional[Alert] = None

        self._gui = GUIHandler()         # scrollable content
        self._gui_header = GUIHandler()  # fixed header (drawn on top)
        self._last_size = (0, 0)
        self._scroll_y: int = 0
        self._content_h: int = 0

        # Slider / selector references for value read-back
        self._sl_fps: Optional[Slider] = None
        self._sl_music: Optional[Slider] = None
        self._sl_sounds: Optional[Slider] = None
        self._sl_buildings: Optional[Slider] = None
        self._sl_ui_scale: Optional[Slider] = None
        self._ls_lang: Optional[ListSelector] = None
        self._ls_window: Optional[ListSelector] = None
        self._ls_res: Optional[ListSelector] = None
        self._ls_title_glow: Optional[ListSelector] = None
        self._ls_colorblind: Optional[ListSelector] = None

        # Controls section state
        # _kb_rows: list of (action, row_rect) built each _build()
        self._kb_rows: list[tuple[str, pygame.Rect]] = []
        # _kb_reset_rect: rect for the "Reset to Defaults" small button in controls section
        self._kb_reset_rect: Optional[pygame.Rect] = None
        # Currently listening for a new key binding: action name or None
        self._kb_listening: Optional[str] = None

        self._build(screen.get_size())

    # ------------------------------------------------------------------
    # Build
    # ------------------------------------------------------------------

    _HEADER_H = 64   # height of the fixed header strip

    def _build(self, size: tuple[int, int]) -> None:
        w = size[0]
        self._last_size = size
        cx = w // 2
        pad = 20

        self._gui.clear()
        self._gui_header.clear()
        self._kb_rows = []
        self._kb_reset_rect = None

        # Fixed header (drawn on top, not scrolled)
        self._gui_header.panel(cx, 0, w, 62, color=theme.PANEL, alpha=220,
                               border_color=None, anchor="midtop")
        self._gui_header.panel(cx, 62, w, 2, color=theme.ACCENT, alpha=180,
                               border_color=None, border_width=0, anchor="midtop")
        self._gui_header.label(cx, 31, t("st_title"),
                               font_size=26, color=theme.TEXT, anchor="center")
        self._gui_header.small_button(
            pad, 31, t("back"),
            font_size=16, anchor="midleft",
            color=theme.TEXT_DIM, hover_color=theme.ACCENT,
            icon_surf=get_icon(ICO_BACK, 16),
            on_click=self._on_back,
        )

        # Content area — two-column form, offset by scroll
        form_x = cx - 340
        form_w = 680
        col_w = (form_w - 30) // 2   # left-column label width
        right_x = form_x + col_w + 30
        right_w = form_w - col_w - 30
        y = 90 - self._scroll_y

        # ---- Section: Display ----
        self._gui.label(form_x, y, t("st_perf"),
                        font_size=20, color=theme.ACCENT)
        self._gui.divider(form_x, y + 28, form_w, anchor="topleft")
        y += 44

        # Window mode
        self._gui.label(form_x, y + 6, t("st_window"),
                        font_size=17, color=theme.TEXT_DIM)
        window_opts: list[tuple[str, str]] = [
            ("windowed",   t("st_windowed")),
            ("fullscreen", t("st_fullscreen")),
        ]
        ls_window = ListSelector(
            right_x, y, right_w, 44,
            options=window_opts,
            value="fullscreen" if self._settings.fullscreen else "windowed",
            font_size=18,
        )
        self._ls_window = ls_window
        self._gui.add(ls_window)
        y += 54

        # Resolution (windowed only)
        self._gui.label(form_x, y + 6, t("st_res"),
                        font_size=17, color=theme.TEXT_DIM)
        res_opts: list[tuple[str, str]] = [
            ("1024x768",   "1024 × 768"),
            ("1280x720",   "1280 × 720"),
            ("1280x800",   "1280 × 800"),
            ("1366x768",   "1366 × 768"),
            ("1440x900",   "1440 × 900"),
            ("1600x900",   "1600 × 900"),
            ("1920x1080",  "1920 × 1080"),
            ("2560x1440",  "2560 × 1440"),
            ("3840x2160",  "3840 × 2160  (4K)"),
        ]
        cur_res = f"{self._settings.resolution_w}x{self._settings.resolution_h}"
        if cur_res not in dict(res_opts):
            cur_res = "1280x720"
        ls_res = ListSelector(
            right_x, y, right_w, 44,
            options=res_opts,
            value=cur_res,
            font_size=18,
        )
        self._ls_res = ls_res
        self._gui.add(ls_res)
        y += 54

        # FPS slider
        self._gui.label(form_x, y + 6, t("st_fps"),
                        font_size=17, color=theme.TEXT_DIM)
        sl_fps = Slider(
            right_x, y, right_w,
            min_val=10, max_val=360,
            value=self._settings.fps_limit if self._settings.fps_limit > 0 else 360,
            step=10, font_size=15,
            format_fn=_fps_fmt,
        )
        self._sl_fps = sl_fps
        self._gui.add(sl_fps)
        y += 54

        # ---- Section: Visuals ----
        self._gui.label(form_x, y, t("st_ui_scale").split()[0] + " & Effects",
                        font_size=20, color=theme.ACCENT)
        self._gui.divider(form_x, y + 28, form_w, anchor="topleft")
        y += 44

        # UI Scale slider
        self._gui.label(form_x, y + 6, t("st_ui_scale"),
                        font_size=17, color=theme.TEXT_DIM)
        sl_ui_scale = Slider(
            right_x, y, right_w,
            min_val=1, max_val=4,
            value=self._settings.ui_scale,
            step=1, font_size=15,
            format_fn=_scale_fmt,
        )
        self._sl_ui_scale = sl_ui_scale
        self._gui.add(sl_ui_scale)
        y += 54

        # Title glow toggle
        self._gui.label(form_x, y + 6, t("st_title_glow"),
                        font_size=17, color=theme.TEXT_DIM)
        bool_opts: list[tuple[str, str]] = [("on", "On"), ("off", "Off")]
        ls_title_glow = ListSelector(
            right_x, y, right_w, 44,
            options=bool_opts,
            value="on" if self._settings.title_glow else "off",
            font_size=18,
        )
        self._ls_title_glow = ls_title_glow
        self._gui.add(ls_title_glow)
        y += 54

        # Colorblind mode toggle
        self._gui.label(form_x, y + 6, t("st_colorblind"),
                        font_size=17, color=theme.TEXT_DIM)
        ls_colorblind = ListSelector(
            right_x, y, right_w, 44,
            options=bool_opts,
            value="on" if self._settings.colorblind_mode else "off",
            font_size=18,
        )
        self._ls_colorblind = ls_colorblind
        self._gui.add(ls_colorblind)
        y += 54

        # ---- Section: Audio ----
        self._gui.label(form_x, y, t("st_audio"),
                        font_size=20, color=theme.ACCENT)
        self._gui.divider(form_x, y + 28, form_w, anchor="topleft")
        y += 44

        # Music volume
        self._gui.label(form_x, y + 6, t("st_vol_music"),
                        font_size=17, color=theme.TEXT_DIM)
        sl_music = Slider(
            right_x, y, right_w,
            min_val=0, max_val=100,
            value=self._settings.volume_music,
            step=5, font_size=15,
            format_fn=_vol_fmt,
            on_change=lambda v: music_manager.set_volume(v / 100.0),
        )
        self._sl_music = sl_music
        self._gui.add(sl_music)
        y += 54

        # Sounds volume
        self._gui.label(form_x, y + 6, t("st_vol_sounds"),
                        font_size=17, color=theme.TEXT_DIM)
        sl_sounds = Slider(
            right_x, y, right_w,
            min_val=0, max_val=100,
            value=self._settings.volume_sounds,
            step=5, font_size=15,
            format_fn=_vol_fmt,
        )
        self._sl_sounds = sl_sounds
        self._gui.add(sl_sounds)
        y += 54

        # Buildings volume
        self._gui.label(form_x, y + 6, t("st_vol_buildings"),
                        font_size=17, color=theme.TEXT_DIM)
        sl_buildings = Slider(
            right_x, y, right_w,
            min_val=0, max_val=100,
            value=self._settings.volume_buildings,
            step=5, font_size=15,
            format_fn=_vol_fmt,
        )
        self._sl_buildings = sl_buildings
        self._gui.add(sl_buildings)
        y += 54

        # ---- Section: Language ----
        self._gui.label(form_x, y, t("st_lang"),
                        font_size=20, color=theme.ACCENT)
        self._gui.divider(form_x, y + 28, form_w, anchor="topleft")
        y += 44

        lang_options: list[tuple[str, str]] = list(LANGUAGES.items())
        ls_lang = ListSelector(
            right_x, y, right_w, 44,
            options=lang_options,
            value=self._settings.language,
            font_size=18,
        )
        self._ls_lang = ls_lang
        self._gui.add(ls_lang)
        y += 60

        # ---- Section: Controls ----
        y += 10
        self._gui.label(form_x, y, t("st_controls"),
                        font_size=20, color=theme.ACCENT)
        self._gui.divider(form_x, y + 28, form_w, anchor="topleft")
        y += 44

        # Instruction hint label
        self._gui.label(form_x, y, t("st_keybind_hint"),
                        font_size=14, color=theme.TEXT_DIM)
        y += 22

        # Keybinding rows — stored for custom draw/click handling
        for action in _ACTIONS:
            row_rect = pygame.Rect(form_x, y, form_w, _KB_ROW_H)
            self._kb_rows.append((action, row_rect))
            y += _KB_ROW_H + _KB_ROW_GAP

        y += 8
        # "Reset keybindings" small button
        self._kb_reset_rect = pygame.Rect(form_x, y, 200, 30)
        y += 40

        # ---- Buttons row ----
        y += 10
        # Save button (centre)
        self._gui.button(
            cx - 10, y, 220, 52, t("st_save"),
            font_size=24, anchor="midtop",
            on_click=self._on_save,
            name="btn_save",
        )
        # Restore defaults (right of save)
        self._gui.small_button(
            cx + 130, y + 26, t("st_restore"),
            font_size=15, anchor="midleft",
            color=theme.TEXT_DIM, hover_color=(200, 80, 60),
            on_click=self._on_restore_defaults,
        )
        y += 80

        # Record raw content height so we know the scroll range
        self._content_h = y + self._scroll_y - 90

    # ------------------------------------------------------------------
    # Callbacks
    # ------------------------------------------------------------------

    def _on_back(self) -> None:
        self._kb_listening = None
        self._pending = "back"

    def _on_restore_defaults(self) -> None:
        defaults = Settings()
        # Copy defaults into the live settings object
        self._settings.__dict__.update(defaults.__dict__)
        self._settings.save()
        # Re-apply colorblind palette
        theme.apply_colorblind(self._settings.colorblind_mode)
        # Re-apply language
        set_language(self._settings.language)
        # Rebuild UI to reflect new values
        self._build(self._last_size)
        self._alert = Alert(t("st_restored"), duration=2.5, font_size=17)

    def _on_reset_keybindings(self) -> None:
        self._keybindings.reset_to_defaults()
        self._kb_listening = None
        self._alert = Alert(t("st_kb_reset"), duration=2.0, font_size=17)

    def _on_save(self) -> None:
        s = self._settings

        if self._ls_window:
            s.fullscreen = (self._ls_window.value == "fullscreen")

        if self._ls_res:
            try:
                rw, rh = self._ls_res.value.split("x")
                s.resolution_w, s.resolution_h = int(rw), int(rh)
            except ValueError:
                pass

        if self._sl_fps:
            raw = self._sl_fps.value
            s.fps_limit = 0 if raw == 360 else raw

        if self._sl_ui_scale:
            s.ui_scale = self._sl_ui_scale.value

        if self._ls_title_glow:
            s.title_glow = (self._ls_title_glow.value == "on")

        if self._ls_colorblind:
            new_cb = (self._ls_colorblind.value == "on")
            if new_cb != s.colorblind_mode:
                s.colorblind_mode = new_cb
                theme.apply_colorblind(new_cb)

        if self._sl_music:
            s.volume_music = self._sl_music.value
            music_manager.set_volume(s.volume_music / 100.0)

        if self._sl_sounds:
            s.volume_sounds = self._sl_sounds.value

        if self._sl_buildings:
            s.volume_buildings = self._sl_buildings.value

        if self._ls_lang:
            new_lang = self._ls_lang.value
            if new_lang != s.language:
                s.language = new_lang
                set_language(new_lang)
                # Rebuild labels with new language
                self._build(self._last_size)

        # Apply display changes immediately
        if self._settings.fullscreen:
            pygame.display.set_mode((0, 0), pygame.FULLSCREEN | pygame.RESIZABLE)
        else:
            pygame.display.set_mode(
                (self._settings.resolution_w, self._settings.resolution_h),
                pygame.RESIZABLE,
            )

        s.save()
        # Keybindings are saved immediately when assigned; call save() again to be safe
        self._keybindings.save()
        self._alert = Alert(t("st_saved"), duration=2.5, font_size=17)

    # ------------------------------------------------------------------
    # Keybinding row drawing
    # ------------------------------------------------------------------

    def _draw_kb_rows(self, surface: pygame.Surface) -> None:
        font_label = theme.font(15)
        font_key   = theme.font(15)
        mx, my = pygame.mouse.get_pos()

        for action, row in self._kb_rows:
            is_listening = (self._kb_listening == action)
            is_hovered   = row.collidepoint(mx, my) and not is_listening

            # Row background
            if is_listening:
                bg_col     = (40, 22, 8)
                border_col = theme.ACCENT
            elif is_hovered:
                bg_col     = (18, 30, 52)
                border_col = theme.BORDER_HOV if hasattr(theme, "BORDER_HOV") else theme.BORDER
            else:
                bg_col     = (14, 24, 38)
                border_col = theme.BORDER

            pygame.draw.rect(surface, bg_col,     row, border_radius=3)
            pygame.draw.rect(surface, border_col, row, width=1, border_radius=3)

            # Accent bar on left edge
            bar_col = theme.ACCENT if is_listening else (40, 80, 145)
            bar_rect = pygame.Rect(row.x, row.y + 1, 4, row.h - 2)
            pygame.draw.rect(surface, bar_col, bar_rect, border_radius=2)

            # Label (left side)
            label_text = t(f"kb_{action}")
            label_surf = font_label.render(label_text, True, theme.TEXT)
            surface.blit(label_surf, (row.x + 14, row.centery - label_surf.get_height() // 2))

            # Key badge (right side)
            if is_listening:
                badge_text = t("st_kb_press")
                badge_col  = theme.ACCENT
            else:
                badge_text = key_name(self._keybindings[action])
                badge_col  = theme.TEXT

            badge_surf = font_key.render(badge_text, True, badge_col)
            # Draw a small badge box around the key name
            badge_w = badge_surf.get_width() + 16
            badge_h = badge_surf.get_height() + 6
            badge_x = row.right - badge_w - 10
            badge_y = row.centery - badge_h // 2
            badge_rect = pygame.Rect(badge_x, badge_y, badge_w, badge_h)
            badge_bg   = (50, 30, 5) if is_listening else (20, 35, 58)
            pygame.draw.rect(surface, badge_bg,   badge_rect, border_radius=3)
            pygame.draw.rect(surface, badge_col,  badge_rect, width=1, border_radius=3)
            surface.blit(badge_surf, (badge_x + 8, badge_y + 3))

    def _draw_kb_reset_btn(self, surface: pygame.Surface) -> None:
        if self._kb_reset_rect is None:
            return
        mx, my = pygame.mouse.get_pos()
        hov = self._kb_reset_rect.collidepoint(mx, my)
        col = (200, 80, 60) if hov else theme.TEXT_DIM
        font = theme.font(14)
        text = t("st_kb_reset_btn")
        surf = font.render(text, True, col)
        surface.blit(surf, (self._kb_reset_rect.x, self._kb_reset_rect.y + 8))

    # ------------------------------------------------------------------
    # Scene interface
    # ------------------------------------------------------------------

    def update(self, dt: float, events: list[pygame.event.Event]) -> Optional[str]:
        if self._last_size != pygame.display.get_surface().get_size():
            self._build(pygame.display.get_surface().get_size())

        sh = pygame.display.get_surface().get_height()
        view_h = sh - self._HEADER_H

        for event in events:
            if event.type in (pygame.VIDEORESIZE, pygame.WINDOWRESIZED):
                new = pygame.display.get_surface().get_size()
                if new != self._last_size:
                    self._build(new)

            elif event.type == pygame.MOUSEWHEEL:
                # Use precise_y for smooth touchpad scrolling
                dy = getattr(event, "precise_y", float(event.y))
                max_scroll = max(0, self._content_h - view_h + 20)
                self._scroll_y = int(max(0, min(self._scroll_y - dy * 30, max_scroll)))
                self._build(self._last_size)

            # ESC: cancel listening or go back
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                if self._kb_listening is not None:
                    self._kb_listening = None
                else:
                    self._pending = "back"

            # Listening for a new key
            elif event.type == pygame.KEYDOWN and self._kb_listening is not None:
                # Disallow keys that are always global (ESC handled above, F3/F11 reserved)
                forbidden = {pygame.K_ESCAPE, pygame.K_F3, pygame.K_F11}
                if event.key not in forbidden:
                    self._keybindings[self._kb_listening] = event.key
                    self._keybindings.save()
                    self._kb_listening = None

            # Mouse click — keybinding rows or reset button
            elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                clicked_row = False
                for action, row in self._kb_rows:
                    if row.collidepoint(event.pos):
                        if self._kb_listening == action:
                            # Click active row again to cancel
                            self._kb_listening = None
                        else:
                            self._kb_listening = action
                        clicked_row = True
                        break
                if not clicked_row:
                    # Clicking elsewhere cancels listening
                    if self._kb_listening is not None:
                        # only cancel if not clicking the GUI elements — let GUI handle its own
                        pass
                    # Reset keybindings button
                    if (self._kb_reset_rect is not None
                            and self._kb_reset_rect.collidepoint(event.pos)):
                        self._on_reset_keybindings()

        # Pass events to GUI elements (sliders, selectors, buttons)
        self._gui.handle_events(events)
        self._gui.update(dt)
        self._gui_header.handle_events(events)
        self._gui_header.update(dt)

        if self._alert and not self._alert.done:
            self._alert.update(dt)

        result, self._pending = self._pending, None
        return result

    def draw(self, surface: pygame.Surface) -> None:
        if surface.get_size() != self._last_size:
            self._build(surface.get_size())

        sw, sh = surface.get_size()
        surface.fill(theme.BG)

        # Background dot grid
        step = 48
        for gx in range(0, sw, step):
            for gy in range(0, sh, step):
                pygame.draw.circle(surface, (18, 32, 52), (gx, gy), 1)

        # Draw scrollable content clipped below the header
        content_clip = pygame.Rect(0, self._HEADER_H, sw, sh - self._HEADER_H)
        surface.set_clip(content_clip)
        self._gui.draw(surface)
        self._draw_kb_rows(surface)
        self._draw_kb_reset_btn(surface)
        surface.set_clip(None)

        # Draw fixed header on top (covers any content that scrolled into header zone)
        self._gui_header.draw(surface)

        if self._alert and not self._alert.done:
            self._alert.draw(surface)
