import {type ColorValue, NativeModules, processColor} from "react-native";
import type {ImageData, Quadrilateral, EnumImageFileFormat, DSRect} from "../core";
import type {EnumFilterType} from "./EnumFilterType";

// @ts-ignore Check whether __turboModuleProxy exists, it may not
const isTurboModuleEnabled = global.__turboModuleProxy != null;

const DynamsoftImageManagerModule = isTurboModuleEnabled ?
  require("./NativeDynamsoftImageManagerModule").default :
  NativeModules.DynamsoftImageManagerModule;

declare global {
  function imageIO_saveToFile(imageData: ImageData, path: string, overWrite: boolean): void;
  function imageIO_readFromFile(path: string): ImageData;
  function imageIO_readFromMemory(data: ArrayBuffer): ImageData;
  function imageIO_saveToMemory(imageData: ImageData, format: number): ArrayBuffer;
  function imageDrawer_drawOnImage(imageData: ImageData, quads: Quadrilateral[], colour: number, thickness: number): ImageData;
  function imageProcessor_cropImage(imageData: ImageData, rect: DSRect): ImageData | undefined;
  function imageProcessor_cropAndDeskewImage(imageData: ImageData, quad: Quadrilateral, dstWidth: number, dstHeight: number, padding: number): ImageData | undefined;
  function imageProcessor_adjustBrightness(imageData: ImageData, brightness: number) : ImageData | undefined;
  function imageProcessor_adjustContrast(imageData: ImageData, contrast: number) : ImageData | undefined;
  function imageProcessor_filterImage(imageData: ImageData, filterType: number) : ImageData | undefined;
  function imageProcessor_convertToGray(imageData: ImageData, r: number, g: number, b: number) : ImageData | undefined;
  function imageProcessor_convertToBinaryGlobal(imageData: ImageData, threshold: number, invert: boolean) : ImageData | undefined;
  function imageProcessor_convertToBinaryLocal(imageData: ImageData, blockSize: number, compensation: number, invert: boolean): ImageData | undefined;

}

let isInstalled = false
const installMethods = () => {
  if (!isInstalled) {
    DynamsoftImageManagerModule.install()
    isInstalled = true
  }
}

/**
 * The ImageManager class is a utility class for managing and manipulating images.
 * It provides functionality for saving images to files and drawing various shapes on images.
 * @deprecated use {@link ImageIO}, {@link ImageDrawer}, {@link ImageProcessor} instead
 * */
export class ImageManager {
  /**
   * Saves an image to the specified path and format.
   * The desired file format is inferred from the file extension provided in the path parameter.
   *
   * @param imageData - The image to be saved, of type {@link ImageData}.
   * @param path - The absolute file path, name and extension name, as a string, under which the image will be saved.
   * @param overWrite - A flag indicating whether to overwrite the file if it already exists. Defaults to true.
   * @deprecated use {@link ImageIO.saveToFile} instead
   * */
  saveToFile(imageData: ImageData | null | undefined, path: string | null | undefined, overWrite: boolean): void {
    new ImageIO().saveToFile(imageData, path, overWrite);
  }

  /**
   * Add quadrilaterals on the image.
   *
   * @param imageData - The {@link ImageData} to modify.
   * @param quads - An array of {@link Quadrilateral} objects to be added on the image.
   * @param colour - An ColorValue as an ARGB colour.
   * @param thickness - The width of the border.
   *
   * @return {ImageData|null|undefined} - The modified ImageData.
   * @deprecated use {@link ImageDrawer.drawOnImage} instead
   * */
  drawOnImage(imageData: ImageData | null | undefined, quads: Quadrilateral[] | null | undefined, colour: number | ColorValue, thickness: number): ImageData | null | undefined {
    return new ImageDrawer().drawOnImage(imageData, quads, colour, thickness);
  }


  /**
   * Crops an image using a quadrilateral.
   * @param {ImageData} imageData - The image data to crop.
   * @param {Quadrilateral} quad - The quadrilateral to use for cropping.
   *
   * @returns {ImageData|null|undefined} - The cropped image data.
   * @deprecated use {@link ImageProcessor.cropAndDeskewImage} instead
   * */
  cropImage(imageData: ImageData | null | undefined, quad: Quadrilateral | null | undefined): ImageData | undefined | null {
    return new ImageProcessor().cropAndDeskewImage(imageData, quad);
  }
}

export class ImageIO {

