"""Beschriftungen der Objektrahmen.

Sie haengen an dem Rahmen, den ``hud/draw_bbox`` zeichnet — und holen sich
dessen Form seit dem Nachziehen auf den modularen Aufbau aus derselben Quelle
(``hud/layout``) statt sie ein zweites Mal und anders zu rechnen. Text, Linien
und Farben kommen aus dem gemeinsamen Werkzeugkasten ``hud/zeichnen``, damit
die Beschriftungen aussehen wie der Rest der Anzeige.

Alle Masse folgen der Oberflaechenskalierung. Vorher tat das nur die Schrift:
Die Fuehrungslinie, der Abstand der Unterstreichung und der Einzug des Textes
standen als feste Pixelzahlen da. Auf einem hochaufloesenden Bildschirm wuchs
darum die Schrift, nicht aber das Geruest darum herum — die Unterstreichung
lag dann mitten im Text.
"""

import bpy
import os
import glob
import time
import math
import traceback

import gpu
import blf
from gpu_extras.batch import batch_for_shader
from bpy_extras.object_utils import world_to_camera_view
from bpy_extras import view3d_utils
from mathutils import Vector
from bpy.types import Panel, Operator, PropertyGroup, AddonPreferences
from bpy.props import (IntProperty, StringProperty, PointerProperty,
                       FloatProperty, BoolProperty, EnumProperty)

from . import state
from .colors import get_heatmap_color
from .grid_math import location_3d_to_region_2d
from .layout import (adjust_label_position, oktagon, abstand_zum_rahmen)
from .zeichnen import (skala, kurve, textmasse, TEXT,
                            text as zeichne_text)


# Grundgroesse der Beschriftung in Pixeln; mit der Oberflaeche skaliert.
SCHRIFTGROESSE = 9


# ============= PREDATOR HUD (TextTest Integration) - Callback-Funktionen =============

