export type PixelCrop = {
  x: number;
  y: number;
  width: number;
  height: number;
};

export type CropImageOptions = {
  rotation?: number;
  flipH?: boolean;
  flipV?: boolean;
  brightness?: number;
  contrast?: number;
  saturate?: number;
  mimeType?: string;
  fileName?: string;
  quality?: number;
};

function createImage(url: string): Promise<HTMLImageElement> {
  return new Promise((resolve, reject) => {
    const image = new Image();
    image.addEventListener('load', () => resolve(image));
    image.addEventListener('error', () =>
      reject(new Error('Failed to load image')),
    );
    // blob:/data: sources are same-origin and never taint the canvas, so we
    // only request CORS access for remote http(s) URLs.
    if (!url.startsWith('blob:') && !url.startsWith('data:')) {
      image.crossOrigin = 'anonymous';
    }
    image.src = url;
  });
}

function getRadianAngle(degrees: number): number {
  return (degrees * Math.PI) / 180;
}

/**
 * Bounding box of an image rotated by the given angle.
 */
function rotateSize(width: number, height: number, rotation: number) {
  const rotRad = getRadianAngle(rotation);
  return {
    width:
      Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height),
    height:
      Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height),
  };
}

/**
 * Renders the source image with rotation, flips and color adjustments, then
 * extracts the requested crop region and returns it as a File ready for upload
 * (File extends Blob, so it can be appended to FormData directly).
 *
 * Accepts a Blob (preferred: same-origin blob: URL, never taints the canvas)
 * or a URL string (falls back to a crossOrigin request).
 */
export async function getCroppedImg(
  source: Blob | string,
  pixelCrop: PixelCrop,
  options: CropImageOptions = {},
): Promise<File> {
  const {
    rotation = 0,
    flipH = false,
    flipV = false,
    brightness = 100,
    contrast = 100,
    saturate = 100,
    mimeType = 'image/png',
    fileName = 'image.png',
    quality = 0.92,
  } = options;

  const localUrl =
    typeof source === 'string' ? source : URL.createObjectURL(source);

  let image: HTMLImageElement;
  try {
    image = await createImage(localUrl);
  } finally {
    if (typeof source !== 'string') {
      URL.revokeObjectURL(localUrl);
    }
  }

  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  if (!ctx) {
    throw new Error('Failed to get 2D canvas context');
  }

  const rotRad = getRadianAngle(rotation);
  const { width: bBoxWidth, height: bBoxHeight } = rotateSize(
    image.width,
    image.height,
    rotation,
  );

  canvas.width = bBoxWidth;
  canvas.height = bBoxHeight;

  ctx.filter = `brightness(${brightness}%) contrast(${contrast}%) saturate(${saturate}%)`;
  ctx.translate(bBoxWidth / 2, bBoxHeight / 2);
  ctx.rotate(rotRad);
  ctx.scale(flipH ? -1 : 1, flipV ? -1 : 1);
  ctx.translate(-image.width / 2, -image.height / 2);
  ctx.drawImage(image, 0, 0);

  const croppedCanvas = document.createElement('canvas');
  const croppedCtx = croppedCanvas.getContext('2d');
  if (!croppedCtx) {
    throw new Error('Failed to get 2D canvas context');
  }

  croppedCanvas.width = Math.max(1, Math.round(pixelCrop.width));
  croppedCanvas.height = Math.max(1, Math.round(pixelCrop.height));

  croppedCtx.drawImage(
    canvas,
    pixelCrop.x,
    pixelCrop.y,
    pixelCrop.width,
    pixelCrop.height,
    0,
    0,
    pixelCrop.width,
    pixelCrop.height,
  );

  return new Promise<File>((resolve, reject) => {
    croppedCanvas.toBlob(
      (blob) => {
        if (!blob) {
          reject(new Error('Canvas is empty'));
          return;
        }
        resolve(new File([blob], fileName, { type: blob.type || mimeType }));
      },
      mimeType,
      quality,
    );
  });
}
