import sharp from "sharp";

const productDimensions = Object.freeze({
  "3:4": Object.freeze({ pixelHeight: 1440, pixelWidth: 1080 }),
  "1:1": Object.freeze({ pixelHeight: 1080, pixelWidth: 1080 }),
  "4:3": Object.freeze({ pixelHeight: 1080, pixelWidth: 1440 }),
  "9:16": Object.freeze({ pixelHeight: 1920, pixelWidth: 1080 }),
});

const gptImageRequestSizes = Object.freeze({
  "3:4": "1056x1408",
  "1:1": "1088x1088",
  "4:3": "1408x1056",
  "9:16": "1008x1792",
});

const allowedMimeTypes = new Set(["image/jpeg", "image/png", "image/webp"]);
const maximumInputBytes = 20 * 1024 * 1024;

function assertRatio(ratio) {
  if (!(ratio in productDimensions)) throw new Error("image_output_ratio_unsupported");
  return ratio;
}

export function productDimensionsForRatio(ratio) {
  return { ...productDimensions[assertRatio(ratio)] };
}

export function gptImageRequestSizeForRatio(ratio) {
  return gptImageRequestSizes[assertRatio(ratio)];
}

export async function normalizeImageOutputToRatio(input) {
  const ratio = assertRatio(input?.ratio);
  if (!Buffer.isBuffer(input?.bytes) || input.bytes.length === 0 || input.bytes.length > maximumInputBytes
    || !allowedMimeTypes.has(input?.mimeType)) {
    throw new Error("image_output_media_invalid");
  }
  const target = productDimensions[ratio];
  if (input.pixelWidth === target.pixelWidth && input.pixelHeight === target.pixelHeight) {
    return {
      bytes: Buffer.from(input.bytes),
      mimeType: input.mimeType,
      normalized: false,
      ...target,
      upstreamPixelHeight: input.pixelHeight,
      upstreamPixelWidth: input.pixelWidth,
    };
  }

  const image = sharp(input.bytes, { failOn: "error", limitInputPixels: 40_000_000 });
  const metadata = await image.metadata();
  if (!metadata.width || !metadata.height) throw new Error("image_output_dimensions_missing");
  const requestedRatio = target.pixelWidth / target.pixelHeight;
  const upstreamRatio = metadata.width / metadata.height;
  if (Math.abs(upstreamRatio - requestedRatio) / requestedRatio > 0.02) {
    throw new Error("image_output_aspect_ratio_mismatch");
  }
  const { data, info } = await image
    .resize(target.pixelWidth, target.pixelHeight, { fit: "fill", kernel: sharp.kernel.lanczos3 })
    .png({ compressionLevel: 9 })
    .toBuffer({ resolveWithObject: true });
  if (info.width !== target.pixelWidth || info.height !== target.pixelHeight || info.format !== "png") {
    throw new Error("image_output_normalization_failed");
  }
  return {
    bytes: data,
    mimeType: "image/png",
    normalized: true,
    ...target,
    upstreamPixelHeight: metadata.height,
    upstreamPixelWidth: metadata.width,
  };
}
