import { useCallback, useEffect, useRef, useState } from 'react';
import {
  detectCardShapesFromFrame,
  DETECTION_START_DELAY_MS,
  mergeDetectedShapesIntoTrackedCards,
  MIN_STABLE_COUNT_FOR_VERIFY,
  selectCardsReadyForVerification,
  SHAPE_DETECTION_INTERVAL_MS,
  VERIFICATION_INTERVAL_MS,
} from './scanner-card-detection.js';

const SESSION_DEVICE_KEY = 'scanner:last-camera-device-id';

const DEVICE_PICKER_MESSAGES = {
  loading: 'Detecting cameras…',
  ready: '',
  empty: 'No camera found. Connect a webcam or use Upload Image.',
  denied: 'Camera access blocked. Allow camera permission or use Upload Image.',
  error: "Couldn't list cameras. Try again or use Upload Image.",
};

/**
 * Camera stream + card shape detection loop for the card scanner.
 * Identification callbacks stay in the parent component.
 */
export function useCameraScanner({
  onError,
  onVerifyCard,
  verificationPausedRef,
  autoDetectPausedRef,
  facingMode = 'environment',
}) {
  const [isStreaming, setIsStreaming] = useState(false);
  const [isDetecting, setIsDetecting] = useState(false);
  const [trackedCards, setTrackedCards] = useState([]);
  const [videoMetrics, setVideoMetrics] = useState({ width: 0, height: 0 });
  const [activeFacingMode, setActiveFacingMode] = useState(facingMode);
  const [videoDevices, setVideoDevices] = useState([]);
  const [selectedDeviceId, setSelectedDeviceIdState] = useState(() => {
    try {
      return sessionStorage.getItem(SESSION_DEVICE_KEY) || '';
    } catch {
      return '';
    }
  });
  const [devicePickerStatus, setDevicePickerStatus] = useState('loading');
  const [devicePickerMessage, setDevicePickerMessage] = useState(
    DEVICE_PICKER_MESSAGES.loading
  );

  const videoRef = useRef(null);
  const canvasRef = useRef(null);
  const detectionCanvasRef = useRef(null);
  const streamRef = useRef(null);
  const detectionIntervalRef = useRef(null);
  const trackingIntervalRef = useRef(null);
  const trackedCardsRef = useRef([]);
  const nextCardIdRef = useRef(1);
  const isStreamingRef = useRef(false);
  const activeFacingModeRef = useRef(activeFacingMode);
  const facingModeInitializedRef = useRef(false);
  const selectedDeviceIdRef = useRef(selectedDeviceId);
  const selectedDeviceIdInitializedRef = useRef(false);

  useEffect(() => {
    isStreamingRef.current = isStreaming;
  }, [isStreaming]);

  useEffect(() => {
    activeFacingModeRef.current = activeFacingMode;
  }, [activeFacingMode]);

  useEffect(() => {
    selectedDeviceIdRef.current = selectedDeviceId;
  }, [selectedDeviceId]);

  const persistSelectedDeviceId = useCallback((deviceId) => {
    try {
      if (deviceId) {
        sessionStorage.setItem(SESSION_DEVICE_KEY, deviceId);
      } else {
        sessionStorage.removeItem(SESSION_DEVICE_KEY);
      }
    } catch {
      // sessionStorage may be unavailable in private mode or SSR
    }
  }, []);

  const setSelectedDeviceId = useCallback((deviceId) => {
    setSelectedDeviceIdState(deviceId);
    persistSelectedDeviceId(deviceId);
  }, [persistSelectedDeviceId]);

  const refreshVideoDevices = useCallback(async () => {
    if (!navigator.mediaDevices?.enumerateDevices) {
      setDevicePickerStatus('error');
      setDevicePickerMessage(DEVICE_PICKER_MESSAGES.error);
      return;
    }

    try {
      const devices = await navigator.mediaDevices.enumerateDevices();
      const videoInputs = devices.filter((device) => device.kind === 'videoinput');
      setVideoDevices(videoInputs);

      if (videoInputs.length === 0) {
        setDevicePickerStatus('empty');
        setDevicePickerMessage(DEVICE_PICKER_MESSAGES.empty);
        return;
      }

      const availableIds = videoInputs.map((device) => device.deviceId);
      const currentId = selectedDeviceIdRef.current;
      if (currentId && !availableIds.includes(currentId)) {
        const fallbackId = videoInputs[0]?.deviceId || '';
        selectedDeviceIdRef.current = fallbackId;
        setSelectedDeviceIdState(fallbackId);
        persistSelectedDeviceId(fallbackId);
      }

      setDevicePickerStatus('ready');
      setDevicePickerMessage(DEVICE_PICKER_MESSAGES.ready);
    } catch (err) {
      console.error('enumerateDevices failed:', err);
      setDevicePickerStatus('error');
      setDevicePickerMessage(DEVICE_PICKER_MESSAGES.error);
    }
  }, [persistSelectedDeviceId]);

  useEffect(() => {
    if (canvasRef.current) {
      canvasRef.current.getContext('2d', { willReadFrequently: true });
    }
    if (detectionCanvasRef.current) {
      detectionCanvasRef.current.getContext('2d', { willReadFrequently: true });
    }
  }, []);

  const detectCardShapes = () => {
    if (!videoRef.current || !detectionCanvasRef.current || !isStreamingRef.current) return [];
    return detectCardShapesFromFrame(videoRef.current, detectionCanvasRef.current);
  };

  const updateTrackedCards = (detectedShapes) => {
    const { cards, nextCardId } = mergeDetectedShapesIntoTrackedCards(
      trackedCardsRef.current,
      detectedShapes,
      nextCardIdRef.current
    );
    nextCardIdRef.current = nextCardId;
    trackedCardsRef.current = cards;
    setTrackedCards(cards);
  };

  const stopDetection = () => {
    console.log('🛑 Stopping card detection...');
    setIsDetecting(false);

    if (detectionIntervalRef.current) {
      clearInterval(detectionIntervalRef.current);
      detectionIntervalRef.current = null;
    }

    if (trackingIntervalRef.current) {
      clearInterval(trackingIntervalRef.current);
      trackingIntervalRef.current = null;
    }

    trackedCardsRef.current = [];
    setTrackedCards([]);
  };

  const removeTrackedCard = useCallback((cardId) => {
    const next = trackedCardsRef.current.filter((card) => card.id !== cardId);
    trackedCardsRef.current = next;
    setTrackedCards(next);
  }, []);

  const resetTrackedCard = useCallback((cardId) => {
    const next = trackedCardsRef.current.map((card) => (
      card.id === cardId
        ? { ...card, status: 'detecting', scanAttempts: 0, negativeAt: undefined }
        : card
    ));
    trackedCardsRef.current = next;
    setTrackedCards(next);
  }, []);

  const resetScanTrackers = useCallback(() => {
    trackedCardsRef.current = [];
    setTrackedCards([]);
    nextCardIdRef.current = 1;
  }, []);

  /**
   * Manual scan: fresh detection pass, then verify the best candidate (or guide bounds).
   * @returns {{ ok: boolean, reason?: string }}
   */
  const triggerManualScan = useCallback(
    (guideVideoBounds = null) => {
      if (!isStreamingRef.current) {
        return { ok: false, reason: 'not_streaming' };
      }

      if (verificationPausedRef?.current) {
        return { ok: false, reason: 'paused' };
      }

      resetScanTrackers();

      const shapes = detectCardShapes();
      updateTrackedCards(shapes);

      let target = trackedCardsRef.current
        .filter((card) => card.status === 'detecting')
        .sort((a, b) => b.stableCount - a.stableCount)[0];

      if (!target && guideVideoBounds?.width > 0 && guideVideoBounds?.height > 0) {
        target = {
          id: nextCardIdRef.current++,
          bounds: guideVideoBounds,
          corners: null,
          status: 'detecting',
          firstSeen: Date.now(),
          lastSeen: Date.now(),
          stableCount: MIN_STABLE_COUNT_FOR_VERIFY,
          scanAttempts: 0,
        };
        trackedCardsRef.current = [target];
        setTrackedCards([target]);
      }

      if (!target) {
        return { ok: false, reason: 'no_card' };
      }

      onVerifyCard?.(target);
      return { ok: true };
    },
    [onVerifyCard, resetScanTrackers, verificationPausedRef]
  );

  const startDetection = () => {
    if (detectionIntervalRef.current || !isStreamingRef.current) return;

    setIsDetecting(true);

    detectionIntervalRef.current = setInterval(() => {
      if (document.hidden) return;
      const shapes = detectCardShapes();
      updateTrackedCards(shapes);
    }, SHAPE_DETECTION_INTERVAL_MS);

    trackingIntervalRef.current = setInterval(() => {
      if (document.hidden) return;
      if (verificationPausedRef?.current) return;
      if (autoDetectPausedRef?.current) return;

      const cardsToVerify = selectCardsReadyForVerification(trackedCardsRef.current);
      cardsToVerify.slice(0, 1).forEach((card) => {
        onVerifyCard?.(card);
      });
    }, VERIFICATION_INTERVAL_MS);
  };

  const stopCamera = () => {
    setIsStreaming(false);
    isStreamingRef.current = false;
    stopDetection();

    if (streamRef.current) {
      streamRef.current.getTracks().forEach((track) => track.stop());
      streamRef.current = null;
    }

    if (videoRef.current) {
      videoRef.current.srcObject = null;
    }
  };

  const buildVideoConstraints = () => {
    const deviceId = selectedDeviceIdRef.current;
    if (deviceId) {
      return {
        deviceId: { exact: deviceId },
        width: { ideal: 1280 },
        height: { ideal: 720 },
      };
    }

    return {
      facingMode: activeFacingModeRef.current,
      width: { ideal: 1280 },
      height: { ideal: 720 },
      aspectRatio: { ideal: 16 / 9 },
    };
  };

  const startCamera = async () => {
    try {
      console.log('🎥 Starting camera...');
      const stream = await navigator.mediaDevices.getUserMedia({
        video: buildVideoConstraints(),
      });

      console.log('📹 Camera stream obtained:', stream);

      if (!videoRef.current) {
        console.error('❌ Video element not available');
        onError?.('Video element not available');
        return;
      }

      videoRef.current.srcObject = stream;
      streamRef.current = stream;

      videoRef.current.onloadedmetadata = () => {
        console.log('📺 Video metadata loaded, attempting to play...');
        videoRef.current?.play().then(() => {
          console.log('▶️ Video playback started successfully');
          setIsStreaming(true);
          isStreamingRef.current = true;
          refreshVideoDevices();
        }).catch((err) => {
          console.error('❌ Video playback failed:', err);
          onError?.(`Video playback failed: ${err.message}`);
        });
      };

      videoRef.current.onerror = (err) => {
        console.error('❌ Video element error:', err);
        onError?.('Video element error occurred');
      };

      setTimeout(() => {
        if (!isStreamingRef.current && videoRef.current?.readyState >= 2) {
          console.log('🔄 Fallback: Attempting to play video directly...');
          videoRef.current.play().then(() => {
            console.log('▶️ Fallback video playback started');
            setIsStreaming(true);
            isStreamingRef.current = true;
            refreshVideoDevices();
          }).catch(console.error);
        }
      }, 2000);
    } catch (err) {
      console.error('❌ Camera access error:', err);
      if (err?.name === 'NotAllowedError') {
        setDevicePickerStatus('denied');
        setDevicePickerMessage(DEVICE_PICKER_MESSAGES.denied);
      }
      onError?.(`Unable to access camera: ${err.message}`);
    }
  };

  useEffect(() => {
    const video = videoRef.current;
    if (!video || !isStreaming) return undefined;

    const syncVideoMetrics = () => {
      setVideoMetrics({
        width: video.videoWidth || 0,
        height: video.videoHeight || 0,
      });
    };

    video.addEventListener('loadedmetadata', syncVideoMetrics);
    video.addEventListener('resize', syncVideoMetrics);
    syncVideoMetrics();

    return () => {
      video.removeEventListener('loadedmetadata', syncVideoMetrics);
      video.removeEventListener('resize', syncVideoMetrics);
    };
  }, [isStreaming]);

  useEffect(() => {
    if (!isStreaming || isDetecting) return undefined;

    const timerId = setTimeout(() => {
      startDetection();
    }, DETECTION_START_DELAY_MS);

    return () => clearTimeout(timerId);
    // eslint-disable-next-line react-hooks/exhaustive-deps -- start detection once per stream session
  }, [isStreaming]);

  useEffect(() => {
    if (!isStreaming) return undefined;
    import('./ocr-worker.js').catch(() => {});
    return undefined;
  }, [isStreaming]);

  useEffect(() => {
    return () => {
      stopCamera();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps -- run stopCamera only on unmount
  }, []);

  const switchFacingMode = useCallback(() => {
    setActiveFacingMode((prev) => (prev === 'environment' ? 'user' : 'environment'));
  }, []);

  useEffect(() => {
    if (!facingModeInitializedRef.current) {
      facingModeInitializedRef.current = true;
      return;
    }

    if (!isStreamingRef.current) return;

    stopCamera();
    startCamera();
    // eslint-disable-next-line react-hooks/exhaustive-deps -- restart stream when facing mode toggles
  }, [activeFacingMode]);

  useEffect(() => {
    if (!selectedDeviceIdInitializedRef.current) {
      selectedDeviceIdInitializedRef.current = true;
      return;
    }

    if (!isStreamingRef.current) return;

    stopCamera();
    startCamera();
    // eslint-disable-next-line react-hooks/exhaustive-deps -- restart stream when selected device changes
  }, [selectedDeviceId]);

  return {
    videoRef,
    canvasRef,
    detectionCanvasRef,
    isStreaming,
    isDetecting,
    trackedCards,
    videoMetrics,
    startCamera,
    stopCamera,
    removeTrackedCard,
    resetTrackedCard,
    resetScanTrackers,
    triggerManualScan,
    streamRef,
    facingMode: activeFacingMode,
    switchFacingMode,
    videoDevices,
    selectedDeviceId,
    setSelectedDeviceId,
    devicePickerStatus,
    devicePickerMessage,
  };
}
