"""
world_manager.py — World selection screen (Minecraft-style).

Lists all saved worlds sorted by last-played date.
The player can select a world, play it, delete it, or create a new one.

Returns action strings from update():
  "back"              → go back to main menu
  "new_world"         → open the new-world creation screen
  "play:<world_id>"   → start the game with the selected world
"""

from __future__ import annotations

from typing import Optional

import pygame

import os
import subprocess
import sys

from helpers.game_data import XP_THRESHOLDS
from helpers.gui import GUIHandler, Button, theme
from helpers.i18n import t
from helpers.icons import get_icon, ICO_BACK
from helpers.world_data import WorldMeta, list_worlds, TEX_BASE

_ROW_H = 92          # px — height of each world list row
_ROW_GAP = 6         # px — gap between rows


class _WorldRow:
    """
    A single row in the world list — draws itself from world metadata.
    Not a UIElement; managed directly by WorldManager.
    """

    _ICON_SZ = 70   # icon display size (square, with letterbox)

    def __init__(
        self,
        world: WorldMeta,
        x: int,
        y: int,
        w: int,
    ) -> None:
        self.world = world
        self.x = x
        self.y = y
        self.w = w
        self._hover = 0.0
        self._icon: Optional[pygame.Surface] = None
        self._icon_loaded = False

    @property
    def rect(self) -> pygame.Rect:
        return pygame.Rect(self.x, self.y, self.w, _ROW_H)

    def update(self, dt: float, selected: bool) -> None:
        target = 1.0 if selected or self.rect.collidepoint(
            pygame.mouse.get_pos()) else 0.0
        self._hover += (target - self._hover) * min(1.0, 10.0 * dt)

    def _load_icon(self) -> None:
        if self._icon_loaded:
            return
        self._icon_loaded = True
        info = self.world.icon_info
        path = TEX_BASE / info.get("path", "")
        try:
            raw = pygame.image.load(str(path)).convert_alpha()
            sz  = self._ICON_SZ
            # Letterbox-fit: scale preserving aspect ratio, center on background
            rw, rh = raw.get_size()
            scale  = min(sz / rw, sz / rh)
            nw, nh = max(1, int(rw * scale)), max(1, int(rh * scale))
            scaled = pygame.transform.scale(raw, (nw, nh))
            surf   = pygame.Surface((sz, sz), pygame.SRCALPHA)
            surf.blit(scaled, ((sz - nw) // 2, (sz - nh) // 2))
            self._icon = surf
        except Exception:
            self._icon = None

    def draw(self, surface: pygame.Surface, selected: bool) -> None:
        self._load_icon()
        r = self.rect

        # Background
        if selected:
            bg = (*theme.BTN_HOVER_BG, 230)
            bar = theme.ACCENT
        elif self._hover > 0.05:
            bg = (*theme.PANEL_LIGHT, int(180 * self._hover))
            bar = theme.BTN_HOVER_BAR
        else:
            bg = (*theme.PANEL, 160)
            bar = theme.BTN_NORMAL_BAR

        bg_surf = pygame.Surface((r.w, r.h), pygame.SRCALPHA)
        bg_surf.fill(bg)
        surface.blit(bg_surf, r.topleft)

        # Left accent bar
        pygame.draw.rect(surface, bar, pygame.Rect(
            r.x, r.y + 2, 4, r.h - 4), border_radius=2)

        # Border
        border = theme.BORDER_HOV if selected else theme.BORDER
        pygame.draw.rect(surface, border, r, width=1, border_radius=2)

        # World icon (left)
        icon_pad = 10
        icon_x   = r.x + icon_pad
        icon_y   = r.y + (r.h - self._ICON_SZ) // 2
        if self._icon:
            surface.blit(self._icon, (icon_x, icon_y))
        else:
            pygame.draw.rect(surface, (30, 50, 80),
                             (icon_x, icon_y, self._ICON_SZ, self._ICON_SZ))

        # Text block
        tx = icon_x + self._ICON_SZ + 12
        sub_col = theme.TEXT_DIM

        name_s = theme.font(20).render(self.world.name, True, theme.TEXT)
        surface.blit(name_s, (tx, r.y + 8))

        # Level / money row (only if world has been played)
        gd      = self.world.game_data or {}
        total_xp = float(gd.get("total_xp", 0.0))
        money   = float(gd.get("money", 0.0))
        if total_xp > 0 or money != 0:
            lv = 1
            for i, thresh in enumerate(XP_THRESHOLDS):
                if total_xp >= thresh:
                    lv = i + 1
            mc      = (80, 220, 120) if money >= 0 else (230, 80, 60)
            lv_part = theme.font(13).render(f"Level {lv}  ·  ", True, sub_col)
            money_s = theme.font(13).render(f"€{money:,.0f}", True, mc)
            surface.blit(lv_part,   (tx, r.y + 34))
            surface.blit(money_s,   (tx + lv_part.get_width(), r.y + 34))

        # Creation date + last played
        from datetime import datetime
        created_dt = datetime.fromtimestamp(self.world.created)
        created_str = created_dt.strftime("Created %d %b %Y")
        bottom_line = f"{self.world.last_played_label}  ·  {created_str}"
        sub_s = theme.font(12).render(bottom_line, True, sub_col)
        surface.blit(sub_s, (tx, r.y + r.h - sub_s.get_height() - 8))

        # Difficulty badge — right side
        diff = self.world.difficulty
        badge_col = (200, 80, 30) if diff == "hard" else (40, 140, 80)
        badge_text = f"  {t('nw_hard')}  " if diff == "hard" else f"  {t('nw_easy')}  "
        badge_s = theme.font(13).render(badge_text, True, badge_col)
        bx = r.right - badge_s.get_width() - 16
        by = r.y + 8
        badge_bg = pygame.Surface(
            (badge_s.get_width() + 2, badge_s.get_height() + 4), pygame.SRCALPHA)
        badge_bg.fill((*badge_col, 30))
        surface.blit(badge_bg, (bx - 1, by - 2))
        pygame.draw.rect(surface, (*badge_col, 120),
                         pygame.Rect(
                             bx - 1, by - 2, badge_s.get_width() + 2, badge_s.get_height() + 4),
                         width=1, border_radius=2)
        surface.blit(badge_s, (bx, by))


class WorldManager:
    """
    Scene: Minecraft-style world selection list.

    update() return values
    ----------------------
    "back"              Main menu
    "new_world"         Open new-world creation
    "play:<world_id>"   Launch game
    None                No transition
    """

    def __init__(self, screen: pygame.Surface) -> None:
        self._pending: Optional[str] = None
        self._selected: Optional[str] = None   # world_id
        self._scroll: int = 0                  # top-visible row index
        self._rows: list[_WorldRow] = []
        self._worlds: list[WorldMeta] = []
        self._last_click_time: float = 0.0
        self._last_click_id: str = ""

        self._gui = GUIHandler()
        self._last_size = (0, 0)
        # world_id awaiting confirm
        self._delete_confirm: Optional[str] = None
        self._build(screen.get_size())

    # ------------------------------------------------------------------
    # Build / reload
    # ------------------------------------------------------------------

    def _reload_worlds(self) -> None:
        self._worlds = list_worlds()

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

        self._reload_worlds()

        # Build row objects
        list_x = cx - 380
        list_w = 760
        # rows are positioned relative to scroll; actual y is computed in draw()

        self._rows = []
        for wm in self._worlds:
            row = _WorldRow(wm, list_x, 0, list_w)
            self._rows.append(row)

        # ---- GUI chrome ----
        self._gui.clear()

        # Header panel
        self._gui.panel(cx, 0, w, 62, color=theme.PANEL, alpha=220,
                        border_color=None, anchor="midtop")
        self._gui.panel(cx, 62, w, 2, color=theme.ACCENT, alpha=180,
                        border_color=None, border_width=0, anchor="midtop")

        # Title
        self._gui.label(cx, 31, t("wm_title"),
                        font_size=26, color=theme.TEXT, anchor="center")

        # Back button — top left
        self._gui.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, name="btn_back",
        )

        # Bottom action bar — 5 buttons: Play | Edit | Delete | Folder | New
        bar_y = h - 70
        self._gui.panel(cx, bar_y, w, 70, color=theme.PANEL, alpha=220,
                        border_color=None, anchor="midtop")
        self._gui.panel(cx, bar_y, w, 2, color=theme.BORDER, alpha=120,
                        border_color=None, border_width=0, anchor="midtop")

        self._gui.add(Button(
            cx - 370, bar_y + 10, 155, 46, t("wm_play"),
            font_size=18, anchor="midleft",
            on_click=self._on_play,
        ), name="btn_play")

        self._gui.add(Button(
            cx - 205, bar_y + 10, 145, 46, t("wm_edit"),
            font_size=18, anchor="midleft",
            on_click=self._on_edit,
        ), name="btn_edit")

        self._gui.add(Button(
            cx - 50, bar_y + 10, 140, 46, t("delete"),
            font_size=18, anchor="midleft",
            on_click=self._on_delete_request,
        ), name="btn_delete")

        self._gui.add(Button(
            cx + 100, bar_y + 10, 145, 46, t("wm_open_folder"),
            font_size=15, anchor="midleft",
            on_click=self._on_open_folder,
        ), name="btn_open_folder")

        self._gui.add(Button(
            cx + 255, bar_y + 10, 145, 46, t("wm_new"),
            font_size=18, anchor="midleft",
            on_click=self._on_new_world,
        ), name="btn_new")

        # Confirm-delete overlay (hidden by default)
        self._confirm_overlay_y = h // 2
        self._screen_w = w
        self._screen_h = h
        self._list_top = 72
        self._list_bot = bar_y - 8
        self._list_x = list_x
        self._list_w = list_w

    def _visible_rows(self) -> int:
        avail = self._list_bot - self._list_top
        return max(1, avail // (_ROW_H + _ROW_GAP))

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

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

    def _on_play(self) -> None:
        if not self._worlds:
            self._pending = "new_world"
        elif self._selected:
            self._pending = f"play:{self._selected}"

    def _on_edit(self) -> None:
        if self._selected:
            self._pending = f"edit_world:{self._selected}"

    def _on_open_folder(self) -> None:
        if not self._selected:
            return
        world = next(
            (w for w in self._worlds if w.world_id == self._selected), None)
        if world is None:
            return
        folder = world.world_dir
        folder.mkdir(parents=True, exist_ok=True)
        if sys.platform == "win32":
            os.startfile(str(folder))
        elif sys.platform == "darwin":
            subprocess.Popen(["open", str(folder)])
        else:
            subprocess.Popen(["xdg-open", str(folder)])

    def _on_delete_request(self) -> None:
        if self._selected:
            self._delete_confirm = self._selected

    def _confirm_delete(self) -> None:
        if self._delete_confirm:
            world = next((w for w in self._worlds if w.world_id ==
                         self._delete_confirm), None)
            if world:
                world.delete()
            self._delete_confirm = None
            self._selected = None
            self._build(self._last_size)

    def _cancel_delete(self) -> None:
        self._delete_confirm = None

    # ------------------------------------------------------------------
    # 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())

        for event in events:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                if self._delete_confirm:
                    self._cancel_delete()
                else:
                    self._pending = "back"

            # Scroll list with mouse wheel
            if event.type == pygame.MOUSEWHEEL and not self._delete_confirm:
                self._scroll = max(0, min(
                    len(self._worlds) - self._visible_rows(),
                    self._scroll - event.y,
                ))

            # Click on a row
            if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                if self._delete_confirm:
                    self.handle_confirm_click(event.pos)
                else:
                    import time as _time
                    now = _time.time()
                    for idx, row in enumerate(self._rows):
                        ry = self._list_top + \
                            (idx - self._scroll) * (_ROW_H + _ROW_GAP)
                        row.y = ry
                        test_r = pygame.Rect(
                            self._list_x, ry, self._list_w, _ROW_H)
                        if self._list_top <= ry and ry + _ROW_H <= self._list_bot:
                            if test_r.collidepoint(event.pos):
                                wid = self._worlds[idx].world_id
                                if (wid == self._last_click_id
                                        and now - self._last_click_time < 0.4):
                                    # Double-click: play immediately
                                    self._pending = f"play:{wid}"
                                else:
                                    self._selected = None if self._selected == wid else wid
                                self._last_click_time = now
                                self._last_click_id = wid

        # Update rows
        for i, row in enumerate(self._rows):
            selected = (self._worlds[i].world_id == self._selected)
            ry = self._list_top + (i - self._scroll) * (_ROW_H + _ROW_GAP)
            row.y = ry
            row.update(dt, selected)

        # Enable/disable action buttons based on selection
        has_sel = (self._selected is not None)
        for btn_name in ("btn_play", "btn_edit", "btn_delete", "btn_open_folder"):
            btn = self._gui.get(btn_name)
            if btn:
                btn.enabled = has_sel
        lbl_empty = self._gui.get("lbl_empty")
        if lbl_empty:
            lbl_empty.visible = (len(self._worlds) == 0)

        self._gui.handle_events(events)
        self._gui.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())

        surface.fill(theme.BG)

        # Draw subtle dot grid
        step = 48
        for gx in range(0, surface.get_width(), step):
            for gy in range(0, surface.get_height(), step):
                pygame.draw.circle(surface, (18, 32, 52), (gx, gy), 1)

        # World list rows (clipped between list_top and list_bot)
        clip_rect = pygame.Rect(
            0, self._list_top, self._screen_w, self._list_bot - self._list_top)
        old_clip = surface.get_clip()
        surface.set_clip(clip_rect)

        for i, row in enumerate(self._rows):
            ry = self._list_top + (i - self._scroll) * (_ROW_H + _ROW_GAP)
            row.y = ry
            row.x = self._list_x
            row.w = self._list_w
            if ry + _ROW_H < self._list_top or ry > self._list_bot:
                continue
            selected = (self._worlds[i].world_id == self._selected)
            row.draw(surface, selected)

        surface.set_clip(old_clip)

        # Scroll indicator
        if len(self._worlds) > self._visible_rows():
            total = len(self._worlds)
            vis = self._visible_rows()
            track_h = self._list_bot - self._list_top
            thumb_h = max(30, int(track_h * vis / total))
            thumb_y = self._list_top + \
                int((track_h - thumb_h) * self._scroll / max(1, total - vis))
            pygame.draw.rect(surface, theme.BORDER,
                             pygame.Rect(
                                 self._list_x + self._list_w + 6, thumb_y, 4, thumb_h),
                             border_radius=2)

        # GUI (header + bottom bar)
        self._gui.draw(surface)

        # Delete confirmation overlay
        if self._delete_confirm:
            world = next((w for w in self._worlds if w.world_id ==
                         self._delete_confirm), None)
            self._draw_confirm_overlay(surface, world)

    def _draw_confirm_overlay(self, surface: pygame.Surface, world: Optional[WorldMeta]) -> None:
        # Dim the background
        dim = pygame.Surface(surface.get_size(), pygame.SRCALPHA)
        dim.fill((0, 0, 0, 140))
        surface.blit(dim, (0, 0))

        cx, cy = self._screen_w // 2, self._screen_h // 2
        bw, bh = 460, 190
        box = pygame.Rect(cx - bw // 2, cy - bh // 2, bw, bh)

        box_surf = pygame.Surface((bw, bh), pygame.SRCALPHA)
        box_surf.fill((*theme.PANEL, 245))
        surface.blit(box_surf, box.topleft)
        pygame.draw.rect(surface, (180, 50, 30), box, width=2, border_radius=4)
        # Orange top edge
        pygame.draw.rect(surface, (180, 50, 30), pygame.Rect(
            box.x, box.y, bw, 3), border_radius=2)

        name = world.name if world else "this world"
        title_s = theme.font(22).render(
            t("wm_confirm_title"), True, (220, 80, 50))
        body_s = theme.font(16).render(
            t("wm_confirm_body", name=name), True, theme.TEXT_DIM)
        surface.blit(title_s, (cx - title_s.get_width() // 2, box.y + 22))
        surface.blit(body_s,  (cx - body_s.get_width() // 2, box.y + 64))

        # Yes / No buttons drawn manually (avoid adding to gui)
        yes_r = pygame.Rect(cx - 170, box.y + bh - 56, 150, 38)
        no_r = pygame.Rect(cx + 20,  box.y + bh - 56, 150, 38)

        mp = pygame.mouse.get_pos()
        for rect, label, col in [
            (yes_r, t("delete"), (160, 40, 30)),
            (no_r,  t("cancel"), theme.BTN_NORMAL_BAR),
        ]:
            hover = rect.collidepoint(mp)
            bg = (*col, 200 if hover else 140)
            bs = pygame.Surface((rect.w, rect.h), pygame.SRCALPHA)
            bs.fill(bg)
            surface.blit(bs, rect.topleft)
            pygame.draw.rect(surface, col, rect, width=1, border_radius=3)
            ls = theme.font(18).render(label, True, theme.TEXT)
            surface.blit(ls, (rect.centerx - ls.get_width() //
                         2, rect.centery - ls.get_height() // 2))

    def handle_confirm_click(self, pos: tuple[int, int]) -> None:
        """Call from update() when delete overlay is active."""
        cx, cy = self._screen_w // 2, self._screen_h // 2
        _bh = 190
        box_y = cy - _bh // 2
        yes_r = pygame.Rect(cx - 170, box_y + _bh - 56, 150, 38)
        no_r = pygame.Rect(cx + 20,  box_y + _bh - 56, 150, 38)
        if yes_r.collidepoint(pos):
            self._confirm_delete()
        elif no_r.collidepoint(pos):
            self._cancel_delete()
