// Copyright 2018-2026 the Deno authors. MIT license.

import { core, internals, primordials } from "ext:core/mod.js";
import { ImageBitmap, op_create_image_bitmap } from "ext:core/ops";
const webidl = core.loadExtScript("ext:deno_webidl/00_webidl.js");
const { DOMException } = core.loadExtScript("ext:deno_web/01_dom_exception.js");
const { createFilteredInspectProxy } = core.loadExtScript(
  "ext:deno_web/01_console.js",
);
const { BlobPrototype } = core.loadExtScript("ext:deno_web/09_file.js");
const { sniffImage } = core.loadExtScript("ext:deno_web/01_mimesniff.js");
const {
  ObjectDefineProperty,
  ObjectPrototypeIsPrototypeOf,
  SymbolFor,
  TypedArrayPrototypeGetBuffer,
  TypedArrayPrototypeGetByteOffset,
  TypedArrayPrototypeGetLength,
  TypedArrayPrototypeGetSymbolToStringTag,
  Uint8Array,
  Uint8ClampedArray,
  PromiseReject,
  RangeError,
  ArrayPrototypeJoin,
} = primordials;
let _imageDataMod;
const loadImageData = () =>
  _imageDataMod ??
    (_imageDataMod = core.loadExtScript("ext:deno_web/16_image_data.js"));

webidl.converters["ImageOrientation"] = webidl.createEnumConverter(
  "ImageOrientation",
  [
    "from-image",
    "flipY",
  ],
);

webidl.converters["PremultiplyAlpha"] = webidl.createEnumConverter(
  "PremultiplyAlpha",
  [
    "none",
    "premultiply",
    "default",
  ],
);

webidl.converters["ColorSpaceConversion"] = webidl.createEnumConverter(
  "ColorSpaceConversion",
  [
    "none",
    "default",
  ],
);

webidl.converters["ResizeQuality"] = webidl.createEnumConverter(
  "ResizeQuality",
  [
    "pixelated",
    "low",
    "medium",
    "high",
  ],
);

webidl.converters["ImageBitmapOptions"] = webidl.createDictionaryConverter(
  "ImageBitmapOptions",
  [
    {
      key: "imageOrientation",
      converter: webidl.converters["ImageOrientation"],
      defaultValue: "from-image",
    },
    {
      key: "premultiplyAlpha",
      converter: webidl.converters["PremultiplyAlpha"],
      defaultValue: "default",
    },
    {
      key: "colorSpaceConversion",
      converter: webidl.converters["ColorSpaceConversion"],
      defaultValue: "default",
    },
    {
      key: "resizeWidth",
      converter: (v, prefix, context, opts) =>
        webidl.converters["unsigned long"](v, prefix, context, {
          ...opts,
          enforceRange: true,
        }),
    },
    {
      key: "resizeHeight",
      converter: (v, prefix, context, opts) =>
        webidl.converters["unsigned long"](v, prefix, context, {
          ...opts,
          enforceRange: true,
        }),
    },
    {
      key: "resizeQuality",
      converter: webidl.converters["ResizeQuality"],
      defaultValue: "low",
    },
  ],
);

const ImageBitmapPrototype = ImageBitmap.prototype;
const privateCustomInspect = SymbolFor("Deno.privateCustomInspect");
ObjectDefineProperty(ImageBitmapPrototype, privateCustomInspect, {
  __proto__: null,
  value(inspect, inspectOptions) {
    return inspect(
      createFilteredInspectProxy({
        object: this,
        evaluate: ObjectPrototypeIsPrototypeOf(ImageBitmapPrototype, this),
        keys: [
          "width",
          "height",
        ],
      }),
      inspectOptions,
    );
  },
});

function float16ToUnorm8(data) {
  const length = TypedArrayPrototypeGetLength(data);
  const result = new Uint8ClampedArray(length);
  for (let i = 0; i < length; i++) {
    result[i] = data[i] * 255;
  }
  return result;
}

