import {
  Component,
  ElementRef,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  PLATFORM_ID,
  ViewChild,
  SimpleChanges,
  inject,
} from '@angular/core';
import { CommonModule, isPlatformBrowser } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import * as THREE from 'three';
// @ts-ignore
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
// @ts-ignore
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

@Component({
  selector: 'app-stl-viewer',
  standalone: true,
  imports: [CommonModule, TranslateModule],
  templateUrl: './stl-viewer.component.html',
  styleUrl: './stl-viewer.component.scss',
})
export class StlViewerComponent implements OnInit, OnDestroy, OnChanges {
  private readonly platformId = inject(PLATFORM_ID);
  private readonly isBrowser = isPlatformBrowser(this.platformId);

  @Input() file: File | null = null;
  @Input() color: string = '#facf0a'; // Default Brand Color
  @Input() height = 300;
  @Input() borderRadius = 'var(--radius-lg)';

  @ViewChild('rendererContainer', { static: true })
  rendererContainer!: ElementRef;

  private scene!: THREE.Scene;
  private camera!: THREE.PerspectiveCamera;
  private renderer!: THREE.WebGLRenderer;
  private controls!: OrbitControls;
  private animationId: number | null = null;
  private currentMesh: THREE.Mesh | null = null;
  private autoRotate = true;
  private resizeObserver: ResizeObserver | null = null;

  loading = false;

  ngOnInit() {
    if (!this.isBrowser) {
      return;
    }
    this.initThree();
  }

  ngOnChanges(changes: SimpleChanges) {
    if (!this.isBrowser) {
      return;
    }

    if (changes['file'] && this.file) {
      this.loadFile(this.file);
    }

    if (changes['color'] && this.currentMesh && !changes['file']) {
      this.applyColorStyle(this.color);
    }
  }

  ngOnDestroy() {
    this.resizeObserver?.disconnect();
    this.resizeObserver = null;

    if (this.animationId) cancelAnimationFrame(this.animationId);
    this.clearCurrentMesh();
    if (this.controls) this.controls.dispose();
    if (this.renderer) this.renderer.dispose();
  }

  private initThree() {
    const width = this.rendererContainer.nativeElement.clientWidth;
    const height = this.rendererContainer.nativeElement.clientHeight;

    this.scene = new THREE.Scene();
    this.scene.background = new THREE.Color(0xf4f8fc);

    // Lights
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.75);
    this.scene.add(ambientLight);

    const hemiLight = new THREE.HemisphereLight(0xf8fbff, 0xc8d3df, 0.95);
    hemiLight.position.set(0, 30, 0);
    this.scene.add(hemiLight);

