import { JimpClass } from "@jimp/types";
import { HorizontalAlign, VerticalAlign } from "@jimp/core";
import { clone } from "@jimp/utils";
import { ResizeStrategy, methods as resizeMethods } from "@jimp/plugin-resize";
import { methods as blitMethods } from "@jimp/plugin-blit";
import { z } from "zod";

const ContainOptionsSchema = z.object({
  /** the width to resize the image to */
  w: z.number(),
  /** the height to resize the image to */
  h: z.number(),
  /** A bitmask for horizontal and vertical alignment */
  align: z.number().optional(),
  /** a scaling method (e.g. Jimp.RESIZE_BEZIER) */
  mode: z.nativeEnum(ResizeStrategy).optional(),
});

export type ContainOptions = z.infer<typeof ContainOptionsSchema>;

export const methods = {
  /**
   * Scale the image to the given width and height keeping the aspect ratio. Some parts of the image may be letter boxed.
   * @param w the width to resize the image to
   * @param h the height to resize the image to
   * @param align A bitmask for horizontal and vertical alignment
   * @param mode a scaling method (e.g. Jimp.RESIZE_BEZIER)
   * @example
   * ```ts
   * import { Jimp } from "jimp";
   *
   * const image = await Jimp.read("test/image.png");
   *
   * image.contain({ w: 150, h: 100 });
   * ```
   */
  contain<I extends JimpClass>(image: I, options: ContainOptions) {
    const {
      w,
      h,
      align = HorizontalAlign.CENTER | VerticalAlign.MIDDLE,
      mode,
    } = ContainOptionsSchema.parse(options);

    const hbits = align & ((1 << 3) - 1);
    const vbits = align >> 3;

    // check if more flags than one is in the bit sets
    if (
      !(
        (hbits !== 0 && !(hbits & (hbits - 1))) ||
        (vbits !== 0 && !(vbits & (vbits - 1)))
      )
    ) {
      throw new Error("only use one flag per alignment direction");
    }

    const alignH = hbits >> 1; // 0, 1, 2
    const alignV = vbits >> 1; // 0, 1, 2

    const f =
      w / h > image.bitmap.width / image.bitmap.height
        ? h / image.bitmap.height
        : w / image.bitmap.width;

    const c = resizeMethods.scale(clone(image), { f, mode });

    image = resizeMethods.resize(image, { w, h, mode });

    image.scan((_, __, idx) => {
      image.bitmap.data.writeUInt32BE(image.background, idx);
    });

    image = blitMethods.blit(image, {
      src: c,
      x: ((image.bitmap.width - c.bitmap.width) / 2) * alignH,
      y: ((image.bitmap.height - c.bitmap.height) / 2) * alignV,
    });

    return image;
  },
};
