import type {DetectionStatus, ImageFilter} from '.';
import {DocumentDetectorMode} from './configuration';
import type {
  CameraImageFormat,
  LicenseStatus,
  FileEncryptionMode,
} from './enum';

export interface Point {
  x: number;
  y: number;
}

export interface Size {
  width: number;
  height: number;
}

export class MedicalCertificateStandardSize {
  /**
   * A5 portrait document (e.g. white sheet, AUB Muster 1b/E (1/2018))
   */
  static A5_PORTRAIT: Size = {width: 148.0, height: 210.0};
  /**
   * A6 landscape document (e.g. yellow sheet, AUB Muster 1b (1.2018))
   */
  static A6_LANDSCAPE: Size = {width: 148.0, height: 105.0};
}

export interface Page {
  /**
   * A string identifying the page in the internal page file storage
   */
  pageId: string;
  /**
   * The page's cropping polygon as calculated by a document detection operation
   * or as set by the cropping UI. Modifying the polygon will change the polygon as shown
   * in the cropping UI but will not automatically re-crop the original image
   */
  polygon: Point[];
  /**
   * The document detection result status for the operation that
   * produced the page (either the document scanner or `detectDocument()`)
   */
  detectionResult: DetectionStatus;
  /**
   * The image source (MANUAL_SNAP, AUTO_SNAP, CAMERA_FRAME) or UNKNOWN
   * if the image has been imported or the source is unknown.
   */
  pageImageSource: PageImageSource;
  /**
   * The Image Filter that was applied on the page image
   */
  filter: ImageFilter;
  /**
   * The value that was set for `documentImageSizeLimit`, which limits the maximum size of the document image.
   */
  documentImageSizeLimit?: Size;
  /**
   * File URI of the original image
   */
  originalImageFileUri: string;
  /**
   * File URI of the cropped document image (if document detection was successful)
   */
  documentImageFileUri?: string;
  /**
   * File URI of a screen-sized preview of the original image
   */
  originalPreviewImageFileUri: string;
  /**
   * File URI of a screen-sized preview of the document image
   */
  documentPreviewImageFileUri?: string;
}

export type PageImageSource =
  // Used by default. If the source is not defined. For example, an image was imported
  | 'UNKNOWN'
  // If the source image was captured manually. For example, when the user pressed a Snap button
  | 'MANUAL_SNAP'
  // If the source image was captured automatically. For example, by auto snapping functions
  | 'AUTO_SNAP'
  // If the source image was taken from the camera frame
  | 'CAMERA_FRAME';

export interface InitializationOptions {
  /**
   * The Scanbot SDK License Key
   */
  licenseKey: string;
  /**
   * If 'true' it enables logging.
   * Consider switching logging OFF in production builds for security and performance reasons!
   */
  loggingEnabled?: boolean;
  /**
   * The quality of stored images, from (1 to 100). Defaults to 80.
   */
  storageImageQuality?: number;
  /**
   * The preferred format for stored images.
   */
  storageImageFormat?: CameraImageFormat;
  /**
   * Optional custom storage path.
   */
  storageBaseDirectory?: string;
  /**
   * The engine used to detect documents.
   * The default and recommended value is ML_BASED.
   */
  documentDetectorMode?: DocumentDetectorMode;
  /**
   * Encryption custom password. Setting this property will enable encryption.
   */
  fileEncryptionPassword?: string;
  /**
   * Encryption mode. Setting this property will enable encryption.
   */
  fileEncryptionMode?: FileEncryptionMode;
  /**
   * If set to `true`, Camera X will be used for the RTU-UI components (Android Only)
   * Default is `false`.
   */
  useCameraX?: boolean;
  /**
   * If set to `false`, GPU Acceleration will be disabled for Barcode Scanner,
   * Document Scanner and Generic Document Recognizer (Android Only).
   * Default is `false`.
   */
  allowGpuAcceleration?: boolean;
  /**
   * Enables/disables XNNPACK acceleration for TensorFlow ML models, which provides
   * highly optimized implementations of floating-point neural network operators (Android Only)
   */
  allowXnnpackAcceleration?: boolean;

  /**
   * Enables Scanbot SDK Core native logging (default is false; Android Only)
   */
  enableNativeLogging?: boolean;
}

export interface LicenseInfo {
  isLicenseValid: boolean;
  licenseStatus: LicenseStatus;
  licenseExpirationDate?: number;
}

export interface FinderAspectRatio {
  width: number;
  height: number;
}

export type MedicalCertificateFormType =
  | '1A'
  | '1B'
  | '1C'
  | '1D'
  | '21A'
  | '1B_CUSTOM'
  | 'UNKNOWN';

export interface MedicalCertificateDateField {
  recognitionConfidence: number;
  dateString: string;
}

export interface MedicalCertificateCheckboxField {
  isChecked: boolean;
  confidence: number;
}

/**
 * Wraps a Javascript Function that is supposed to be
 * executed on the native layer.
 * Note: You MUST use JSTextFunctionBuilder to create this
 */
export type JSTextFunction<T> = {
  body: string;
  argsCount: number;
  _jsFunction?: T;
};

export class JSTextFunctionBuilder<CallbackType extends Function> {
  constructor(private callback: CallbackType) {}

  // Performs preprocessing on the function string representation.
  build(): JSTextFunction<CallbackType> {
    let fnStr = this.callback.toString();

    // Renames input arguments to standard sequential names (eg. __arg0__, __arg1__...)
    const params = this.getParamNames();
    params.forEach((param: string, index: number) => {
      const regex = new RegExp(`([^\\w])(${param})([^\\w])`, 'g');
      // eslint-disable-next-line no-useless-escape
      fnStr = fnStr.replace(regex, `\$1\_\_arg${index}\_\_\$3`);
    });

    // Gives a name to the function, so that it can be referenced from the native JSContext
    fnStr = fnStr.replace('function', 'function __execute__');

    return {
      body: fnStr,
      argsCount: params.length,
    };
  }

  // Returns the list of all the callback function arguments
  getParamNames(): string[] {
    const STRIP_COMMENTS =
      // eslint-disable-next-line no-useless-escape
      /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/gm;
    const ARGUMENT_NAMES = /([^\s,]+)/g;

    let fnStr = this.callback.toString().replace(STRIP_COMMENTS, '');
    let result: any | null = fnStr
      .slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')'))
      .match(ARGUMENT_NAMES);
    if (result === null) {
      result = [];
    }

    return result;
  }
}

export type JSStringToBoolTextFunction = JSTextFunction<
  (value: string) => boolean
>;
export type JSStringToStringTextFunction = JSTextFunction<
  (value: string) => string
>;
export class JSStringToBoolTextFunctionBuilder extends JSTextFunctionBuilder<
  (value: string) => boolean
> {}
export class JSStringToStringTextFunctionBuilder extends JSTextFunctionBuilder<
  (value: string) => string
> {}
