import { Html5Qrcode } from 'html5-qrcode';
import { FC, useCallback, useEffect, useRef, useState } from 'react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';

type QRScannerProps = {
  title: string;
  onScan: (data: string) => void;
  onCancel: () => void;
};

export const QRScanner: FC<QRScannerProps> = ({ title, onScan, onCancel }) => {
  const [isScanning, setIsScanning] = useState(false);
  const [hasMultipleCameras, setHasMultipleCameras] = useState(false);
  const [cameraId, setCameraId] = useState<string | null>(null);
  const [scannerId] = useState(
    `qr-reader-${Math.random().toString(36).substring(2, 9)}`,
  );
  const scannerRef = useRef<Html5Qrcode | null>(null);
  const scannerInitializedRef = useRef(false);

  // Check available cameras
  const checkForCameras = useCallback(async () => {
    try {
      const devices = await Html5Qrcode.getCameras();
      setHasMultipleCameras(devices.length > 1);
      if (devices.length > 0) {
        setCameraId(devices[devices.length - 1].id);
      } else {
        toast.error('No camera detected');
      }
    } catch (error) {
      console.error('Error checking cameras:', error);
      toast.error('Cannot access camera, please check permission settings');
      setHasMultipleCameras(false);
    }
  }, []);

  // init scanner
  const handleScan = useCallback(
    (data: string) => {
      if (scannerRef.current && scannerRef.current.isScanning) {
        scannerRef.current.stop().catch(console.error);
        scannerInitializedRef.current = false;
      }
      onScan(data);
      setIsScanning(false);
    },
    [onScan],
  );

  const initScanner = useCallback(async () => {
    if (!cameraId || scannerInitializedRef.current) return;

    try {
      // Ensure previous scanner is stopped
      if (scannerRef.current) {
        if (scannerRef.current.isScanning) {
          await scannerRef.current.stop();
        }
        scannerRef.current = null;
      }

      // Create new scanner instance
      const html5QrCode = new Html5Qrcode(scannerId);
      scannerRef.current = html5QrCode;

      // Start scanning
      await html5QrCode.start(
        cameraId,
        {
          fps: 10,
          qrbox: { width: 250, height: 250 },
          aspectRatio: 1,
          videoConstraints: {
            facingMode: 'environment',
            autoGainControl: true,
          },
        },
        (decodedText) => {
          handleScan(decodedText);
        },
        (errorMessage) => {
          if (!errorMessage.includes('NotFoundException')) {
            console.log('QR scan error:', errorMessage);
          }
        },
      );

      scannerInitializedRef.current = true;
    } catch (err) {
      console.error('Error initializing scanner:', err);
      toast.error(
        'Failed to start camera: ' +
          (err instanceof Error ? err.message : String(err)),
      );
      setIsScanning(false);
    }
  }, [cameraId, handleScan, scannerId]);

  // Switch camera
  const toggleCamera = useCallback(async () => {
    try {
      // Stop current scanner
      if (scannerRef.current && scannerRef.current.isScanning) {
        await scannerRef.current.stop();
        scannerInitializedRef.current = false;
      }

      // Get all cameras
      const cameras = await Html5Qrcode.getCameras();
      if (cameras.length > 1) {
        const currentIndex = cameras.findIndex(
          (camera) => camera.id === cameraId,
        );
        const nextIndex = (currentIndex + 1) % cameras.length;
        setCameraId(cameras[nextIndex].id);
      }
    } catch (error) {
      console.error('Error switching camera:', error);
      toast.error('Unable to switch camera, please try again');
    }
  }, [cameraId]);

  // handle close
  const handleStop = useCallback(() => {
    if (scannerRef.current && scannerRef.current.isScanning) {
      scannerRef.current.stop().catch(console.error);
      scannerInitializedRef.current = false;
    }
    setIsScanning(false);
    onCancel();
  }, [onCancel]);

  // 开始扫描
  const startScanning = useCallback(() => {
    setIsScanning(true);
    checkForCameras();
  }, [checkForCameras]);

  // when cameraId changes, init scanner
  useEffect(() => {
    if (isScanning && cameraId) {
      // add delay to ensure DOM is fully rendered
      const timer = setTimeout(() => {
        initScanner();
      }, 500);

      return () => clearTimeout(timer);
    }
  }, [isScanning, cameraId, initScanner]);

  // when component unmounts, clean up
  useEffect(() => {
    return () => {
      if (scannerRef.current && scannerRef.current.isScanning) {
        scannerRef.current.stop().catch(console.error);
      }
    };
  }, []);

  return (
    <div className="w-full flex items-center justify-center">
      {!isScanning ? (
        <Button
          className="bg-accent size-30"
          variant="outline"
          onClick={startScanning}
        >
          {title}
        </Button>
      ) : (
        <div className="bg-accent w-full border rounded-lg p-4">
          <div id={scannerId} style={{ width: '100%', minHeight: '300px' }} />

          <div className="mt-4 flex gap-2">
            {hasMultipleCameras && (
              <Button
                className="flex-1"
                variant="secondary"
                onClick={toggleCamera}
              >
                Switch Camera
              </Button>
            )}
            <Button
              className="flex-1"
              variant="destructive"
              onClick={handleStop}
            >
              Stop
            </Button>
          </div>
        </div>
      )}
    </div>
  );
};
