import Quagga, {
  QuaggaJSCodeReader,
  QuaggaJSResultObject,
} from "@ericblade/quagga2";
import { useState, useCallback, useEffect } from "react";

const constraints = {
  width: 640,
  height: 480,
};

const locator = {
  patchSize: "medium",
  halfSample: true,
  willReadFrequently: true,
};

const readers: QuaggaJSCodeReader[] = ["ean_reader"];

export const useScanner = ({
  onDetected,
}: {
  onDetected?: (result: string) => void;
}): {
  setScannerRef: (ref: HTMLDivElement | null) => void;
  scannerError: string | undefined;
} => {
  const [scannerError, setScannerError] = useState<string | undefined>();

  const [scannerRef, setScannerRef] = useState<HTMLDivElement | null>(null);

  const getMedian = useCallback((arr: (number | undefined)[]) => {
    const newArr = [...arr].filter((x) => typeof x !== "undefined");
    if (!newArr.length) {
      return;
    }
    newArr.sort((a, b) => a! - b!);
    const half = Math.floor(newArr.length / 2);
    if (newArr.length % 2 === 1) {
      return newArr[half];
    }
    return (newArr[half - 1]! + newArr[half]!) / 2;
  }, []);

  const getMedianOfCodeErrors = useCallback(
    (result: QuaggaJSResultObject) => {
      const errors = result.codeResult.decodedCodes.flatMap((x) => x.error);
      const medianOfErrors = getMedian(errors);
      return medianOfErrors;
    },
    [getMedian]
  );

  const errorCheck = useCallback(
    (result: QuaggaJSResultObject) => {
      if (!onDetected) {
        return;
      }
      const err = getMedianOfCodeErrors(result);

      if (!err) {
        return;
      }

      if (err < 0.1 && result.codeResult.code) {
        onDetected(result.codeResult.code);
      }
    },
    [getMedianOfCodeErrors, onDetected]
  );

  const handleProcessed = (result: QuaggaJSResultObject) => {
    const drawingCtx = Quagga.canvas.ctx.overlay;
    const drawingCanvas = Quagga.canvas.dom.overlay;
    drawingCtx.font = "24px Arial";
    drawingCtx.fillStyle = "green";
    const width = drawingCanvas.getAttribute("width");
    const height = drawingCanvas.getAttribute("height");

    if (result) {
      if (result.boxes && width && height) {
        drawingCtx.clearRect(0, 0, parseInt(width), parseInt(height));
        result.boxes
          .filter((box) => box !== result.box)
          .forEach((box) => {
            Quagga.ImageDebug.drawPath(box, { x: 0, y: 1 }, drawingCtx, {
              color: "purple",
              lineWidth: 2,
            });
          });
      }
      if (result.box) {
        Quagga.ImageDebug.drawPath(result.box, { x: 0, y: 1 }, drawingCtx, {
          color: "blue",
          lineWidth: 2,
        });
      }
      if (result.codeResult && result.codeResult.code) {
        drawingCtx.font = "24px Arial";
        drawingCtx.fillText(result.codeResult.code, 10, 20);
      }
    }
  };

  const getCameraId = useCallback(async () => {
    await navigator.mediaDevices.getUserMedia({
      video: true,
    });

    const devices = await navigator.mediaDevices
      .enumerateDevices()
      .then((ds) => ds.filter((d) => d.kind === "videoinput"));

    const back = devices.filter((d) => d.label === "Back Camera")[0];

    return !back ? devices[0].deviceId : back.deviceId;
  }, []);

  const initQuagga = useCallback(async () => {
    try {
      const cameraId = await getCameraId();

      if (!scannerRef || !cameraId) {
        console.log("fehlt", scannerRef, cameraId);
        return;
      }
      await Quagga.init(
        {
          inputStream: {
            type: "LiveStream",
            constraints: {
              ...constraints,
              ...(cameraId && { deviceId: cameraId }),
            },
            target: scannerRef,
            willReadFrequently: true,
          },
          locator,
          decoder: { readers },
          locate: true,
        },
        async (err) => {
          Quagga.onProcessed(handleProcessed);

          if (err) {
            return console.error("Error starting Quagga:", err);
          }
          if (scannerRef) {
            Quagga.start();
          }
        }
      );
      Quagga.onDetected(errorCheck);
    } catch (e: unknown) {
      setScannerError(JSON.stringify(e));
    }
  }, [errorCheck, getCameraId, scannerRef]);

  const stopQuagga = useCallback(async () => {
    await Quagga.CameraAccess.release();
    await Quagga.stop();
    Quagga.offDetected(errorCheck);
    Quagga.offProcessed(handleProcessed);
  }, [errorCheck]);

  useEffect(() => {
    const init = () => {
      initQuagga();
    };
    const disable = () => {
      stopQuagga();
    };

    if (scannerRef) {
      init();
    }
    return disable();
  }, [stopQuagga, initQuagga, scannerRef]);

  return {
    setScannerRef,
    scannerError,
  };
};