#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import os
from dataclasses import dataclass
from typing import Dict, Tuple, List, Optional

import pygame
import pygame.surfarray as surfarray

from detector import IMX500Detector, FrameSnapshot
from steps import StepTransformer, build_step_text, build_gate_text, total_steps_for_level
from ui.theme import Theme
from ui.textlayout import TextLayout
from ui.renderer import Renderer


@dataclass(frozen=True)
class StepInfo:
    title: str
    body: str


class App:
    def __init__(self, args: argparse.Namespace):
        self.args = args

        # UI services
        self.theme = Theme()
        self.text_layout = TextLayout()
        self.renderer = Renderer(self.theme)

        # Pygame setup
        pygame.init()
        pygame.font.init()
        pygame.mixer.init()
        
        self.screen = self._set_fullscreen()
        self.win_w, self.win_h = self.screen.get_size()
        pygame.display.set_caption("IMX500 GUI")

        # Assets paths
        self.base_dir = os.path.dirname(os.path.abspath(__file__))
        self.landing_bg_path = "assets/landingpagebg.jpg"
        self.font_path = "assets/Kanit-Bold.ttf"
        self.audio_dir = os.path.join(self.base_dir, "assets", "audio")

        # Background caches
        self._landing_bg_original = None
        self._landing_bg_scaled = None
        self._landing_bg_scaled_size = None
        self._load_landing_bg()

        # Application State
        self.running = True
        self.state = "LIVEINTRO"  # LIVEINTRO / LANDING / RUNNING / GATE
        self.mode = "LIVE"        # LIVE / ANALYSE
        self.step = 0             # 0=LIVE, 1..N=Analyse
        self.clock = pygame.time.Clock()

        # User Configuration
        self.lang = "DE"
        self.level = None  # "SCHUELER" / "STUDENT"

        # Logic Components
        self.detector: IMX500Detector | None = None
        self.transformer: StepTransformer | None = None
        self.snapshot: FrameSnapshot | None = None

        # Simulation / Rendering Caches
        self.sim_surface = None
        self.sim_step_cached: int | None = None

        # Gate Text Caches
        self.gate_cached_step: int | None = None
        self.gate_cached_title_font = None
        self.gate_cached_body_font = None
        self.gate_cached_title_lines = None
        self.gate_cached_body_lines = None

        # Gate Animation
        self.gate_anim_frames: list[pygame.Surface] | None = None
        self.gate_anim_key: str | None = None
        self.gate_anim_idx: int = 0
        self.gate_anim_last_ms: int = 0
        self.gate_anim_frame_ms: int = 55

        # Hitboxes (General)
        self.lang_button_rect = pygame.Rect(0, 0, 0, 0)
        self.home_button_rect = pygame.Rect(0, 0, 0, 0)
        self.level_left_rect = pygame.Rect(0, 0, 0, 0)
        self.level_right_rect = pygame.Rect(0, 0, 0, 0)
        self.cta_button_rect = pygame.Rect(0, 0, 0, 0)
        
        # Hitboxes (Gate)
        self.gate_prev_rect = pygame.Rect(0, 0, 0, 0)
        self.gate_next_rect = pygame.Rect(0, 0, 0, 0)
        
        # Hitboxes (Navigation/Audio)
        self.nav_prev_rect = pygame.Rect(0, 0, 0, 0)
        self.nav_next_rect = pygame.Rect(0, 0, 0, 0)
        self.nav_action_rect = pygame.Rect(0, 0, 0, 0)
        self.nav_audio_rect = pygame.Rect(0, 0, 0, 0)
        
        # Audio State
        self.current_audio_file: str | None = None
        self.audio_is_paused = False

        # Gate Logic
        self.gate_next_step: int | None = None

        # Text Resources
        self.UI = {
            "DE": {
                "landing_title": "KI OBJEKTERKENNUNG",
                "landing_sub": "wähle hier dein Niveau",
                "level_left": "Schüler:in",
                "level_right": "Student:in",
                "hint": "ESC/q zum Beenden",
                "live_hint": "SPACE: Analyse",
                "quit_hint": "ESC/q: Quit",
                "analyse_hint": "ENTER next | BACKSPACE prev | SPACE end",
                "workflow": "Workflow",
                "workflow_hint": "SPACE friert ein & wechselt zu Analyse.",
                "lang_label": "DE",
                "liveintro_cta": "Was erkennt die KI? / What does the AI detect?",
                "home": "Home",
                "gate_btn": "Weiter",
                "back": "Zurück"
            },
            "EN": {
                "landing_title": "AI OBJECT DETECTION",
                "landing_sub": "choose your level",
                "level_left": "Pupil",
                "level_right": "Student",
                "hint": "ESC/q to quit",
                "live_hint": "SPACE: Analyse",
                "quit_hint": "ESC/q: Quit",
                "analyse_hint": "ENTER next | BACKSPACE prev | SPACE end",
                "workflow": "Workflow",
                "workflow_hint": "SPACE freezes & switches to Analyse.",
                "lang_label": "EN",
                "liveintro_cta": "Was erkennt die KI? / What does the AI detect?",
                "home": "Home",
                "gate_btn": "Continue",
                "back": "Back"
            },
        }

        self._recompute_responsive()

    # ---------------- Responsive System ----------------
    def _scale(self) -> float:
        s_w = self.win_w / 1920.0
        s_h = self.win_h / 1080.0
        return max(0.6, min(1.6, (s_w + s_h) * 0.5))

    def _fsize(self, px: float) -> int:
        return max(12, int(px * self._scale()))

    def _load_font(self, size: int) -> pygame.font.Font:
        try:
            return pygame.font.Font(self.font_path, size)
        except Exception:
            return pygame.font.Font(None, size)

    def _recompute_responsive(self) -> None:
        self.win_w, self.win_h = self.screen.get_size()

        self.font_ui = self._load_font(self._fsize(22))
        self.font_small = self._load_font(self._fsize(20))
        self.font_header = self._load_font(self._fsize(28))
        self.font_title = self._load_font(self._fsize(38))
        self.font_landing_title = self._load_font(self._fsize(86))
        self.font_landing_sub = self._load_font(self._fsize(44))
        self.font_landing_btn = self._load_font(self._fsize(44))

        self.pad = max(12, int(18 * self._scale()))
        
        # Button sizes
        self.lang_btn_w = max(72, int(92 * self._scale()))
        self.lang_btn_h = max(36, int(44 * self._scale()))
        self.home_btn_w = max(int(96 * self._scale()), 80)
        self.home_btn_h = max(int(44 * self._scale()), 34)
        
        self.level_radius = max(18, int(28 * self._scale()))
        self.level_border_w = max(2, int(4 * self._scale()))

        self._landing_bg_scaled = None
        self._landing_bg_scaled_size = None

    # ---------------- Helpers ----------------
    def _t(self, key: str) -> str:
        return self.UI[self.lang][key]

    def _total_steps(self) -> int:
        return total_steps_for_level(self.level or "SCHUELER")

    def _step_map(self) -> Dict[int, object]:
        assert self.snapshot is not None
        assert self.level is not None
        return build_step_text(lang=self.lang, level=self.level, debug=self.snapshot.debug)

    def _gate_map(self) -> Dict[int, object]:
        assert self.snapshot is not None
        assert self.level is not None
        return build_gate_text(lang=self.lang, level=self.level, debug=self.snapshot.debug)

    def _set_fullscreen(self) -> pygame.Surface:
        info = pygame.display.Info()
        return pygame.display.set_mode((info.current_w, info.current_h), pygame.FULLSCREEN)

    def _invalidate_caches(self) -> None:
        self.sim_step_cached = None
        self.gate_cached_step = None
        self._stop_audio()

    def _ensure_camera(self) -> None:
        if self.detector is None:
            self.detector = IMX500Detector(self.args)
        if self.snapshot is None:
            self.snapshot = FrameSnapshot(src_size=(self.args.cam_width, self.args.cam_height))

    def _go_home(self) -> None:
        self.state = "LIVEINTRO"
        self.mode = "LIVE"
        self.step = 0
        self.level = None
        self.gate_next_step = None
        self._invalidate_caches()

    def _is_student(self) -> bool:
        return self.level == "STUDENT"

    def _load_landing_bg(self) -> None:
        try:
            path = os.path.join(self.base_dir, self.landing_bg_path)
            self._landing_bg_original = pygame.image.load(path).convert()
        except Exception:
            self._landing_bg_original = None
        self._landing_bg_scaled = None
        self._landing_bg_scaled_size = None

    def _get_landing_bg_scaled(self) -> pygame.Surface | None:
        if self._landing_bg_original is None:
            return None
        size = (self.win_w, self.win_h)
        if self._landing_bg_scaled is None or self._landing_bg_scaled_size != size:
            self._landing_bg_scaled = pygame.transform.smoothscale(self._landing_bg_original, size)
            self._landing_bg_scaled_size = size
        return self._landing_bg_scaled

    # ---------------- Audio Logic ----------------
    def _get_audio_filename(self) -> str | None:
        """Ermittelt den Dateinamen basierend auf Level und Step."""
        # STRIKTE REGEL: Audio NUR fuer SCHUELER
        if self.level != "SCHUELER":
            return None
            
        if self.lang == "EN":
             filename = f"schueler_step_{self.step}_english.mp3"
        else:
             filename = f"schueler_step_{self.step}.mp3"

        path = os.path.join(self.audio_dir, filename)
        if os.path.exists(path):
            return path
        return None

    def _toggle_audio(self) -> None:
        if self.audio_is_paused:
            pygame.mixer.music.unpause()
            self.audio_is_paused = False
            return

        if pygame.mixer.music.get_busy():
            pygame.mixer.music.pause()
            self.audio_is_paused = True
            return

        path = self._get_audio_filename()
        if path:
            try:
                pygame.mixer.music.load(path)
                pygame.mixer.music.play()
                self.current_audio_file = path
                self.audio_is_paused = False
            except Exception as e:
                print(f"Audio error: {e}")

    def _stop_audio(self) -> None:
        pygame.mixer.music.stop()
        self.audio_is_paused = False
        self.current_audio_file = None

    # ---------------- Layout Calculation ----------------
    def make_layout(self) -> Tuple[pygame.Rect, pygame.Rect, pygame.Rect, Optional[pygame.Rect]]:
        pad = self.pad
        
        # 1. Panel Decision
        show_panel = False
        if self.mode == "ANALYSE":
            if self.level == "SCHUELER":
                show_panel = (self.step >= 3)
            else:
                show_panel = (self.step >= 6)
        
        # 2. Vertical Grid
        title_h = max(60, int(80 * self._scale()))
        nav_h = max(80, int(100 * self._scale()))
        available_h = self.win_h - title_h - nav_h - 4 * pad
        y_title = pad
        y_content = y_title + title_h + pad
        y_nav = y_content + available_h + pad

        # 3. Horizontal Grid
        # Change: Force 1:1 Aspect Ratio (Square) ONLY for Student Steps 2-6
        target_ar = 16 / 9
        if self.level == "STUDENT" and (2 <= self.step <= 6):
            target_ar = 1.0  # 9:9 Aspect Ratio (Square)

        if show_panel:
            panel_w = max(int(350 * self._scale()), int(self.win_w * 0.28))
            max_video_w = self.win_w - panel_w - 3 * pad
            video_w = int(available_h * target_ar)
            if video_w > max_video_w:
                video_w = max_video_w
            
            video_rect = pygame.Rect(pad, y_content, video_w, available_h)
            panel_x = video_rect.right + pad
            panel_w_actual = self.win_w - panel_x - pad
            panel_rect = pygame.Rect(panel_x, y_content, panel_w_actual, available_h)
        else:
            panel_rect = None
            max_video_w = self.win_w - 2 * pad
            video_w = int(available_h * target_ar)
            if video_w < max_video_w:
                x_video = pad + (max_video_w - video_w) // 2
            else:
                x_video = pad
                video_w = max_video_w
            video_rect = pygame.Rect(x_video, y_content, video_w, available_h)

        title_rect = pygame.Rect(pad, y_title, self.win_w - 2*pad, title_h)
        nav_rect = pygame.Rect(video_rect.x, y_nav, video_rect.w, nav_h)
        
        return video_rect, title_rect, nav_rect, panel_rect

    # ---------------- UI Drawing ----------------

    def _draw_title_area(self, rect: pygame.Rect) -> None:
        if self.mode == "LIVE":
            text = "Live Workflow"
        else:
            step_map = self._step_map()
            info = step_map.get(self.step)
            text = info.title if info else f"Schritt {self.step}"
        self.renderer.draw_text(self.screen, self.font_title, text, (rect.x, rect.centery - self.font_title.get_height()//2), self.theme.TEXT)

    def _draw_step_indicator_global(self) -> None:
        buttons_w = self.home_btn_w + self.lang_btn_w + 3 * self.pad
        ind_w = int(self.win_w * 0.25)
        ind_h = int(26 * self._scale())
        gap = int(50 * self._scale())
        x = self.win_w - buttons_w - ind_w - gap
        title_h = max(60, int(80 * self._scale()))
        y = self.pad + (title_h - ind_h) // 2
        
        rect = pygame.Rect(x, y, ind_w, ind_h)
        self.renderer.draw_step_indicator(self.screen, rect, step=self.step, total_steps=self._total_steps(), font_small=self.font_small)

    def _draw_navigation_area(self, rect: pygame.Rect) -> None:
        self.nav_prev_rect = pygame.Rect(0,0,0,0)
        self.nav_next_rect = pygame.Rect(0,0,0,0)
        self.nav_action_rect = pygame.Rect(0,0,0,0)
        self.nav_audio_rect = pygame.Rect(0,0,0,0)
        
        if self.mode == "LIVE":
            btn_w = min(rect.w, int(600 * self._scale()))
            btn_h = rect.h
            btn_x = rect.centerx - btn_w // 2
            self.nav_action_rect = pygame.Rect(btn_x, rect.y, btn_w, btn_h)
            lbl = "Analyse starten" if self.lang == "DE" else "Start Analysis"
            self.renderer.draw_button(self.screen, self.nav_action_rect, lbl, self.font_ui, primary=True)
            
        else:
            # Audio Check
            audio_path = self._get_audio_filename()
            has_audio = (audio_path is not None)
            
            gap = int(20 * self._scale())
            
            if has_audio:
                btn_w = (rect.w - 2 * gap) // 3
                btn_h = rect.h
                
                self.nav_prev_rect = pygame.Rect(rect.x, rect.y, btn_w, btn_h)
                self.nav_audio_rect = pygame.Rect(rect.x + btn_w + gap, rect.y, btn_w, btn_h)
                self.nav_next_rect = pygame.Rect(rect.x + 2*btn_w + 2*gap, rect.y, btn_w, btn_h)
                
                is_playing = pygame.mixer.music.get_busy() and not self.audio_is_paused
                audio_lbl = "Audio ||" if is_playing else "Audio ▶"
                
                self.renderer.draw_button(self.screen, self.nav_audio_rect, audio_lbl, self.font_ui, primary=True)
                
            else:
                btn_w = (rect.w - gap) // 2
                btn_h = rect.h
                self.nav_prev_rect = pygame.Rect(rect.x, rect.y, btn_w, btn_h)
                self.nav_next_rect = pygame.Rect(rect.x + btn_w + gap, rect.y, btn_w, btn_h)
            
            lbl_prev = "Zurück" if self.lang == "DE" else "Back"
            is_last = self.step >= self._total_steps()
            lbl_next = ("Beenden" if self.lang == "DE" else "Finish") if is_last else ("Weiter" if self.lang == "DE" else "Next")
            
            self.renderer.draw_button(self.screen, self.nav_prev_rect, lbl_prev, self.font_ui, primary=False)
            self.renderer.draw_button(self.screen, self.nav_next_rect, lbl_next, self.font_ui, primary=True)

    def _draw_right_panel_content(self, panel_rect: pygame.Rect) -> None:
        self.renderer.draw_card(self.screen, panel_rect, fill=self.theme.PANEL_2, outline=self.theme.LINE, radius=self.theme.RADIUS)
        header_h = int(60 * self._scale())
        self.renderer.draw_text(self.screen, self.font_header, "Ergebnisse", (panel_rect.x + 20, panel_rect.y + 20), self.theme.TEXT)
        content_rect = pygame.Rect(
            panel_rect.x + 14, 
            panel_rect.y + header_h, 
            panel_rect.w - 28, 
            panel_rect.h - header_h - 14
        )
        chart_font = self._load_font(self._fsize(26))
        self.renderer.draw_bar_chart(self.screen, content_rect, self.snapshot.top3, self.args.threshold, chart_font, self.font_ui)

    def _draw_home_button(self) -> None:
        pad = self.pad
        w, h = self.home_btn_w, self.home_btn_h
        self.home_button_rect = pygame.Rect(self.win_w - w - pad, pad, w, h)
        self.renderer.draw_button(self.screen, self.home_button_rect, self._t("home"), self.font_ui, primary=False)

    def _draw_lang_button(self) -> None:
        w, h = self.lang_btn_w, self.lang_btn_h
        pad = self.pad
        x = self.win_w - self.home_btn_w - 2 * pad - w
        self.lang_button_rect = pygame.Rect(x, pad, w, h)
        self.renderer.draw_card(self.screen, self.lang_button_rect, fill=self.theme.PANEL_2, outline=self.theme.LINE, radius=max(10, int(12 * self._scale())))
        pygame.draw.rect(self.screen, self.theme.ACCENT, self.lang_button_rect, width=max(1, int(2 * self._scale())), border_radius=max(10, int(12 * self._scale())))
        txt = self.font_ui.render(self._t("lang_label"), True, self.theme.TEXT)
        self.screen.blit(txt, txt.get_rect(center=self.lang_button_rect.center))

    def _toggle_lang(self) -> None:
        self.lang = "EN" if self.lang == "DE" else "DE"
        self._invalidate_caches()

    # ---------------- Live Intro / Landing / Gate ----------------
    def _liveintro_button_layout(self) -> None:
        btn_w = int(self.win_w * 0.60)
        btn_w = max(int(520 * self._scale()), min(btn_w, int(1400 * self._scale())))
        btn_h = max(int(90 * self._scale()), int(self.win_h * 0.11))
        btn_x = (self.win_w - btn_w) // 2
        btn_y = int(self.win_h * 0.72)
        self.cta_button_rect = pygame.Rect(btn_x, btn_y, btn_w, btn_h)

    def _draw_liveintro(self) -> None:
        self._recompute_responsive()
        self.screen.fill(self.theme.BG)
        if self.snapshot is not None and self.snapshot.frame_rgb is not None:
            surf = surfarray.make_surface(self.snapshot.frame_rgb.swapaxes(0, 1))
            self.screen.blit(pygame.transform.smoothscale(surf, (self.win_w, self.win_h)), (0, 0))
            if self.snapshot.dets:
                for d in self.snapshot.dets:
                    r = self.renderer.rect_in_video_coords(d.box, self.snapshot.src_size, pygame.Rect(0, 0, self.win_w, self.win_h))
                    pygame.draw.rect(self.screen, self.renderer.conf_color(d.conf), r, width=2)
        self._liveintro_button_layout()
        self.renderer.draw_button(self.screen, self.cta_button_rect, self._t("liveintro_cta"), self.font_landing_btn, primary=True)
        pygame.display.flip()
        self.clock.tick(30)

    def _landing_layout(self) -> None:
        bw = int(self.win_w * 0.30)
        bh = int(self.win_h * 0.18)
        bw = max(int(300 * self._scale()), min(bw, int(720 * self._scale())))
        bh = max(int(120 * self._scale()), min(bh, int(240 * self._scale())))
        y = int(self.win_h * 0.62)
        gap = max(int(self.win_w * 0.06), int(100 * self._scale()))
        left_x = (self.win_w // 2) - (gap // 2) - bw
        right_x = (self.win_w // 2) + (gap // 2)
        self.level_left_rect = pygame.Rect(left_x, y, bw, bh)
        self.level_right_rect = pygame.Rect(right_x, y, bw, bh)

    def _draw_level_button(self, rect: pygame.Rect, label: str) -> None:
        self.renderer.draw_button(self.screen, rect, label, self.font_landing_btn, primary=True, border_width=self.level_border_w)

    def _draw_landing(self) -> None:
        self._recompute_responsive()
        bg = self._get_landing_bg_scaled()
        if bg is not None: self.screen.blit(bg, (0, 0))
        else: self.screen.fill((7, 14, 26))
        overlay = pygame.Surface((self.win_w, self.win_h), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 70))
        self.screen.blit(overlay, (0, 0))
        self._landing_layout()
        col = self.theme.BTN_PRIMARY_BORDER
        title_surf = self.font_landing_title.render(self._t("landing_title"), True, col)
        self.screen.blit(title_surf, title_surf.get_rect(center=(self.win_w // 2, int(self.win_h * 0.22))))
        sub_surf = self.font_landing_sub.render(self._t("landing_sub"), True, col)
        self.screen.blit(sub_surf, sub_surf.get_rect(center=(self.win_w // 2, int(self.win_h * 0.36))))
        self._draw_level_button(self.level_left_rect, self._t("level_left"))
        self._draw_level_button(self.level_right_rect, self._t("level_right"))
        hint_surf = self.font_ui.render(self._t("hint"), True, col)
        self.screen.blit(hint_surf, hint_surf.get_rect(center=(self.win_w // 2, int(self.win_h * 0.92))))
        self._draw_home_button()
        self._draw_lang_button()
        pygame.display.flip()
        self.clock.tick(30)

    def _start_detection(self, level: str) -> None:
        self.level = level
        self._ensure_camera()
        if self.transformer is None: self.transformer = StepTransformer()
        self.mode = "LIVE"
        self.step = 0
        self.gate_next_step = None
        self._invalidate_caches()
        self.state = "RUNNING"

    def _gate_layout(self) -> None:
        btn_w_total = int(self.win_w * 0.35)
        btn_w_total = max(int(320 * self._scale()), min(btn_w_total, int(800 * self._scale())))
        btn_h = max(int(90 * self._scale()), int(self.win_h * 0.11))
        
        y = int(self.win_h * 0.80)
        gap = int(20 * self._scale())
        
        # Two buttons side by side
        single_btn_w = (btn_w_total - gap) // 2
        
        start_x = (self.win_w - btn_w_total) // 2
        
        self.gate_prev_rect = pygame.Rect(start_x, y, single_btn_w, btn_h)
        self.gate_next_rect = pygame.Rect(start_x + single_btn_w + gap, y, single_btn_w, btn_h)

    def _enter_gate_for_step(self, target_step: int) -> None:
        self.gate_next_step = target_step
        self.state = "GATE"
        self._invalidate_caches()

    def _accept_gate(self) -> None:
        if self.gate_next_step is None: self.state = "RUNNING"; return
        self.mode = "ANALYSE"
        self.step = self.gate_next_step
        self.gate_next_step = None
        self.state = "RUNNING"
        self._invalidate_caches()

    def _gate_back(self) -> None:
        # Wenn wir bei Step 1 sind, geht es zurück zu LIVE
        if self.gate_next_step == 1:
            self.mode = "LIVE"
            self.step = 0
            self.state = "RUNNING"
            self.gate_next_step = None
            self._invalidate_caches()
        else:
            # Ansonsten zurück zum vorherigen Analyse-Schritt (ohne Gate)
            target = (self.gate_next_step or 2) - 1
            self.mode = "ANALYSE"
            self.step = target
            self.state = "RUNNING"
            self.gate_next_step = None
            self._invalidate_caches()

    def _load_gate_animation_frames(self, step_n: int) -> None:
        sequences = {
            1: dict(folder="schritt_1_experte", prefix="schritt1experte", start=1, end=62),
            2: dict(folder="schritt_2_experte", prefix="schritt2experte", start=1, end=101),
            3: dict(folder="schritt_3_experte", prefix="schritt3experte", start=1, end=48),
            4: dict(folder="schritt_4_experte", prefix="schritt4experte", start=1, end=78),
            5: dict(folder="schritt_5_experte", prefix="schritt5experte", start=1, end=29),
            6: dict(folder="schritt_6_experte", prefix="schritt6experte", start=1, end=68),
            7: dict(folder="schritt_7_experte", prefix="schritt7experte", start=1, end=80),
        }
        seq = sequences.get(step_n)
        if seq is None:
            self.gate_anim_frames = None; self.gate_anim_key = None; return
        folder = os.path.join(self.base_dir, "assets", seq["folder"])
        prefix = seq["prefix"]; start = seq["start"]; end = seq["end"]
        key = f"{folder}:{prefix}:{start}:{end}"
        if self.gate_anim_key == key and self.gate_anim_frames is not None: return
        frames = []
        for i in range(start, end + 1):
            path = os.path.join(folder, f"{prefix}{i}.jpg")
            try: 
                frames.append(pygame.image.load(path).convert())
            except Exception as e:
                if i == start: 
                    print(f"DEBUG: Konnte {path} nicht laden. Grund: {e}")
                    
        self.gate_anim_frames = frames if frames else None
        self.gate_anim_key = key
        self.gate_anim_idx = 0
        self.gate_anim_last_ms = pygame.time.get_ticks()

    def _draw_gate(self) -> None:
        self._recompute_responsive()
        bg = self._get_landing_bg_scaled()
        if bg is not None: self.screen.blit(bg, (0, 0))
        else: self.screen.fill((0, 0, 0))
        overlay = pygame.Surface((self.win_w, self.win_h), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 85))
        self.screen.blit(overlay, (0, 0))
        self._gate_layout()

        assert self.snapshot is not None
        gate_map = self._gate_map()
        step_n = self.gate_next_step or 1
        info = gate_map.get(step_n)
        if info is None: info = StepInfo(title=f"Zwischenschritt {step_n}", body="")

        outer = pygame.Rect(int(self.win_w * 0.06), int(self.win_h * 0.10), int(self.win_w * 0.88), int(self.win_h * 0.62))
        gap = max(12, int(18 * self._scale()))
        text_w = int(outer.w * (2 / 3)) - gap // 2
        anim_w = outer.w - text_w - gap
        text_rect = pygame.Rect(outer.x, outer.y, text_w, outer.h)
        anim_rect = pygame.Rect(text_rect.right + gap, outer.y, anim_w, outer.h)
        target_w, target_h = 720, 405
        anim_frame_rect = pygame.Rect(0, 0, target_w, target_h)
        anim_frame_rect.center = anim_rect.center
        if anim_frame_rect.w > anim_rect.w or anim_frame_rect.h > anim_rect.h:
            scale = min(anim_rect.w / target_w, anim_rect.h / target_h)
            anim_frame_rect.size = (max(1, int(target_w * scale)), max(1, int(target_h * scale)))
            anim_frame_rect.center = anim_rect.center

        if self.gate_cached_step != step_n:
            title, body = self.text_layout.split_title_body(info.title, info.body)
            self.gate_cached_title_font, self.gate_cached_body_font = self.text_layout.fit_title_and_body(
                title, body, text_rect, min_body=self._fsize(18), max_body=self._fsize(34),
                title_ratio=1.25, line_spacing=max(2, int(4 * self._scale())),
            )
            self.gate_cached_title_lines = self.text_layout.wrap_lines(title, self.gate_cached_title_font, text_rect.w)
            self.gate_cached_body_lines = self.text_layout.wrap_lines(body, self.gate_cached_body_font, text_rect.w) if body else []
            self.gate_cached_step = step_n

        y = text_rect.y
        y = self.text_layout.draw_wrapped_lines(self.screen, self.gate_cached_title_lines, self.gate_cached_title_font, (235, 235, 235), text_rect.x, y, line_spacing=max(2, int(6 * self._scale())))
        if self.gate_cached_body_lines:
            y += self.gate_cached_body_font.get_linesize()
            self.text_layout.draw_wrapped_lines(self.screen, self.gate_cached_body_lines, self.gate_cached_body_font, (200, 200, 200), text_rect.x, y, line_spacing=max(2, int(6 * self._scale())))

        self._load_gate_animation_frames(step_n)
        self.renderer.draw_card(self.screen, anim_frame_rect, fill=(10, 10, 10), outline=(60, 60, 60), radius=max(12, int(16 * self._scale())))
        if self.gate_anim_frames:
            now = pygame.time.get_ticks()
            if now - self.gate_anim_last_ms >= self.gate_anim_frame_ms:
                self.gate_anim_idx = (self.gate_anim_idx + 1) % len(self.gate_anim_frames)
                self.gate_anim_last_ms = now
            frame = self.gate_anim_frames[self.gate_anim_idx]
            new_size = (max(1, int(frame.get_width() * min(anim_frame_rect.w / frame.get_width(), anim_frame_rect.h / frame.get_height()))),
                        max(1, int(frame.get_height() * min(anim_frame_rect.w / frame.get_width(), anim_frame_rect.h / frame.get_height()))))
            frame_s = pygame.transform.smoothscale(frame, new_size)
            self.screen.blit(frame_s, frame_s.get_rect(center=anim_frame_rect.center).topleft)

        # Draw Buttons
        self.renderer.draw_button(self.screen, self.gate_prev_rect, self._t("back"), self.font_landing_btn, primary=False)
        self.renderer.draw_button(self.screen, self.gate_next_rect, self._t("gate_btn"), self.font_landing_btn, primary=True)
        
        self._draw_home_button()
        self._draw_lang_button()
        pygame.display.flip()
        self.clock.tick(30)

    # ---------------- Events ----------------
    def handle_events(self) -> None:
        for event in pygame.event.get():
            if event.type == pygame.QUIT: self.running = False
            elif event.type == pygame.KEYDOWN:
                if event.key in (pygame.K_q, pygame.K_ESCAPE): self.running = False
                if self.state == "GATE":
                    if event.key == pygame.K_RETURN: self._accept_gate(); return
                    if event.key == pygame.K_BACKSPACE: self._gate_back(); return
                if self.state == "RUNNING":
                    if event.key == pygame.K_SPACE:
                        if self.mode == "LIVE":
                            if self._is_student(): self._enter_gate_for_step(1)
                            else: self.mode, self.step = "ANALYSE", 1; self._invalidate_caches()
                        else: self.mode, self.step = "LIVE", 0; self._invalidate_caches()
                    elif self.mode == "ANALYSE":
                        if event.key == pygame.K_RETURN:
                            nxt = min(self._total_steps(), self.step + 1)
                            if nxt != self.step:
                                if self._is_student(): self._enter_gate_for_step(nxt)
                                else: self.step = nxt; self._invalidate_caches()
                        elif event.key == pygame.K_BACKSPACE:
                            if self._is_student():
                                # CHANGE: Im Student-Mode geht BACKSPACE jetzt zum Gate des AKTUELLEN Steps zurück
                                self._enter_gate_for_step(self.step)
                            else:
                                prv = max(1, self.step - 1)
                                if prv != self.step:
                                    self.step = prv; self._invalidate_caches()
            elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                if self.state == "LIVEINTRO":
                    if self.cta_button_rect.collidepoint(event.pos): self.state = "LANDING"; return
                if self.state in ("LANDING", "RUNNING", "GATE"):
                    if self.home_button_rect.collidepoint(event.pos): self._go_home(); return
                    if self.lang_button_rect.collidepoint(event.pos): self._toggle_lang(); return
                if self.state == "LANDING":
                    if self.level_left_rect.collidepoint(event.pos): self._start_detection("SCHUELER"); return
                    if self.level_right_rect.collidepoint(event.pos): self._start_detection("STUDENT"); return
                if self.state == "GATE":
                    if self.gate_next_rect.collidepoint(event.pos): self._accept_gate(); return
                    if self.gate_prev_rect.collidepoint(event.pos): self._gate_back(); return
                if self.state == "RUNNING":
                    # Audio Button Check
                    if self.nav_audio_rect.collidepoint(event.pos):
                        self._toggle_audio()
                        return
                    
                    if self.mode == "LIVE":
                        if self.nav_action_rect.collidepoint(event.pos):
                            if self._is_student(): self._enter_gate_for_step(1)
                            else: self.mode, self.step = "ANALYSE", 1; self._invalidate_caches()
                    elif self.mode == "ANALYSE":
                        if self.nav_next_rect.collidepoint(event.pos):
                            if self.step == self._total_steps():
                                self.mode, self.step = "LIVE", 0; self._invalidate_caches()
                            else:
                                nxt = min(self._total_steps(), self.step + 1)
                                if nxt != self.step:
                                    if self._is_student(): self._enter_gate_for_step(nxt)
                                    else: self.step = nxt; self._invalidate_caches()
                        if self.nav_prev_rect.collidepoint(event.pos):
                            if self._is_student():
                                # CHANGE: Im Student-Mode geht der ZURÜCK-Button jetzt zum Gate des AKTUELLEN Steps zurück
                                self._enter_gate_for_step(self.step)
                            else:
                                prv = max(1, self.step - 1)
                                if prv != self.step:
                                    self.step = prv; self._invalidate_caches()

    def update(self) -> None:
        if self.state == "LIVEINTRO":
            self._ensure_camera()
            self.snapshot = self.detector.capture_snapshot()
        elif self.state == "RUNNING" and self.mode == "LIVE":
            self.snapshot = self.detector.capture_snapshot()

    # ---------------- Left overlays helpers ----------------
    def _draw_hud_lines(self, video_rect: pygame.Rect, lines: List[str]) -> None:
        x, y = video_rect.x + self.pad, video_rect.y + self.pad
        for line in lines:
            self.renderer.draw_pill(self.screen, self.font_small, line, (x, y), fg=self.theme.TEXT)
            y += self.font_small.get_linesize() + 6

    def _draw_roi_overlay(self, video_rect: pygame.Rect) -> None:
        if not self.snapshot or not self.snapshot.debug.get("roi"): return
        r = self.renderer.rect_in_video_coords(self.snapshot.debug["roi"], self.snapshot.src_size, video_rect)
        pygame.draw.rect(self.screen, (80, 160, 255), r, width=2)

    def _draw_det_list(self, video_rect: pygame.Rect, dets, color=None, draw_label=False) -> None:
        if not self.snapshot: return
        for d in dets:
            r = self.renderer.rect_in_video_coords(d.box, self.snapshot.src_size, video_rect)
            col = color if color else self.renderer.conf_color(d.conf)
            pygame.draw.rect(self.screen, col, r, width=2)
            if draw_label:
                label = f"{d.label} {d.conf*100:.0f}%"
                txt = self.font_ui.render(label, True, self.theme.TEXT)
                pill = pygame.Surface((txt.get_width()+16, txt.get_height()+10), pygame.SRCALPHA)
                pill.fill((15, 16, 20, 180))
                self.screen.blit(pill, (r.x, max(video_rect.y+6, r.y-txt.get_height()-16)))
                self.screen.blit(txt, (r.x+8, max(video_rect.y+10, r.y-txt.get_height()-12)))

    def _draw_pixel_inspector(self, video_rect: pygame.Rect) -> None:
        if self.step != 1: return

        mx, my = pygame.mouse.get_pos()
        if not video_rect.collidepoint(mx, my): return

        # Hole die Farbe direkt vom Bildschirm (genau das Pixel unter der Maus)
        try:
            col = self.screen.get_at((mx, my))
        except Exception:
            return

        r, g, b, _ = col

        # Tooltip Text
        text = f"R:{r} G:{g} B:{b}"
        font = self.font_ui
        txt_surf = font.render(text, True, (255, 255, 255)) # Weißer Text
        
        # Positionierung (etwas versetzt, damit die Maus nicht verdeckt wird)
        tip_x = mx + 20
        tip_y = my + 20
        
        # Am Bildschirmrand? Nach links/oben schieben
        if tip_x + txt_surf.get_width() > self.win_w:
            tip_x = mx - txt_surf.get_width() - 15
        if tip_y + txt_surf.get_height() > self.win_h:
            tip_y = my - txt_surf.get_height() - 15
        
        bg_rect = pygame.Rect(tip_x - 6, tip_y - 6, txt_surf.get_width() + 12, txt_surf.get_height() + 12)
        
        # Zeichnen
        pygame.draw.rect(self.screen, (20, 20, 20), bg_rect, border_radius=6) # Dunkler Hintergrund
        pygame.draw.rect(self.screen, (r, g, b), bg_rect, width=2, border_radius=6) # Rahmen in Pixelfarbe
        self.screen.blit(txt_surf, (tip_x, tip_y))

    # ---------------- Draw Main ----------------
    def draw(self) -> None:
        if self.state == "LIVEINTRO": self._draw_liveintro(); return
        if self.state == "LANDING": self._draw_landing(); return
        if self.state == "GATE": self._draw_gate(); return

        self._recompute_responsive()
        assert self.snapshot is not None
        
        video_rect, title_rect, nav_rect, panel_rect = self.make_layout()

        self.screen.fill(self.theme.BG)
        
        # 1. Video
        self.renderer.draw_card(self.screen, video_rect, fill=(12, 13, 16), outline=self.theme.LINE, radius=self.theme.RADIUS)
        self._draw_left_view(video_rect)
        self._draw_pixel_inspector(video_rect)
        
        # 2. Title
        self._draw_title_area(title_rect)
        
        # 3. Nav Buttons
        self._draw_navigation_area(nav_rect)
        
        # 4. Optional Panel (Scores)
        if panel_rect:
            self._draw_right_panel_content(panel_rect)
        
        # 5. Global Elements
        self._draw_step_indicator_global()
        self._draw_home_button()
        self._draw_lang_button()

        pygame.display.flip()
        self.clock.tick(30)

    def _draw_left_view(self, video_rect: pygame.Rect) -> None:
        if self.snapshot.frame_rgb is None: return
        if self.mode == "LIVE":
            surf = surfarray.make_surface(self.snapshot.frame_rgb.swapaxes(0, 1))
            self.screen.blit(pygame.transform.smoothscale(surf, (video_rect.w, video_rect.h)), video_rect.topleft)
            for d in self.snapshot.dets:
                r = self.renderer.rect_in_video_coords(d.box, self.snapshot.src_size, video_rect)
                pygame.draw.rect(self.screen, self.renderer.conf_color(d.conf), r, width=2)
            self._draw_hud_lines(video_rect, [f"Stream: {self.snapshot.debug.get('src_size', (0,0))}"])
        else:
            if self.sim_step_cached != self.step:
                sim_rgb = self.transformer.apply(self.snapshot.frame_rgb, self.step, self.level)
                self.sim_surface = surfarray.make_surface(sim_rgb.swapaxes(0, 1))
                self.sim_step_cached = self.step
            if self.sim_surface:
                self.screen.blit(pygame.transform.smoothscale(self.sim_surface, (video_rect.w, video_rect.h)), video_rect.topleft)
        
        dbg = self.snapshot.debug
        if self.level == "SCHUELER":
            if self.step == 1:
                self.renderer.draw_pixel_grid(self.screen, video_rect, spacing=max(18, int(26*self._scale())))
                self.renderer.draw_pill(self.screen, self.font_ui, "RGB Pixel", (video_rect.x+self.pad, video_rect.y+self.pad), fg=self.theme.TEXT)
            if self.step == 3:
                self._draw_det_list(video_rect, self.snapshot.raw_dets, color=(120, 120, 120))
                self._draw_det_list(video_rect, self.snapshot.dets[:10])
            if self.step == 4 and self.snapshot.top_dets: self._draw_det_list(video_rect, self.snapshot.top_dets, draw_label=True)
        else:
            if self.step == 1: self._draw_hud_lines(video_rect, ["Step1 Capture"])
            elif self.step == 2:
                self._draw_roi_overlay(video_rect)
                self._draw_hud_lines(video_rect, ["Step2 ROI/Aspect", f"ROI: {dbg.get('roi')}"])
            elif self.step == 3: self._draw_hud_lines(video_rect, ["Step3 Inference (IMX500)"])
            elif self.step == 4:
                lines = ["Step4 Read tensors"]
                if dbg.get("output_shapes"): lines.extend([f"out{i}: {s}" for i, s in enumerate(dbg["output_shapes"][:2])])
                self._draw_hud_lines(video_rect, lines)
            elif self.step == 5:
                self._draw_det_list(video_rect, self.snapshot.raw_dets, color=(255, 220, 80))
                self._draw_hud_lines(video_rect, ["Step5 Parse candidates"])
            elif self.step == 6:
                self._draw_det_list(video_rect, self.snapshot.raw_dets, color=(120, 120, 120))
                self._draw_det_list(video_rect, self.snapshot.dets[:10])
                self._draw_hud_lines(video_rect, ["Step6 Filter/Rank"])
            elif self.step == 7:
                self._draw_det_list(video_rect, self.snapshot.top_dets, draw_label=True)
                self._draw_hud_lines(video_rect, ["Step7 Render"])

    # ---------------- Main Loop ----------------
    def run(self) -> None:
        try:
            while self.running:
                self.handle_events()
                self.update()
                self.draw()
        finally:
            pygame.quit()
            if self.detector: self.detector.stop()


def get_args() -> argparse.Namespace:
    p = argparse.ArgumentParser()
    p.add_argument("--model", required=True)
    p.add_argument("--threshold", type=float, default=0.55)
    p.add_argument("--iou", type=float, default=0.65)
    p.add_argument("--max-detections", type=int, default=10)
    p.add_argument("--bbox-normalization", action=argparse.BooleanOptionalAction, default=None)
    p.add_argument("--bbox-order", choices=["yx", "xy"], default="yx")
    p.add_argument("--postprocess", choices=["", "nanodet"], default=None)
    p.add_argument("-r", "--preserve-aspect-ratio", action=argparse.BooleanOptionalAction, default=None)
    p.add_argument("--labels", type=str, default=None)
    p.add_argument("--cam-width", type=int, default=1280)
    p.add_argument("--cam-height", type=int, default=720)
    return p.parse_args()


def main() -> None:
    args = get_args()
    App(args).run()


if __name__ == "__main__":
    main()