function createImageBitmap(
  image,
  sxOrOptions = undefined,
  sy = undefined,
  sw = undefined,
  sh = undefined,
  options = undefined,
) {
  const prefix = "Failed to execute 'createImageBitmap'";
  // Add the value when implementing to add support for ImageBitmapSource
  const imageBitmapSources = [
    "Blob",
    "ImageData",
    "ImageBitmap",
  ];

  // Overload: createImageBitmap(image [, options ])
  if (arguments.length < 3) {
    options = webidl.converters["ImageBitmapOptions"](
      sxOrOptions,
      prefix,
      "Argument 2",
    );
  } else {
    // Overload: createImageBitmap(image, sx, sy, sw, sh [, options ])
    sxOrOptions = webidl.converters["long"](sxOrOptions, prefix, "Argument 2");
    sy = webidl.converters["long"](sy, prefix, "Argument 3");
    sw = webidl.converters["long"](sw, prefix, "Argument 4");
    sh = webidl.converters["long"](sh, prefix, "Argument 5");
    options = webidl.converters["ImageBitmapOptions"](
      options,
      prefix,
      "Argument 6",
    );

    // 1.
    if (sw === 0) {
      return PromiseReject(new RangeError("sw has to be greater than 0"));
    }

    if (sh === 0) {
      return PromiseReject(new RangeError("sh has to be greater than 0"));
    }
  }

  // 2.
  if (options.resizeWidth === 0) {
    return PromiseReject(
      new DOMException(
        "options.resizeWidth has to be greater than 0",
        "InvalidStateError",
      ),
    );
  }
  if (options.resizeHeight === 0) {
    return PromiseReject(
      new DOMException(
        "options.resizeHeight has to be greater than 0",
        "InvalidStateError",
      ),
    );
  }

  // 3.
  const isBlob = ObjectPrototypeIsPrototypeOf(BlobPrototype, image);
  const { ImageDataPrototype } = loadImageData();
  const isImageData = ObjectPrototypeIsPrototypeOf(ImageDataPrototype, image);
  const isImageBitmap = ObjectPrototypeIsPrototypeOf(
    ImageBitmapPrototype,
    image,
  );
  if (!isBlob && !isImageData && !isImageBitmap) {
    return PromiseReject(
      new DOMException(
        `${prefix}: The provided value for 'image' is not of type '(${
          ArrayPrototypeJoin(imageBitmapSources, " or ")
        })'`,
        "InvalidStateError",
      ),
    );
  }

  // 4.
  return (async () => {
    //
    // For performance reasons, the arguments passed to op are represented as numbers that don't need to be serialized.
    //

    let width = 0;
    let height = 0;
    // If the image doesn't have a MIME type, mark it as 0.
    let mimeType = 0;
    let imageBitmapSource, buf;
    if (isBlob) {
      imageBitmapSource = 0;
      buf = new Uint8Array(await image.arrayBuffer());
      // NOTE: The MIME type of image/svg+xml can't support
      // https://github.com/whatwg/html/pull/10172
      const mimeTypeString = sniffImage(null, buf);

      if (mimeTypeString === "image/png") {
        mimeType = 1;
      } else if (mimeTypeString === "image/jpeg") {
        mimeType = 2;
      } else if (mimeTypeString === "image/gif") {
        mimeType = 3;
      } else if (mimeTypeString === "image/bmp") {
        mimeType = 4;
      } else if (mimeTypeString === "image/x-icon") {
        mimeType = 5;
      } else if (mimeTypeString === "image/webp") {
        mimeType = 6;
      } else {
        return PromiseReject(
          new DOMException(
            `The the MIME type ${mimeTypeString} of source image is not a supported format\n
info: The following MIME types are supported.
docs: https://mimesniff.spec.whatwg.org/#image-type-pattern-matching-algorithm\n`,
            "InvalidStateError",
          ),
        );
      }
    } else if (isImageData) {
      width = image.width;
      height = image.height;
      imageBitmapSource = 1;
      let data = image.data;
      switch (TypedArrayPrototypeGetSymbolToStringTag(data)) {
        case "Float16Array":
          data = float16ToUnorm8(data);
          break;
      }
      buf = new Uint8Array(
        TypedArrayPrototypeGetBuffer(data),
        TypedArrayPrototypeGetByteOffset(data),
        TypedArrayPrototypeGetLength(data),
      );
    } else if (isImageBitmap) {
      width = image.width;
      height = image.height;
      imageBitmapSource = 2;
      buf = new Uint8Array(TypedArrayPrototypeGetBuffer(image[_bitmapData]()));
    }

    // If those options are not provided, assign 0 to mean undefined(None).
    const _sx = typeof sxOrOptions === "number" ? sxOrOptions : 0;
    const _sy = sy ?? 0;
    const _sw = sw ?? 0;
    const _sh = sh ?? 0;

    // If those options are not provided, assign 0 to mean undefined(None).
    const resizeWidth = options.resizeWidth ?? 0;
    const resizeHeight = options.resizeHeight ?? 0;

    // If the imageOrientation option is set "from-image" or not set, assign 0.
    const imageOrientation = options.imageOrientation === "flipY" ? 1 : 0;

    // If the premultiplyAlpha option is "default" or not set, assign 0.
    let premultiplyAlpha = 0;
    if (options.premultiplyAlpha === "premultiply") {
      premultiplyAlpha = 1;
    } else if (options.premultiplyAlpha === "none") {
      premultiplyAlpha = 2;
    }

    // If the colorSpaceConversion option is "default" or not set, assign 0.
    const colorSpaceConversion = options.colorSpaceConversion === "none"
      ? 1
      : 0;

    // If the resizeQuality option is "low" or not set, assign 0.
    let resizeQuality = 0;
    if (options.resizeQuality === "pixelated") {
      resizeQuality = 1;
    } else if (options.resizeQuality === "medium") {
      resizeQuality = 2;
    } else if (options.resizeQuality === "high") {
      resizeQuality = 3;
    }

    return op_create_image_bitmap(
      buf,
      width,
      height,
      _sx,
      _sy,
      _sw,
      _sh,
      imageOrientation,
      premultiplyAlpha,
      colorSpaceConversion,
      resizeWidth,
      resizeHeight,
      resizeQuality,
      imageBitmapSource,
      mimeType,
    );
  })();
}

const _bitmapData = SymbolFor("Deno_bitmapData");
function getBitmapData(imageBitmap) {
  return imageBitmap[_bitmapData]();
}

internals.getBitmapData = getBitmapData;

export { createImageBitmap, ImageBitmap };
