import { Injectable, signal } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class FaceDetectorService {
  public isCameraActive = signal<boolean>(false);
  public currentTiltAngle = signal<number>(0);
  public isTilted = signal<boolean>(false);

  private stream: MediaStream | null = null;
  private camera: any = null;
  private faceMesh: any = null;
  private onTiltCallback: ((direction: 'next' | 'prev') => void) | null = null;

  // Gesture state machine
  private tiltStartTime: number = 0;
  private inCooldown: boolean = false;
  private readonly TILT_THRESHOLD = 15; // Degrees to trigger next/prev page
  private readonly TILT_HOLD_MS = 300;   // How long to hold the tilt
  private readonly RETURN_THRESHOLD = 6; // Degrees to reset cooldown

  constructor() {}

  /**
   * Loads MediaPipe scripts dynamically if not already loaded.
   */
  private loadScripts(): Promise<void> {
    return new Promise((resolve, reject) => {
      if ((window as any).FaceMesh && (window as any).Camera) {
        resolve();
        return;
      }

      const cameraScript = document.createElement('script');
      cameraScript.src = 'https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js';
      cameraScript.crossOrigin = 'anonymous';

      const faceMeshScript = document.createElement('script');
      faceMeshScript.src = 'https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/face_mesh.js';
      faceMeshScript.crossOrigin = 'anonymous';

      cameraScript.onload = () => {
        document.head.appendChild(faceMeshScript);
      };

      faceMeshScript.onload = () => {
        resolve();
      };

      cameraScript.onerror = (err) => reject(err);
      faceMeshScript.onerror = (err) => reject(err);

      document.head.appendChild(cameraScript);
    });
  }

  /**
   * Starts camera capture and face mesh tracking.
   */
  async start(videoElement: HTMLVideoElement, onTilt: (direction: 'next' | 'prev') => void): Promise<void> {
    if (this.isCameraActive()) return;
    this.onTiltCallback = onTilt;

    try {
      await this.loadScripts();

      // Request camera permissions and stream
      this.stream = await navigator.mediaDevices.getUserMedia({
        video: {
          width: { ideal: 320 },
          height: { ideal: 240 },
          facingMode: 'user'
        },
        audio: false
      });

      videoElement.srcObject = this.stream;
      videoElement.setAttribute('playsinline', 'true');
      videoElement.muted = true;
      videoElement.play();

      const FaceMeshLib = (window as any).FaceMesh;
      const CameraLib = (window as any).Camera;

      if (!FaceMeshLib || !CameraLib) {
        throw new Error('MediaPipe libraries failed to initialize.');
      }

      this.faceMesh = new FaceMeshLib({
        locateFile: (file: string) => `https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/${file}`
      });

      this.faceMesh.setOptions({
        maxNumFaces: 1,
        refineLandmarks: false,
        minDetectionConfidence: 0.6,
        minTrackingConfidence: 0.6
      });

      this.faceMesh.onResults((results: any) => {
        this.processLandmarks(results);
      });

      this.camera = new CameraLib(videoElement, {
        onFrame: async () => {
          if (this.isCameraActive() && this.faceMesh) {
            await this.faceMesh.send({ image: videoElement });
          }
        },
        width: 320,
        height: 240
      });

      this.isCameraActive.set(true);
      await this.camera.start();
      console.log('[FaceDetector] Face tracking started successfully.');
    } catch (err) {
      console.error('[FaceDetector] Failed to start face tracking:', err);
      this.stop();
      throw err;
    }
  }

  /**
   * Processes landmarks to calculate head tilt angle.
   */
  private processLandmarks(results: any) {
    if (!results.multiFaceLandmarks || results.multiFaceLandmarks.length === 0) {
      this.currentTiltAngle.set(0);
      this.isTilted.set(false);
      return;
    }

    const landmarks = results.multiFaceLandmarks[0];

    // Left eye corner (landmark 33) and right eye corner (landmark 263)
    const leftEye = landmarks[33];
    const rightEye = landmarks[263];

    if (!leftEye || !rightEye) return;

    // Calculate angle in degrees
    const dy = rightEye.y - leftEye.y;
    const dx = rightEye.x - leftEye.x;
    
    // Normalize angle (roll)
    let angle = Math.atan2(dy, dx) * (180 / Math.PI);
    
    // Smooth angle updates
    this.currentTiltAngle.set(Math.round(angle));

    const absAngle = Math.abs(angle);

    if (absAngle > this.TILT_THRESHOLD) {
      this.isTilted.set(true);

      if (!this.inCooldown) {
        if (this.tiltStartTime === 0) {
          this.tiltStartTime = Date.now();
        } else if (Date.now() - this.tiltStartTime > this.TILT_HOLD_MS) {
          // Trigger the tilt gesture!
          const direction = angle > 0 ? 'prev' : 'next';
          console.log(`[FaceDetector] Head tilt gesture detected! Angle: ${angle.toFixed(1)}°, Direction: ${direction}`);
          if (this.onTiltCallback) {
            this.onTiltCallback(direction);
          }
          this.inCooldown = true;
          this.tiltStartTime = 0;
        }
      }
    } else {
      this.isTilted.set(false);
      this.tiltStartTime = 0;

      // Reset cooldown when the head returns near the center
      if (absAngle < this.RETURN_THRESHOLD) {
        this.inCooldown = false;
      }
    }
  }

  /**
   * Stops camera capture and releases face mesh resources.
   */
  stop() {
    this.isCameraActive.set(false);
    this.currentTiltAngle.set(0);
    this.isTilted.set(false);
    this.inCooldown = false;
    this.tiltStartTime = 0;

    if (this.camera) {
      try {
        this.camera.stop();
      } catch (e) {}
      this.camera = null;
    }

    if (this.stream) {
      this.stream.getTracks().forEach(track => track.stop());
      this.stream = null;
    }

    if (this.faceMesh) {
      try {
        this.faceMesh.close();
      } catch (e) {}
      this.faceMesh = null;
    }

    this.onTiltCallback = null;
    console.log('[FaceDetector] Face tracking stopped.');
  }
}