    const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.35);
    directionalLight1.position.set(6, 8, 6);
    this.scene.add(directionalLight1);

    const directionalLight2 = new THREE.DirectionalLight(0xe8f0ff, 0.85);
    directionalLight2.position.set(-7, 4, -5);
    this.scene.add(directionalLight2);

    const directionalLight3 = new THREE.DirectionalLight(0xffffff, 0.55);
    directionalLight3.position.set(0, 5, -9);
    this.scene.add(directionalLight3);

    // Camera
    this.camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 1000);
    this.camera.position.z = 100;

    // Renderer
    this.renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: false,
      powerPreference: 'high-performance',
    });
    this.renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
    this.renderer.outputColorSpace = THREE.SRGBColorSpace;
    this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
    this.renderer.toneMappingExposure = 1.2;
    this.renderer.setSize(width, height);
    this.rendererContainer.nativeElement.appendChild(this.renderer.domElement);

    // Controls
    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
    this.controls.enableDamping = true;
    this.controls.dampingFactor = 0.06;
    this.controls.enablePan = false;
    this.controls.minDistance = 10;
    this.controls.maxDistance = 600;
    this.controls.addEventListener('start', () => {
      this.autoRotate = false;
    });

    this.animate();

    // Handle resize
    this.resizeObserver = new ResizeObserver(() => {
      if (!this.rendererContainer) return;
      const w = this.rendererContainer.nativeElement.clientWidth;
      const h = this.rendererContainer.nativeElement.clientHeight;
      this.camera.aspect = w / h;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(w, h);
    });
    this.resizeObserver.observe(this.rendererContainer.nativeElement);
  }

  dimensions = { x: 0, y: 0, z: 0 };

  private loadFile(file: File) {
    this.loading = true;
    this.autoRotate = true;
    const reader = new FileReader();
    reader.onload = (event) => {
      try {
        const loader = new STLLoader();
        const geometry = loader.parse(event.target?.result as ArrayBuffer);

        this.clearCurrentMesh();

        geometry.computeVertexNormals();

        const material = new THREE.MeshStandardMaterial({
          color: this.color,
          roughness: 0.42,
          metalness: 0.05,
          emissive: 0x000000,
          emissiveIntensity: 0,
        });

        this.currentMesh = new THREE.Mesh(geometry, material);
        this.applyColorStyle(this.color);

        // Center geometry
        geometry.computeBoundingBox();
        geometry.center();

        // Get Dimensions
        const boundingBox = geometry.boundingBox!;
        const size = new THREE.Vector3();
        boundingBox.getSize(size);

        this.dimensions = {
          x: Math.round(size.x * 10) / 10,
          y: Math.round(size.y * 10) / 10,
          z: Math.round(size.z * 10) / 10,
        };

        // Rotate to stand upright (usually necessary for STLs)
        this.currentMesh.rotation.x = -Math.PI / 2;

        this.scene.add(this.currentMesh);

        // Adjust camera to fit object and keep it visually centered
        const maxDim = Math.max(size.x, size.y, size.z);
        const fov = this.camera.fov * (Math.PI / 180);

        // Calculate distance towards camera (z-axis)
        let cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2));
        cameraZ *= 1.72;

        this.camera.position.set(
          cameraZ * 0.68,
          cameraZ * 0.62,
          cameraZ * 1.08,
        );
        this.controls.target.set(0, 0, 0);
        this.camera.lookAt(0, 0, 0);
        this.camera.updateProjectionMatrix();
        this.controls.update();
      } catch (err) {
        console.error('Error loading STL:', err);
      } finally {
        this.loading = false;
      }
    };
    reader.readAsArrayBuffer(file);
  }

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

    if (this.currentMesh && this.autoRotate) {
      this.currentMesh.rotation.z += 0.0025;
    }

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

  private clearCurrentMesh() {
    if (!this.currentMesh) {
      return;
    }

    this.scene.remove(this.currentMesh);
    this.currentMesh.geometry.dispose();

    const meshMaterial = this.currentMesh.material;
    if (Array.isArray(meshMaterial)) {
      meshMaterial.forEach((m) => m.dispose());
    } else {
      meshMaterial.dispose();
    }

    this.currentMesh = null;
  }

  private applyColorStyle(color: string) {
    if (!this.currentMesh) {
      return;
    }

    const darkColor = this.isDarkColor(color);
    const meshMaterial = this.currentMesh.material;

    if (meshMaterial instanceof THREE.MeshStandardMaterial) {
      meshMaterial.color.set(color);
      if (darkColor) {
        meshMaterial.emissive.set(0x2a2f36);
        meshMaterial.emissiveIntensity = 0.28;
        meshMaterial.roughness = 0.5;
        meshMaterial.metalness = 0.03;
      } else {
        meshMaterial.emissive.set(0x000000);
        meshMaterial.emissiveIntensity = 0;
        meshMaterial.roughness = 0.42;
        meshMaterial.metalness = 0.05;
      }
      meshMaterial.needsUpdate = true;
    }
  }

  private isDarkColor(color: string): boolean {
    const c = new THREE.Color(color);
    const luminance = 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b;
    return luminance < 0.22;
  }
}