  /**
   * Reads an image from the specified path.
   *
   * @param path - The absolute file path, name and extension name, as a string, of the image to be read.
   *
   * @return {ImageData|null|undefined} - The image data, of type {@link ImageData}, or null if the image cannot be read.
   * */
  readFromFile(path: string | null | undefined): ImageData | null | undefined {
    installMethods();
    if (typeof global.imageIO_readFromFile !== 'function') {
      return null;
    }
    if (path && path.length > 0) {
      return global.imageIO_readFromFile(path);
    }
    return null;
  }

  /**
   * Saves an image to the specified path and format.
   * The desired file format is inferred from the file extension provided in the path parameter.
   *
   * @param imageData - The image to be saved, of type {@link ImageData}.
   * @param path - The absolute file path, name and extension name, as a string, under which the image will be saved.
   * @param overWrite - A flag indicating whether to overwrite the file if it already exists. Defaults to true.
   * */
  saveToFile(imageData: ImageData | null | undefined, path: string | null | undefined, overWrite: boolean): void {
    installMethods();
    if (typeof global.imageIO_saveToFile !== 'function') {
      return;
    }
    if (imageData && path) {
      if (path.length > 0) {
        global.imageIO_saveToFile(imageData, path, overWrite);
      }
    }
  }

  /**
   * Reads an image from ArrayBuffer memory.
   *
   * @param data - The image data, of type ArrayBuffer.
   *
   * @return {ImageData|null|undefined} - The image data, of type {@link ImageData}, or null if the image cannot be read.
   * */
  readFromMemory(data: ArrayBuffer | null | undefined): ImageData | null | undefined {
    installMethods();
    if (typeof global.imageIO_readFromMemory !== 'function') {
      return null;
    }
    if (data) {
      return global.imageIO_readFromMemory(data);
    }
    return null;
  }

  /**
   * Saves an image to the specified path and format.
   * The desired file format is inferred from the file extension provided in the path parameter.
   *
   * @param imageData - The image to be saved, of type {@link ImageData}.
   * @param format - The format of the image data, of type {@link EnumImageFileFormat}.
   * */
  saveToMemory(imageData: ImageData | null | undefined, format: EnumImageFileFormat): ArrayBuffer | null | undefined {
    installMethods();
    if (typeof global.imageIO_saveToMemory !== 'function') {
      return null;
    }
    if (imageData) {
      return global.imageIO_saveToMemory(imageData, format);
    }
    return null;
  }

}

export class ImageDrawer {
  /**
   * Add quadrilaterals on the image.
   *
   * @param imageData - The {@link ImageData} to modify.
   * @param quads - An array of {@link Quadrilateral} objects to be added on the image.
   * @param colour - An ColorValue as an ARGB colour.
   * @param thickness - The width of the border.
   *
   * @return {ImageData|null|undefined} - The modified ImageData.
   * */
  drawOnImage(imageData: ImageData | null | undefined, quads: Quadrilateral[] | null | undefined, colour: number | ColorValue, thickness: number): ImageData | null | undefined {
    installMethods();
    if (typeof global.imageDrawer_drawOnImage !== 'function') {
      return imageData;
    }
    if (imageData && quads) {
      const colorNumber = processColor(colour) as number;
      if (colorNumber) {
        const alpha = (colorNumber >> 24) & 0xff;
        if (quads.length == 0 || thickness <= 0 || alpha == 0) {
          return imageData;
        }
        return global.imageDrawer_drawOnImage(imageData, quads, colorNumber, thickness);
      }
    }
    return imageData;
  }
}

export class ImageProcessor {

  /**
   * Crops an image using a rectangle.
   * @param {ImageData} imageData - The image data to crop.
   * @param {DSRect} rect - The rectangle to use for cropping.
   *
   * @returns {ImageData|null|undefined} - The cropped image data.
   * */
  cropImage(imageData: ImageData | null | undefined, rect: DSRect | null | undefined): ImageData | null | undefined {
    installMethods()
    if (!rect || !imageData) {
      return imageData;
    }
    // convert to boolean
    rect.measuredInPercentage = !!rect.measuredInPercentage;
    if (typeof global.imageProcessor_cropImage !== 'function') {
      return imageData;
    }
    return global.imageProcessor_cropImage(imageData, rect);
  }


  /**
   * Crops an image using a quadrilateral.
   * @param {ImageData} imageData - The image data to crop.
   * @param {Quadrilateral} quad - The quadrilateral to use for cropping.
   * @param {number} dstWidth - The width of the cropped image.
   * @param {number} dstHeight - The height of the cropped image.
   * @param {number} padding - The padding size.
   *
   * @returns {ImageData|null|undefined} - The cropped image data.
   * */
  cropAndDeskewImage(imageData: ImageData | null | undefined, quad: Quadrilateral | null | undefined, dstWidth: number = 0, dstHeight: number = 0, padding: number = 0): ImageData | undefined | null {
    installMethods();
    if (!quad || !imageData) {
      return imageData;
    }
    if (typeof global.imageProcessor_cropAndDeskewImage !== 'function') {
      return imageData;
    }
    return global.imageProcessor_cropAndDeskewImage(imageData, quad, dstWidth, dstHeight, padding);
  }

