// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import type { LoadImageResult } from 'blueimp-load-image';
import loadImage from 'blueimp-load-image';

import type { MIMEType } from '../types/MIME.std.ts';
import { IMAGE_JPEG } from '../types/MIME.std.ts';
import { canvasToBlob } from './canvasToBlob.std.ts';
import { getValue } from '../RemoteConfig.dom.ts';
import { parseNumber } from './libphonenumberUtil.std.ts';
import { itemStorage } from '../textsecure/Storage.preload.ts';

enum MediaQualityLevels {
  One = 1,
  Two = 2,
  Three = 3,
}

const DEFAULT_LEVEL = MediaQualityLevels.One;

const MiB = 1024 * 1024;

const DEFAULT_LEVEL_DATA = {
  maxDimensions: 1600,
  quality: 0.7,
  size: MiB,
  thresholdSize: 0.2 * MiB,
};

const MEDIA_QUALITY_LEVEL_DATA = new Map([
  [MediaQualityLevels.One, DEFAULT_LEVEL_DATA],
  [
    MediaQualityLevels.Two,
    {
      maxDimensions: 2048,
      quality: 0.75,
      size: MiB * 1.5,
      thresholdSize: 0.3 * MiB,
    },
  ],
  [
    MediaQualityLevels.Three,
    {
      maxDimensions: 4096,
      quality: 0.75,
      size: MiB * 3,
      thresholdSize: 0.4 * MiB,
    },
  ],
]);

const SCALABLE_DIMENSIONS = [3072, 2048, 1600, 1024, 768];
const MIN_DIMENSIONS = 512;

function parseCountryValues(values: string): Map<string, MediaQualityLevels> {
  const map = new Map<string, MediaQualityLevels>();
  values.split(',').forEach(value => {
    const [countryCode, level] = value.split(':');
    map.set(
      countryCode,
      Number(level) === 2 ? MediaQualityLevels.Two : MediaQualityLevels.One
    );
  });
  return map;
}

function getMediaQualityLevel(): MediaQualityLevels {
  const values = getValue('desktop.mediaQuality.levels');
  if (!values) {
    return DEFAULT_LEVEL;
  }

  const e164 = itemStorage.user.getNumber();
  if (!e164) {
    return DEFAULT_LEVEL;
  }

  const parsedPhoneNumber = parseNumber(e164);
  if (!parsedPhoneNumber.isValidNumber) {
    return DEFAULT_LEVEL;
  }

  const countryValues = parseCountryValues(values);

  const level = parsedPhoneNumber.countryCode
    ? countryValues.get(parsedPhoneNumber.countryCode)
    : undefined;
  if (level) {
    return level;
  }

  return countryValues.get('*') || DEFAULT_LEVEL;
}

async function getCanvasBlobAsJPEG(
  image: HTMLCanvasElement,
  dimensions: number,
  quality: number
): Promise<Blob> {
  const canvas = loadImage.scale(image, {
    canvas: true,
    maxHeight: dimensions,
    maxWidth: dimensions,
  });
  if (!(canvas instanceof HTMLCanvasElement)) {
    throw new Error('image not a canvas');
  }
  return canvasToBlob(canvas, IMAGE_JPEG, quality);
}

export async function scaleImageToLevel({
  fileOrBlobOrURL,
  contentType,
  size,
  highQuality,
}: {
  fileOrBlobOrURL: File | Blob | string;
  contentType: MIMEType;
  size: number;
  highQuality: boolean | null;
}): Promise<{
  blob: Blob;
  contentType: MIMEType;
}> {
  let data: LoadImageResult;
  try {
    data = await loadImage(fileOrBlobOrURL, {
      canvas: true,
      orientation: true,
      meta: true, // Check if we need to strip EXIF data
    });
    if (!(data.image instanceof HTMLCanvasElement)) {
      throw new Error('image not a canvas');
    }
  } catch (cause) {
    const error = new Error('scaleImageToLevel: Failed to process image', {
      cause,
    });
    throw error;
  }

  const level = highQuality ? MediaQualityLevels.Three : getMediaQualityLevel();
  const {
    maxDimensions,
    quality,
    size: targetSize,
    thresholdSize,
  } = MEDIA_QUALITY_LEVEL_DATA.get(level) || DEFAULT_LEVEL_DATA;
  if (size <= thresholdSize) {
    // Always encode through canvas as a temporary fix for a library bug
    const blob: Blob = await canvasToBlob(data.image, contentType);
    return {
      blob,
      contentType,
    };
  }

  for (const scalableDimensions of SCALABLE_DIMENSIONS) {
    if (maxDimensions < scalableDimensions) {
      continue;
    }

    // We need these operations to be in serial
    // oxlint-disable-next-line no-await-in-loop
    const blob = await getCanvasBlobAsJPEG(
      data.image,
      scalableDimensions,
      quality
    );
    if (blob.size <= targetSize) {
      return {
        blob,
        contentType: IMAGE_JPEG,
      };
    }
  }

  const blob = await getCanvasBlobAsJPEG(data.image, MIN_DIMENSIONS, quality);
  return {
    blob,
    contentType: IMAGE_JPEG,
  };
}
