import { CommonFieldType } from './CommonFieldType';
import { DeepPartial, PartiallyConstructible, Point } from '../utils';

/**
 Result of OCR text recognition.
 */
export class OcrResult extends PartiallyConstructible {
  /**
   Maximum number of accumulated frames to inspect before actual result is returned.
   */
  public readonly text: string;
  /**
   Minimum number of accumulated frames that have equal result.
   */
  public readonly confidence: number;

  /** @param source {@displayType `DeepPartial<OcrResult>`} */
  public constructor(source: DeepPartial<OcrResult> = {}) {
    super();
    if (source.text !== undefined) {
      this.text = source.text;
    } else {
      throw new Error('text must be present in constructor argument');
    }
    if (source.confidence !== undefined) {
      this.confidence = source.confidence;
    } else {
      throw new Error('confidence must be present in constructor argument');
    }
  }
}

/**
 Field validation status.
 - `UNDEFINED`: Field value was not considered during validation. iOS only
 - `INVALID`: Field value failed validation.
 - `VALID`: Field value passed validation.
 - `CONFIRMED`: Field value was confirmed.
 */
export type ValidationStatus = 'UNDEFINED' | 'INVALID' | 'VALID' | 'CONFIRMED';

/**`
 Generic document field
 */
export class Field extends PartiallyConstructible {
  /**
   The type of the field.
   */
  public readonly type: Field.Type;
  /**
   Value of the field. Applicable only to text fields.
   */
  public readonly value: OcrResult | null;
  /**
   Coordinates of the field in the root document coordinate system. Android only.
   */
  public readonly polygonInRoot?: Point[];
  /**
   Field validation status. Applicable only to fields that support some kind of validation.
   */
  public readonly validationStatus: ValidationStatus | null;

  /** @param source {@displayType `DeepPartial<Field>`} */
  public constructor(source: DeepPartial<Field> = {}) {
    super();
    if (source.type !== undefined) {
      this.type = new Field.Type(source.type);
    } else {
      throw new Error('type must be present in constructor argument');
    }
    if (source.value !== undefined) {
      this.value = source.value != null ? new OcrResult(source.value) : null;
    } else {
      throw new Error('value must be present in constructor argument');
    }
    if (source.polygonInRoot !== undefined) {
      this.polygonInRoot = source.polygonInRoot.map((it) => new Point(it.x, it.y));
    }
    if (source.validationStatus !== undefined) {
      this.validationStatus = source.validationStatus != null ? source.validationStatus : null;
    } else {
      throw new Error('validationStatus must be present in constructor argument');
    }
  }
}

export namespace Field {
  /**
   Generic Document Type
   */
  export class Type extends PartiallyConstructible {
    /**
     Local field type name scoped to the containing document type
     */
    public readonly name: string;
    /**
     Unique global field type name prefixed with the document types of all containing documents
     */
    public readonly fullName: string;
    /**
     Normalized global field type name. Fields in document types derived from the same base document type in the schema will have the same normalized name.
     */
    public readonly normalizedName: string;
    /**
     Commonly occurring fields that have the same semantic meaning in different document types will often have a set common type.
     */
    public readonly commonType: CommonFieldType | null;
    /**
     The friendly, human-readable display name of this field type in English. iOS only.
     */
    public displayText?: string | null = null;
    /**
     A document can contain multiple fields of the same name, the property serves for storing natural order of such fields, null if multiple entries aren't allowed for this field
     */
    public readonly listIndex: number | null = null;

    /** @param source {@displayType `DeepPartial<Type>`} */
    public constructor(source: DeepPartial<Type> = {}) {
      super();
      if (source.name !== undefined) {
        this.name = source.name;
      } else {
        throw new Error('name must be present in constructor argument');
      }
      if (source.fullName !== undefined) {
        this.fullName = source.fullName;
      } else {
        throw new Error('fullName must be present in constructor argument');
      }
      if (source.normalizedName !== undefined) {
        this.normalizedName = source.normalizedName;
      } else {
        throw new Error('normalizedName must be present in constructor argument');
      }
      if (source.commonType !== undefined) {
        this.commonType = source.commonType != null ? source.commonType : null;
      } else {
        throw new Error('commonType must be present in constructor argument');
      }
      if (source.displayText !== undefined) {
        this.displayText = source.displayText;
      }
      if (source.listIndex !== undefined) {
        this.listIndex = source.listIndex != null ? source.listIndex : null;
      }
    }
  }
}

/**
 Generic document
 */
export class GenericDocument extends PartiallyConstructible {
  /**
   Document type
   */
  public readonly type: GenericDocument.Type;
  /**
   List of document fields
   */
  public readonly fields: Field[];
  /**
   List of document sub-documents
   */
  public readonly children: GenericDocument[];
  /**
   The average confidence in the accuracy of the document recognition result
   Default is 0
   */
  public readonly confidence: number = 0.0;
  /**
   The weight of the confidence. Can be used to calculate the weighted average confidence of two documents.
   Default is 0
   */
  public readonly confidenceWeight: number = 0.0;

  /** @param source {@displayType `DeepPartial<GenericDocument>`} */
  public constructor(source: DeepPartial<GenericDocument> = {}) {
    super();
    if (source.type !== undefined) {
      this.type = new GenericDocument.Type(source.type);
    } else {
      throw new Error('type must be present in constructor argument');
    }
    if (source.fields !== undefined) {
      this.fields = source.fields.map((it) => {
        return new Field(it);
      });
    } else {
      throw new Error('fields must be present in constructor argument');
    }
    if (source.children !== undefined) {
      this.children = source.children.map((it) => {
        return new GenericDocument(it);
      });
    } else {
      throw new Error('children must be present in constructor argument');
    }
    if (source.confidence !== undefined) {
      this.confidence = source.confidence;
    }
    if (source.confidenceWeight !== undefined) {
      this.confidenceWeight = source.confidenceWeight;
    }
  }
}

export namespace GenericDocument {
  /**
   Generic Document Type
   */
  export class Type extends PartiallyConstructible {
    /**
     Local document type name
     */
    public readonly name: string;
    /**
     Unique global document type name prefixed with the document types of all containing documents
     */
    public readonly fullName: string;
    /**
     Normalized global document type name. Common document types appearing as child documents in different places will often have the same normalized type name.
     */
    public readonly normalizedName: string;
    /**
     The friendly, human-readable display name of this document type in English. iOS only.
     */
    public displayText?: string | null = null;
    /**
     A document can contain multiple fields of the same name, the property serves for storing natural order of such fields, null if multiple entries aren't allowed for this field
     */
    public readonly listIndex: number | null = null;

    /** @param source {@displayType `DeepPartial<Type>`} */
    public constructor(source: DeepPartial<Type> = {}) {
      super();
      if (source.name !== undefined) {
        this.name = source.name;
      } else {
        throw new Error('name must be present in constructor argument');
      }
      if (source.fullName !== undefined) {
        this.fullName = source.fullName;
      } else {
        throw new Error('fullName must be present in constructor argument');
      }
      if (source.normalizedName !== undefined) {
        this.normalizedName = source.normalizedName;
      } else {
        throw new Error('normalizedName must be present in constructor argument');
      }
      if (source.displayText !== undefined) {
        this.displayText = source.displayText;
      }
      if (source.listIndex !== undefined) {
        this.listIndex = source.listIndex != null ? source.listIndex : null;
      }
    }
  }
}
