<template>
  <div class="character-3d-shell">
    <div v-show="!showFallback" ref="container" class="character-3d-container"></div>
    <img
      v-if="showFallback"
      class="character-fallback"
      :src="fallbackImageSrc"
      :alt="`Character ${actualGender}`"
    />
  </div>
</template>

<script>
import { markRaw } from 'vue';
import { getApiBaseURL } from '@/utils/axios.js';

/** Backend-Route: GET /api/models/3d/falukant/characters/:filename (Proxy mit Draco-Optimierung) */
const MODELS_API_PATH = '/api/models/3d/falukant/characters';
let threeRuntimePromise = null;
let threeLoadersPromise = null;
let threeModelRuntimePromise = null;

async function loadThreeRuntime() {
  if (!threeRuntimePromise) {
    threeRuntimePromise = import('@/utils/threeRuntime.js');
  }

  return threeRuntimePromise;
}

async function loadThreeLoaders() {
  if (!threeLoadersPromise) {
    threeLoadersPromise = import('@/utils/threeLoaders.js');
  }

  return threeLoadersPromise;
}

async function loadThreeModelRuntime() {
  if (!threeModelRuntimePromise) {
    threeModelRuntimePromise = import('@/utils/threeModelRuntime.js');
  }

  return threeModelRuntimePromise;
}