def draw_bbox_labels_callback(context):
    """Zeichnet Text-Labels mit Triangle-Count (POST_PIXEL für 2D-Text)"""
    try:
        scene = context.scene
        if not scene.basematerials_tracking:
            return
        
        state._bbox_animation_time = time.time()
        
        # Mausposition aus globaler Variable verwenden
        mouse_pos = state._mouse_position
        
        # Label-Positionen für diese Frame sammeln (Kollisionsvermeidung)
        current_frame_labels = []
        
        view_layer = context.view_layer
        region = context.region
        rv3d = context.space_data.region_3d
        
        # FIXE PARAMETER (nicht mehr änderbar)
        use_heatmap = True  # Immer Heatmap
        alpha = 0.8  # Fixe Transparenz
        show_labels = True  # Immer Labels anzeigen
        mesh_only = True  # Nur Meshes
        
        # Objekte sammeln - Geometrie aus CACHE holen!
        geometry_counts = []
        triangle_counts = []
        valid_objects = []
        
        for obj in view_layer.objects:
            if not obj.visible_get():
                continue
            if mesh_only and obj.type != 'MESH':
                continue
            
            # Aus Cache holen (wurde beim Aktivieren berechnet)
            if obj.name in state._cached_geometry_data:
                cached_data = state._cached_geometry_data[obj.name]
                geo_count = cached_data['geo_count']
                tri_count = cached_data['tri_count']
            else:
                # Nur bei neuen Objekten berechnen
                geo_count = 0
                tri_count = 0
                if obj.type == 'MESH' and obj.data:
                    geo_count = len(obj.data.vertices) + len(obj.data.edges) + len(obj.data.polygons)
                    for poly in obj.data.polygons:
                        tri_count += len(poly.vertices) - 2
                
                # In Cache speichern
                state._cached_geometry_data[obj.name] = {
                    'geo_count': geo_count,
                    'tri_count': tri_count
                }
            
            geometry_counts.append(geo_count)
            triangle_counts.append(tri_count)
            valid_objects.append(obj)
        
        if not valid_objects:
            return
        
        # Min/Max für Heatmap
        if use_heatmap and geometry_counts:
            min_geo = min(geometry_counts)
            max_geo = max(geometry_counts)
        else:
            min_geo, max_geo = 0, 1
        
        # Alle Masse folgen der Oberflaechenskalierung. Frueher stand hier die
        # Groesse des GPU-Viewports, die dem Linienzeichner uebergeben wurde —
        # der Rahmen daneben benutzte dafuer die Groesse der Region. Eine der
        # beiden Linienbreiten war damit zwangslaeufig falsch skaliert; mit dem
        # gemeinsamen Zeichner entfaellt die Angabe ganz.
        s = skala(context)

        # Jedes Label zeichnen - VON P5 (oben rechts) ausgehend
        for i, obj in enumerate(valid_objects):
            try:
                # Bounding Box in 2D berechnen (wie im main callback)
                bbox_3d = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
                bbox_2d = []
                all_visible = True
                
                for corner_3d in bbox_3d:
                    corner_2d = location_3d_to_region_2d(region, rv3d, corner_3d)
                    if corner_2d is None:
                        all_visible = False
                        break
                    bbox_2d.append(corner_2d)
                
                if not all_visible:
                    continue
                
                # Animation-Progress - prüfen ob Objekt seine Animation gestartet hat
                if obj.name not in state._object_animations:
                    # Objekt hat noch keine Startzeit - nicht zeichnen
                    continue
                
                obj_start_time = state._object_animations[obj.name]
                elapsed = time.time() - obj_start_time
                anim_duration = 1.0
                
                # Noch nicht an der Reihe
                if elapsed < 0:
                    continue
                
                if elapsed < anim_duration:
                    t = elapsed / anim_duration
                    t = 1.0 - math.pow(1.0 - t, 3)
                else:
                    t = 1.0
                
                # 2D BBox berechnen
                min_x = min(p.x for p in bbox_2d)
                max_x = max(p.x for p in bbox_2d)
                min_y = min(p.y for p in bbox_2d)
                max_y = max(p.y for p in bbox_2d)
                
                center_x = (min_x + max_x) / 2
                center_y = (min_y + max_y) / 2
                width_2d = max_x - min_x
                height_2d = max_y - min_y
                
                # Skalierung
                start_scale = 3.0
                end_scale = 1.0
                current_scale = start_scale + (end_scale - start_scale) * t
                
                half_w = (width_2d / 2) * current_scale
                half_h = (height_2d / 2) * current_scale
                
                # Der Rahmen, an dem die Beschriftung haengt — dieselbe Form,
                # die daneben gezeichnet wird. Vorher rechnete sich diese
                # Stelle ein eigenes Oktagon: Eckenschnitt 20 Prozent der
                # halben Breite statt fester 10 Pixel, und ohne die
                # Mindestgroesse von 20 Pixeln. Die Fuehrungslinie setzte
                # dadurch neben dem sichtbaren Rahmen an, und das Einblenden
                # bei Mausnaehe mass gegen eine Form, die niemand sah.
                ecken = oktagon(center_x, center_y, half_w, half_h)
                p5 = ecken[4]      # obere rechte Kante nach dem Eckenschnitt
                p6 = ecken[5]      # obere linke Kante

                # Abstand der Maus zum sichtbaren Rahmen
                min_distance_to_frame = abstand_zum_rahmen(mouse_pos, ecken)

                # Fade-Bereich — folgt jetzt der Oberflaechenskalierung, sonst
                # reagiert die Anzeige auf einem hochaufloesenden Bildschirm
                # erst, wenn die Maus schon fast auf dem Rahmen steht.
                fade_start = 10.0 * s   # Voll sichtbar ab 10 px
                fade_end = 40.0 * s     # Unsichtbar ab 40 px
                
                # Distanz-basierte Alpha-Berechnung
                if min_distance_to_frame < fade_start:
                    distance_alpha = 1.0
                elif min_distance_to_frame < fade_end:
                    fade_progress = (min_distance_to_frame - fade_start) / (fade_end - fade_start)
                    distance_alpha = 1.0 - fade_progress
                else:
                    # Außerhalb - Label überspringen
                    continue
                
                # Farbe für Oktagon (Heatmap)
                if use_heatmap:
                    color = get_heatmap_color(geometry_counts[i], min_geo, max_geo)
                else:
                    color = (1.0, 0.5, 0.0)  # Fallback Orange
                
                # Tris aus bereits berechneten Daten holen (NICHT neu berechnen!)
                tri_count = triangle_counts[i]
                
                # Text mit Objekt-Name
                text = f"{tri_count} Tris / {obj.name}"

                # Schrift und Mass kommen aus dem gemeinsamen Werkzeugkasten,
                # damit die Beschriftung dieselbe Schrift und denselben
                # Schatten bekommt wie die uebrige Anzeige.
                schriftgroesse = SCHRIFTGROESSE * s
                text_width, text_height = textmasse(schriftgroesse, text)

                # LABEL-RICHTUNG: Abwechselnd links/rechts für bessere Verteilung
                # Jedes 2. Label geht nach links (P6 statt P5)
                label_goes_right = (i % 2 == 0)  # Gerade Index = rechts, ungerade = links
                
                if label_goes_right:
                    # P5 (oben rechts) - Label nach RECHTS
                    p_start = p5
                    direction_multiplier = 1
                else:
                    # P6 (oben links) - Label nach LINKS
                    p_start = p6
                    direction_multiplier = -1  # Negativ für links

                # Linie: 45° nach außen, dann horizontal
                diagonal_length = 25 * s
                diagonal_x = diagonal_length * math.cos(math.radians(45)) * direction_multiplier
                diagonal_y = diagonal_length * math.sin(math.radians(45))

                horizontal_length = text_width + 10 * s

                # KOLLISIONSVERMEIDUNG - Initiale Position
                p_diagonal = Vector((p_start.x + diagonal_x, p_start.y + diagonal_y))
                
                if label_goes_right:
                    # Text rechts vom Diagonal-Ende
                    p_end = Vector((p_diagonal.x + horizontal_length, p_diagonal.y))
                    initial_text_x = p_end.x - text_width
                else:
                    # Text links vom Diagonal-Ende (Linie geht nach links!)
                    p_end = Vector((p_diagonal.x - horizontal_length, p_diagonal.y))
                    initial_text_x = p_end.x
                
                initial_text_y = p_diagonal.y + text_height * 0.4
                
                # Prüfe Kollision mit bereits gezeichneten Labels
                adjusted_pos = adjust_label_position(
                    (initial_text_x, initial_text_y),
                    text_width,
                    text_height,
                    current_frame_labels,
                    region
                )
                
                # Position anpassen
                final_text_x = adjusted_pos.x
                final_text_y = adjusted_pos.y
                
                # ANIMIERTE Position - Smooth Übergang zur Zielposition
                target_pos = Vector((final_text_x, final_text_y))
                
                if obj.name not in state._label_positions:
                    # Neue Position - starte bei Initial
                    state._label_positions[obj.name] = {
                        'current': target_pos.copy(),
                        'target': target_pos.copy()
                    }
                
                # Zielposition aktualisieren
                state._label_positions[obj.name]['target'] = target_pos
                
                # Aktuelle Position smooth zur Zielposition bewegen (Lerp)
                lerp_speed = 0.30  # 30% pro Frame = ~0.15s bis zum Ziel bei 60 FPS
                current_pos = state._label_positions[obj.name]['current']
                current_pos = current_pos.lerp(target_pos, lerp_speed)
                state._label_positions[obj.name]['current'] = current_pos
                
                # Aktuelle Label-Box zur Kollisionsliste hinzufügen
                current_frame_labels.append((current_pos.x, current_pos.y, text_width, text_height))
                
                # Linie zeichnen: P5 → Diagonal → HORIZONTAL (unterstreicht Text!)

                # Text-Offset: fuenf Zeichen nach rechts. Die Zeichenbreite
                # kommt jetzt aus dem gemessenen Text — vorher wurde sie aus
                # der UNSKALIERTEN Schriftgroesse geschaetzt, wodurch der
                # Einzug auf einem hochaufloesenden Bildschirm zu klein blieb
                # und die Unterstreichung mitten im Text lag.
                char_width_estimate = text_width / max(1, len(text))
                text_x_offset = char_width_estimate * 5  # 5 Zeichen

                # Text-Position mit Offset
                text_x_final = current_pos.x + text_x_offset

                # Horizontale Linie UNTER dem Text (Unterstreichung)
                horizontal_y = current_pos.y - 6 * s

                # Diagonal-Länge: 33px nach rechts von P5 (10% mehr)
                diagonal_x_distance = 33 * s

                # Diagonal-Endpunkt (wo horizontale beginnt)
                p_diagonal_end = Vector((p_start.x + diagonal_x_distance * direction_multiplier, horizontal_y))
                
                # Horizontale: Unterschiedlich je nach Richtung
                if label_goes_right:
                    # Nach rechts: Diagonal-Ende → Text-Ende
                    p_horizontal_end = Vector((text_x_final + text_width, horizontal_y))
                else:
                    # Nach links: Diagonal-Ende → Text-Anfang
                    p_horizontal_end = Vector((text_x_final, horizontal_y))
                
                # 3-Punkt Linie: Start → Diagonal-Ende → Text
                line_points = [p_start, p_diagonal_end, p_horizontal_end]
                
                # Linie und Text im Ton der uebrigen Anzeige (hud/zeichnen).
                # Der gemeinsame Linienzeichner legt jedes Teilstueck als
                # schmales Viereck — er braucht keine Viewport-Groesse und
                # haengt damit nicht mehr an einer Angabe, die hier und beim
                # Rahmen aus zwei verschiedenen Quellen kam.
                farbe = (TEXT[0], TEXT[1], TEXT[2], distance_alpha)

                gpu.state.blend_set('ALPHA')
                kurve([(p.x, p.y) for p in line_points], farbe, 1.5 * s)
                gpu.state.blend_set('NONE')

                # Text an animierter Position mit Offset zeichnen
                zeichne_text(text_x_final, current_pos.y, schriftgroesse,
                             farbe, text)

            except Exception as e:
                continue
        
    except Exception as e:
        pass
