import type { BarcodeFormat, DetectedBarcode } from "barcode-detector/pure";

/*** select camera ***/
export interface Camera {
  label: string;
  constraints: {
    deviceId?: string;
    facingMode: string;
  };
}
export const defaultConstraintOptions: Array<Camera> = [
  { label: "rear camera", constraints: { facingMode: "environment" } },
  { label: "front camera", constraints: { facingMode: "user" } },
];

export async function getAvailableCameras(useDefault: boolean = false): Promise<Array<Camera>> {
  // NOTE: on iOS we can't invoke `enumerateDevices` before the user has given
  // camera access permission. `QrcodeStream` internally takes care of
  // requesting the permissions. The `camera-on` event should guarantee that this
  // has happened.
  const devices = await navigator.mediaDevices.enumerateDevices();
  const videoDevices = devices.filter(({ kind }) => kind === "videoinput");

  return [
    ...(useDefault ? defaultConstraintOptions : []),
    ...videoDevices.map(({ deviceId, label }) => ({
      label: `${label}`, //(ID: ${deviceId})
      constraints: { deviceId, facingMode: "custom" },
    })),
  ];
}

/*** track functons ***/
export function paintOutline(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) {
  for (const detectedCode of detectedCodes) {
    const [firstPoint, ...otherPoints] = detectedCode.cornerPoints;

    ctx.strokeStyle = "red";

    ctx.beginPath();
    ctx.moveTo(firstPoint.x, firstPoint.y);
    for (const { x, y } of otherPoints) {
      ctx.lineTo(x, y);
    }
    ctx.lineTo(firstPoint.x, firstPoint.y);
    ctx.closePath();
    ctx.stroke();
  }
}
export function paintBoundingBox(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) {
  for (const detectedCode of detectedCodes) {
    const {
      boundingBox: { x, y, width, height },
    } = detectedCode;

    ctx.lineWidth = 2;
    ctx.strokeStyle = "#007bff";
    ctx.strokeRect(x, y, width, height);
  }
}
export function paintCenterText(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) {
  for (const detectedCode of detectedCodes) {
    const { boundingBox, rawValue } = detectedCode;

    const centerX = boundingBox.x + boundingBox.width / 2;
    const centerY = boundingBox.y + boundingBox.height / 2;

    const fontSize = Math.max(12, (50 * boundingBox.width) / ctx.canvas.width);

    ctx.font = `bold ${fontSize}px sans-serif`;
    ctx.textAlign = "center";

    ctx.lineWidth = 3;
    ctx.strokeStyle = "#35495e";
    ctx.strokeText(detectedCode.rawValue, centerX, centerY);

    ctx.fillStyle = "#5cb984";
    ctx.fillText(rawValue, centerX, centerY);
  }
}
export const trackFunctionOptions = [
  { text: "nothing (default)", value: undefined },
  { text: "outline", value: paintOutline },
  { text: "centered text", value: paintCenterText },
  { text: "bounding box", value: paintBoundingBox },
  {
    text: "mixed",
    value: (detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) => {
      paintOutline(detectedCodes, ctx);
      paintCenterText(detectedCodes, ctx);
    },
  },
];

/*** barcode formats ***/
export const barcodeFormats: Array<BarcodeFormat> = [
  "aztec",
  "code_128",
  "code_39",
  "code_93",
  "codabar",
  "databar",
  "databar_expanded",
  "data_matrix",
  "dx_film_edge",
  "ean_13",
  "ean_8",
  "itf",
  "maxi_code",
  "micro_qr_code",
  "pdf417",
  "qr_code",
  "rm_qr_code",
  "upc_a",
  "upc_e",
  "linear_codes",
  "matrix_codes",
];

/*** error handling ***/
export function handleScannerError(err: Error) {
  let error = `[${err.name}]: `;

  if (err.name === "NotAllowedError") {
    error += "you need to grant camera access permission";
  } else if (err.name === "NotFoundError") {
    error += "no camera on this device";
  } else if (err.name === "NotSupportedError") {
    error += "secure context required (HTTPS, localhost)";
  } else if (err.name === "NotReadableError") {
    error += "is the camera already in use?";
  } else if (err.name === "OverconstrainedError") {
    error += "installed cameras are not suitable";
  } else if (err.name === "StreamApiNotSupportedError") {
    error += "Stream API is not supported in this browser";
  } else if (err.name === "InsecureContextError") {
    error += "Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.";
  } else {
    error += err.message;
  }

  return error;
}