export default {
  name: 'Character3D',
  props: {
    gender: {
      type: String,
      default: null,
      validator: (value) => value === null || ['male', 'female'].includes(value)
    },
    age: {
      type: Number,
      default: null,
      validator: (value) => value === null || (value >= 0 && value <= 120)
    },
    noBackground: {
      type: Boolean,
      default: false
    },
    lightweight: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      randomGender: null,
      randomAge: null,
      scene: null,
      camera: null,
      renderer: null,
      model: null,
      animationId: null,
      mixer: null,
      clock: null,
      baseYPosition: 0,
      showFallback: false,
      threeRuntime: null,
      threeLoaders: null,
      threeModelRuntime: null
    };
  },
  computed: {
    actualGender() {
      if (this.gender) {
        return this.gender;
      }
      // Zufällige Auswahl beim ersten Mount, dann persistent
      if (this.randomGender === null) {
        this.randomGender = Math.random() < 0.5 ? 'male' : 'female';
      }
      return this.randomGender;
    },
    actualAge() {
      if (this.age !== null && this.age !== undefined) {
        return this.age;
      }
      // Zufällige Auswahl beim ersten Mount, dann persistent
      if (this.randomAge === null) {
        // Zufällige Altersgruppe auswählen, damit verschiedene Altersbereiche dargestellt werden
        const ageGroups = [
          { min: 0, max: 3 },    // toddler
          { min: 4, max: 7 },     // child
          { min: 8, max: 12 },     // preteen
          { min: 13, max: 17 },   // teen
          { min: 18, max: 65 }    // adult
        ];
        const selectedGroup = ageGroups[Math.floor(Math.random() * ageGroups.length)];
        this.randomAge = Math.floor(Math.random() * (selectedGroup.max - selectedGroup.min + 1)) + selectedGroup.min;
      }
      return this.randomAge;
    },
    ageGroup() {
      const age = this.actualAge;
      if (age <= 3) return 'toddler';
      if (age <= 7) return 'child';
      if (age <= 12) return 'preteen';
      if (age <= 17) return 'teen';
      return 'adult';
    },
    modelPath() {
      const base = getApiBaseURL();
      const prefix = base ? `${base}${MODELS_API_PATH}` : MODELS_API_PATH;
      return `${prefix}/${this.actualGender}_${this.ageGroup}.glb`;
    },
    exactAgeModelPath() {
      // Pfad für genaues Alter (z.B. female_1y.glb für Alter 1)
      const age = this.actualAge;
      const base = getApiBaseURL();
      const prefix = base ? `${base}${MODELS_API_PATH}` : MODELS_API_PATH;
      return `${prefix}/${this.actualGender}_${age}y.glb`;
    },
    fallbackImageSrc() {
      return this.actualGender === 'female'
        ? '/images/mascot/mascot_female.png'
        : '/images/mascot/mascot_male.png';
    }
  },
  watch: {
    async actualGender() {
      await this.loadModel();
    },
    async ageGroup() {
      await this.loadModel();
    }
  },
  async mounted() {
    await this.init3D();
    await this.loadModel();
    this.animate();
  },
  beforeUnmount() {
    this.cleanup();
  },
  methods: {
    async ensureThreeRuntime() {
      if (!this.threeRuntime) {
        this.threeRuntime = markRaw(await loadThreeRuntime());
      }

      return this.threeRuntime;
    },

    async ensureThreeLoaders() {
      if (!this.threeLoaders) {
        this.threeLoaders = markRaw(await loadThreeLoaders());
      }

      return this.threeLoaders;
    },

    async ensureThreeModelRuntime() {
      if (!this.threeModelRuntime) {
        this.threeModelRuntime = markRaw(await loadThreeModelRuntime());
      }

      return this.threeModelRuntime;
    },

    async init3D() {
      const container = this.$refs.container;
      if (!container) return;
      this.showFallback = false;
      const runtime = await this.ensureThreeRuntime();
      this.clock = markRaw(new runtime.Clock());

      // Scene erstellen - markRaw verwenden, um Vue's Reactivity zu vermeiden
      this.scene = markRaw(new runtime.Scene());
      if (!this.noBackground) {
        this.scene.background = new runtime.Color(0xf0f0f0);
        await this.loadBackground();
      }

      // Camera erstellen
      const aspect = container.clientWidth / container.clientHeight;
      this.camera = markRaw(new runtime.PerspectiveCamera(50, aspect, 0.1, 1000));
      this.camera.position.set(0, 1.5, 3);
      this.camera.lookAt(0, 1, 0);

      // Renderer erstellen
      this.renderer = markRaw(new runtime.WebGLRenderer({ antialias: true, alpha: true }));
      this.renderer.setSize(container.clientWidth, container.clientHeight);
      this.renderer.setPixelRatio(window.devicePixelRatio);
      container.appendChild(this.renderer.domElement);

      // Verbesserte Beleuchtung für hellere Modelle
      // Mehr ambient light für gleichmäßigere Ausleuchtung
      const ambientLight = new runtime.AmbientLight(0xffffff, 1.0);
      this.scene.add(ambientLight);

      // Hauptlicht von vorne oben - stärker
      const directionalLight = new runtime.DirectionalLight(0xffffff, 1.2);
      directionalLight.position.set(5, 10, 5);
      this.scene.add(directionalLight);

      // Zusätzliches Licht von hinten - heller
      const backLight = new runtime.DirectionalLight(0xffffff, 0.75);
      backLight.position.set(-5, 5, -5);
      this.scene.add(backLight);

      // Zusätzliches Seitenlicht für mehr Tiefe
      const sideLight = new runtime.DirectionalLight(0xffffff, 0.5);
      sideLight.position.set(-5, 5, 5);
      this.scene.add(sideLight);

      // Resize Handler
      window.addEventListener('resize', this.onWindowResize);
    },

    async loadBackground() {
      const runtime = await this.ensureThreeRuntime();
      // Optimierte Versionen (512×341, ~130 KB); Originale ~3 MB
      const backgrounds = ['bg1_opt.png', 'bg2_opt.png'];
      const randomBg = backgrounds[Math.floor(Math.random() * backgrounds.length)];
      const bgPath = `/images/falukant/backgrounds/${randomBg}`;
      
      const loader = new runtime.TextureLoader();
      loader.load(
        bgPath,
        (texture) => {
          // Hintergrund erfolgreich geladen
          if (this.scene) {
            this.scene.background = texture;
          }
        },
        undefined,
        (error) => {
          console.warn('Fehler beim Laden des Hintergrunds:', error);
          // Fallback auf Standardfarbe bei Fehler
          if (this.scene) {
            this.scene.background = new runtime.Color(0xf0f0f0);
          }
        }
      );
    },

    async loadModel() {
      if (!this.scene) return;
      const modelRuntime = await this.ensureThreeModelRuntime();
      const loaders = await this.ensureThreeLoaders();

      // Altes Modell entfernen
      if (this.model) {
        this.scene.remove(this.model);
        // Cleanup des alten Modells
        this.model.traverse((object) => {
          if (object.geometry) object.geometry.dispose();
          if (object.material) {
            if (Array.isArray(object.material)) {
              object.material.forEach(m => m.dispose());
            } else {
              object.material.dispose();
            }
          }
        });
        this.model = null;
      }

      if (this.mixer) {
        this.mixer = null;
      }

      try {
        const dracoLoader = new loaders.DRACOLoader();
        dracoLoader.setDecoderPath('/draco/gltf/');
        const loader = new loaders.GLTFLoader();
        loader.setDRACOLoader(dracoLoader);

        const base = getApiBaseURL();
        const prefix = base ? `${base}${MODELS_API_PATH}` : MODELS_API_PATH;
        
        // Fallback-Hierarchie:
        // Standard:
        // 1. Exaktes Altersmodell
        // 2. Altersbereich
        // 3. Basis-Modell
        // Lightweight:
        // 1. Altersbereich
        // 2. Basis-Modell
        const exactAgePath = this.exactAgeModelPath;
        const ageGroupPath = this.modelPath;
        const fallbackPath = `${prefix}/${this.actualGender}.glb`;

        let gltf;
        try {
          if (this.lightweight) {
            try {
              gltf = await loader.loadAsync(ageGroupPath);
            } catch (ageGroupError) {
              gltf = await loader.loadAsync(fallbackPath);
            }
          } else {
            try {
              gltf = await loader.loadAsync(exactAgePath);
            } catch (exactAgeError) {
              try {
                gltf = await loader.loadAsync(ageGroupPath);
              } catch (ageGroupError) {
                gltf = await loader.loadAsync(fallbackPath);
              }
            }
          }
        } finally {
          dracoLoader.dispose();
        }

        // Modell als raw markieren, um Vue's Reactivity zu vermeiden
        this.model = markRaw(gltf.scene);
        
        // Initiale Bounding Box für Größenberechnung (vor Skalierung)
        const initialBox = new modelRuntime.Box3().setFromObject(this.model);
        const initialSize = initialBox.getSize(new modelRuntime.Vector3());
        
        // Skalierung basierend auf Alter
        const age = this.actualAge;
        let ageScale = 1;
        if (age <= 3) {
          ageScale = 0.4;
        } else if (age <= 7) {
          ageScale = 0.6;
        } else if (age <= 12) {
          ageScale = 0.75;
        } else if (age <= 17) {
          ageScale = 0.9;
        } else {
          ageScale = 1.0;
        }
        
        // Modell skalieren, damit es in die Szene passt
        const maxDimension = Math.max(initialSize.x, initialSize.y, initialSize.z);
        const targetHeight = 2; // Zielhöhe in 3D-Einheiten
        const modelScale = (targetHeight / maxDimension) * ageScale;
        this.model.scale.set(modelScale, modelScale, modelScale);
        
        // Bounding Box NACH dem Skalieren neu berechnen
        const scaledBox = new modelRuntime.Box3().setFromObject(this.model);
        const scaledCenter = scaledBox.getCenter(new modelRuntime.Vector3());
        
        // Modell zentrieren basierend auf der skalierten Bounding Box
        // Position direkt setzen statt zu subtrahieren, um Proxy-Probleme zu vermeiden
        const baseY = -scaledBox.min.y; // Auf Boden setzen (y=0 entspricht dem unteren Rand)
        this.model.position.set(-scaledCenter.x, baseY, -scaledCenter.z);
        this.baseYPosition = baseY; // Basisposition für Animation speichern
        
        this.scene.add(this.model);

        // Animationen laden falls vorhanden
        if (gltf.animations && gltf.animations.length > 0) {
          this.mixer = markRaw(new modelRuntime.AnimationMixer(this.model));
          gltf.animations.forEach((clip) => {
            this.mixer.clipAction(clip).play();
          });
        }

        // Keine Rotation - Figuren bleiben statisch
        if (this.model) {
          this.model.rotation.y = 0;
        }
      } catch (error) {
        console.error('Error loading 3D model:', error);
        this.showFallback = true;
      }
    },

    animate() {
      this.animationId = requestAnimationFrame(this.animate);

      if (!this.clock) {
        return;
      }

      const delta = this.clock.getDelta();

      // Animation-Mixer aktualisieren
      if (this.mixer) {
        this.mixer.update(delta);
      }

      // Keine Bewegung - Modelle bleiben statisch

      if (this.renderer && this.scene && this.camera) {
        this.renderer.render(this.scene, this.camera);
      }
    },

    onWindowResize() {
      const container = this.$refs.container;
      if (!container || !this.camera || !this.renderer) return;

      const width = container.clientWidth;
      const height = container.clientHeight;

      this.camera.aspect = width / height;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(width, height);
    },

    cleanup() {
      if (this.animationId) {
        cancelAnimationFrame(this.animationId);
      }

      window.removeEventListener('resize', this.onWindowResize);

      if (this.mixer) {
        this.mixer = null;
      }

      if (this.model) {
        this.model.traverse((object) => {
          if (object.geometry) object.geometry.dispose();
          if (object.material) {
            if (Array.isArray(object.material)) {
              object.material.forEach(m => m.dispose());
            } else {
              object.material.dispose();
            }
          }
        });
        this.model = null;
      }

      if (this.renderer) {
        const container = this.$refs.container;
        if (container && this.renderer.domElement) {
          container.removeChild(this.renderer.domElement);
        }
        this.renderer.dispose();
      }

      if (this.scene) {
        this.scene.clear();
      }
    }
  }
};
</script>

<style scoped>
.character-3d-shell {
  width: 100%;
  height: 100%;
  min-height: 0;
  position: relative;
}

.character-3d-container {
  width: 100%;
  height: 100%;
  min-height: 0;
  position: relative;
  overflow: hidden;
}

.character-fallback {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center bottom;
}
</style>