  /**
   * Adjusts the brightness of an image.
   *
   * @param imageData - The image data to adjust the brightness of.
   * @param brightness - The brightness adjustment value. Must be between -100 and 100.
   *
   * @return {ImageData|null|undefined} - The adjusted image data.
   * */
  adjustBrightness(imageData: ImageData | null | undefined, brightness: number): ImageData | null | undefined {
    installMethods();
    if(!imageData || brightness < -100 || brightness > 100) {
      return imageData;
    }
    if (typeof global.imageProcessor_adjustBrightness !== 'function') {
      return imageData;
    }
    return global.imageProcessor_adjustBrightness(imageData, brightness);
  }

  /**
   * Adjusts the contrast of an image.
   *
   * @param imageData - The image data to adjust the contrast of.
   * @param contrast - The contrast adjustment value. Must be between -100 and 100.
   *
   * @return {ImageData|null|undefined} - The adjusted image data.
   * */
  adjustContrast(imageData: ImageData | null | undefined, contrast: number): ImageData | null | undefined {
    installMethods();
    if(!imageData || contrast < -100 || contrast > 100) {
      return imageData;
    }
    if (typeof global.imageProcessor_adjustContrast !== 'function') {
      return imageData;
    }
    return global.imageProcessor_adjustContrast(imageData, contrast);
  }

  filterImage(imageData: ImageData | null | undefined, filterType: EnumFilterType): ImageData | null | undefined {
    installMethods();
    if(!imageData) {
      return imageData;
    }
    if (typeof global.imageProcessor_filterImage !== 'function') {
      return imageData;
    }
    return global.imageProcessor_filterImage(imageData, filterType);
  }

  /**
   * Converts an image to grayscale.
   *
   * @param imageData - The image data to convert to grayscale.
   * @param r - The weight of the red channel. Must be between 0 and 1.
   * @param g - The weight of the green channel. Must be between 0 and 1.
   * @param b - The weight of the blue channel. Must be between 0 and 1.
   *
   * @return {ImageData|null|undefined} - The grayscale image data.
   * */
  convertToGray(imageData: ImageData | null | undefined, r: number = 0.3, g: number = 0.59, b: number = 0.11): ImageData | null | undefined {
    installMethods();
    if(!imageData || r < 0 || r > 1 || g < 0 || g > 1 || b < 0 || b > 1) {
      return imageData;
    }
    if (typeof global.imageProcessor_convertToGray !== 'function') {
      return imageData;
    }
    return global.imageProcessor_convertToGray(imageData, r, g, b);
  }

  /**
   * Converts an image to binary using a global threshold.
   *
   * @param imageData - The image data to convert to binary.
   * @param threshold - The threshold value. Must be between -1 and 255.(default is -1, automatic calculate the threshold).
   * @param invert - If true, invert the binary image (black becomes white and white becomes black).
   *
   * @return {ImageData|null|undefined} - The binary image data.
   * */
  convertToBinaryGlobal(imageData: ImageData | null | undefined, threshold: number = -1, invert: boolean = false) {
    installMethods();
    if(!imageData || threshold < -1 || threshold > 255) {
      return imageData;
    }
    if (typeof global.imageProcessor_convertToBinaryGlobal !== 'function') {
      return imageData;
    }
    return global.imageProcessor_convertToBinaryGlobal(imageData, threshold, invert);
  }


  /**
   * Converts an image to binary using a local threshold.
   *
   * @param imageData - The image data to convert to binary.
   * @param blockSize - Size of the block for local binarization(default is 0).
   * @param compensation - Adjustment value to modify the threshold (default is 0).
   * @param invert - If true, invert the binary image (black becomes white and white becomes black).
   *
   * @return {ImageData|null|undefined} - The binary image data.
   * */
  convertToBinaryLocal(imageData: ImageData | null | undefined, blockSize: number = 0, compensation: number = 0, invert: boolean = false) {
    installMethods();
    if(!imageData) {
      return imageData;
    }
    if (typeof global.imageProcessor_convertToBinaryLocal !== 'function') {
      return imageData;
    }
    return global.imageProcessor_convertToBinaryLocal(imageData, blockSize, compensation, invert);
  }
}
