// Copyright © 2022 Olo Inc. All rights reserved.
// This software is made available under the Olo Pay SDK License (See LICENSE.md file)
import type { StyleProp, TextStyle, ViewProps, ViewStyle } from 'react-native';

export interface IOloPaySDK {
  /**
   * Initialize the Olo Pay SDK. The SDK must be initialized prior to calling other methods.
   *
   * This promise will only be rejected on iOS. If it is rejected the `code` property on the returned
   * error will be [`PromiseRejectionCode.missingParameter`](#promiserejectioncode)
   *
   * _**NOTE:**_ On iOS, this method will also initialize Apple Pay and a [DigitalWalletReadyEvent](#digitalwalletreadyevent)
   * will be emitted when it is ready to process payments. On Android, a separate initialization
   * call to `initializeGooglePay()` is required.
   *
   * @param options Initialization options
   */
  initialize(
    options: AndroidInitializationOptions | iOSInitializationOptions
  ): Promise<void>;

  /**
   * _**ANDROID ONLY:** If this method is called on iOS the promise will be rejected_
   *
   * Initialize digital wallets. This must be called after initializing the SDK. When digital wallets
   * are ready, a [DigitalWalletReadyEvent](#digitalwalletreadyevent) will be emitted.
   *
   * If the promise is rejected, possible values of the `code` property on the returned error will be one of:
   * - [PromiseRejectionCode.unimplemented (iOS)](#promiserejectioncode)
   * - [PromiseRejectionCode.missingParameter](#promiserejectioncode)
   * - [PromiseRejectionCode.sdkUninitialized](#promiserejectioncode)
   *
   * @param options Options for initializing digital wallets. _`countryCode` and `merchantName` are required options._
   */
  initializeGooglePay(options: GooglePayInitializationOptions): Promise<void>;

  /**
   * _**ANDROID ONLY:** If this method is called on iOS the promise will be rejected_
   *
   * Call this to change the country and merchant name used for processing Google Pay payments. This will immediately
   * trigger a [DigitalWalletReadyEvent](#digitalwalletreadyevent) indicating digital wallets are not ready. When Google Pay
   * has been reinitialized and is ready to be used with the new parameters, another event will be
   * emitted.
   *
   * _**NOTE:** If other options need to be changed besides country code and merchant name you can call
   *            `initializeGooglePay()` instead, but it is more expensive than calling this method._
   *
   * If the promise is rejected, possible values of the `code` property on the returned error will be one of:
   * - [PromiseRejectionCode.unimplemented (iOS)](#promiserejectioncode)
   * - [PromiseRejectionCode.missingParameter](#promiserejectioncode)
   * - [PromiseRejectionCode.sdkUninitialized](#promiserejectioncode)
   * - [PromiseRejectionCode.googlePayUninitialized](#promiserejectioncode)
   *
   * @param options Options for changing the country and merchant name. _`countryCode` and `merchantName` are required options._
   */
  changeGooglePayVendor(options: ChangeGooglePayVendorOptions): Promise<void>;

  /**
   * Launch the digital wallet flow and generate a payment method to be used with Olo's Ordering API.
   *
   * If the promise is rejected, the `code` property of the returned error object will be one of:
   * - [PromiseRejectionCode.sdkUninitialized](#promiserejectioncode)
   * - [PromiseRejectionCode.invalidParameter](#promiserejectioncode)
   * - [PromiseRejectionCode.missingParameter](#promiserejectioncode)
   * - [PromiseRejectionCode.applePayUnsupported (iOS)](#promiserejectioncode)
   * - [PromiseRejectionCode.googlePayNotReady (Android)](#promiserejectioncode)
   * - [PromiseRejectionCode.googlePayUninitialized (Android)](#promiserejectioncode)
   * - [PromiseRejectionCode.generalError](#promiserejectioncode)
   *
   * @param options Options for processing a digital wallet payment. _`amount` is a required option_
   */
  getDigitalWalletPaymentMethod(
    options: DigitalWalletPaymentRequestOptions
  ): Promise<DigitalWalletPaymentMethodResult | undefined>;

  /**
   * Check if the Olo Pay SDK has been initialized
   */
  isInitialized(): Promise<InitializationStatus>;

  /**
   * Check if digital wallets have been initialized. On iOS, digital wallets are initialized when the SDK is initialized, so this method
   * will behave the same as `isInitialized()`. On Android, a separate call to `initializeGooglePay()` is required to initialize digital wallets.
   */
  isDigitalWalletInitialized(): Promise<InitializationStatus>;

  /**
   * Check if digital wallets are ready to be used. Events are emitted whenever the digital wallet status
   * changes, so listenting to that event can be used instead of calling this method, if desired.
   */
  isDigitalWalletReady(): Promise<DigitalWalletStatus>;
}

/** Type alias representing options for a digital wallet payment method request */
export type DigitalWalletPaymentRequestOptions =
  | GooglePayPaymentRequestOptions
  | ApplePayPaymentRequestOptions;

/**
 * Type alias representing a digital wallet payment method result.  The
 * object will either contain payment method data or error data.
 */
export type DigitalWalletPaymentMethodResult =
  | { paymentMethod: PaymentMethod; error?: undefined }
  | { paymentMethod?: undefined; error: DigitalWalletError };

/**
 * Type representing a digital wallet error
 * | Property | Description |
 * | -------- | ----------- |
 * | `errorMessage` | Error message indicating what went wrong |
 * | `digitalWalletType` | Enum value indicating Apple Pay or Google Pay. If this is a Google Pay error, there are additional properties indicating the type of error that occurred. See `GooglePayError` |
 */
export type DigitalWalletError = {
  errorMessage: string;
  digitalWalletType: DigitalWalletType;
} & (GooglePayError | null);

/**
 * Type representing specific error types that can occur while processing a Google Pay transaction
 * | Property | Description |
 * | -------- | ----------- |
 * | `googlePayErrorType` | The type of error that occurred. See `GooglePayErrorType` |
 */
type GooglePayError = {
  googlePayErrorType: GooglePayErrorType;
};

/**
 * Options for initializing the Olo Pay SDK
 * | Property | Description | Default |
 * | -------- | ----------- | ------- |
 * | `productionEnvironment` | `true` to use the production environment, `false` for the test environment. | `true` |
 * | `freshInstall` | **_DEPRECATED:_** This property is deprecated and is ignored if used |
 */
type OloPayInitializationConfig = {
  productionEnvironment?: boolean;
  freshInstall?: boolean;
};

/**
 * Options for initializing Apple Pay
 * | Property | Description |
 * | -------- | ----------- |
 * | `applePayMerchantId` | The merchant id registered with Apple for Apple Pay |
 * | `applePayCompanyLabel` | The company name that will be displayed on the Apple Pay payment sheet |
 */
type ApplePayInitializationConfig = {
  applePayMerchantId: string;
  applePayCompanyLabel: string;
};

/** Options for initializing the Android Olo Pay SDK. This is a type alias for code readability. */
export type AndroidInitializationOptions = OloPayInitializationConfig;

/** Options for initializing the iOS Olo Pay SDK. This is a type alias for code readability */
export type iOSInitializationOptions = OloPayInitializationConfig &
  ApplePayInitializationConfig;

/**
 * Options for intializing Google Pay
 * | Property | Description | Default |
 * | -------- | ----------- | ------- |
 * | `googlePayProductionEnvironment` | `true` to use the production environment, `false` for the test environment | `true` |
 * | `countryCode` | A two character country code for the vendor that will be processing the payment | - |
 * | `merchantName` | The merchant/vendor display name | - |
 * | `fullAddressFormat` | Determines what address fields are required to complete a Google Pay transaction. `true` includes name, street address, locality, region, country code, and postal code. `false` only includes name, country code, and postal code | `false` |
 * | `existingPaymentMethodRequired` | Whether an existing saved payment method is required for Google Pay to be considered ready | `true` |
 * | `emailRequired` | Whether Google Pay collects an email when processing payments | `false` |
 * | `phoenNumberRequired` | Whether Google Pay collects a phone number when processing payments | `false` |
 */
export type GooglePayInitializationOptions = {
  googlePayProductionEnvironment?: boolean;
  countryCode: string;
  merchantName: string;
  fullAddressFormat?: boolean;
  existingPaymentMethodRequired?: boolean;
  emailRequired?: boolean;
  phoneNumberRequired?: boolean;
};

/**
 * Options for changing the country code or merchant name for Google Pay transactions
 * | Property | Description |
 * | -------- | ----------- |
 * | `countryCode` | A two character country code for the vendor that will be processing the payment |
 * | `merchantName` | The merchant/vendor display name |
 */
export type ChangeGooglePayVendorOptions = {
  countryCode: string;
  merchantName: string;
};

/**
 * Options for requesting a payment method via Google Pay
 * | Property | Description | Default |
 * | -------- | ----------- | ------- |
 * | `amount` | The amount to be charged | - |
 * | `currencyCode` | A three character currency code for the transaction | 'USD' |
 * | `currencyMulitplier` | Multiplier to convert the amount to the currency's smallest currency unit (e.g. $2.34 * 100 = 234 cents) | 100 |
 *
 * **_Important_:** The amount charged will be equivalent to `amount * currencyCode` so ensure these are set properly
 */
export type GooglePayPaymentRequestOptions = {
  amount: number;
  currencyCode?: string;
  currencyMultiplier?: number;
};

/**
 * Options for requesting a payment method via Apple Pay
 * | Property | Description | Default |
 * | -------- | ----------- | ------- |
 * | `amount` | The amount to be charged | - |
 * | `currencyCode` | A three character currency code for the transaction | 'USD' |
 * | `countryCode` | A two character country code | 'US' |
 */
export type ApplePayPaymentRequestOptions = {
  amount: number;
  countryCode?: string;
  currencyCode?: string;
};

/**
 * Represents the status of digital wallets.
 * | Property | Description |
 * | -------- | ----------- |
 * | `isReady` | `true` if digital wallets are ready to be used, `false` otherwise |
 */
export type DigitalWalletStatus = {
  isReady: boolean;
};

/**
 * Represents the status for SDK initialization
 * | Property | Description |
 * | -------- | ----------- |
 * | `isInitialized` | `true` if the SDK is initialized, `false` otherwise |
 */
export type InitializationStatus = {
  isInitialized: boolean;
};

/**
 * Payment method used for submitting payments to Olo's Ordering API
 * | Property | Description |
 * | -------- | ----------- |
 * | `id` | The payment method id. This should be set to the token field when submitting a basket |
 * | `last4` | The last four digits of the card |
 * | `cardType` | The issuer of the card |
 * | `expMonth` | Two-digit number representing the card's expiration month |
 * | `expYear` | Four-digit number representing the card's expiration year |
 * | `postalCode` | Zip or postal code |
 * | `countryCode` | Two character country code |
 * | `isDigitalWallet` | `true` if this payment method was created by digital wallets (e.g. Apple Pay or Google Pay), `false` otherwise |
 */
export type PaymentMethod = {
  id?: string;
  last4?: string;
  cardType?: CardType;
  expMonth?: number;
  expYear?: number;
  postalCode?: string;
  countryCode?: string;
  isDigitalWallet: boolean;
};

// SEE DOCUMENTATION IN README.MD
export type CvvUpdateToken = {
  id?: string;
  productionEnvironment?: boolean;
};

/** Digital wallet types */
export enum DigitalWalletType {
  applePay = 'applePay',
  googlePay = 'googlePay',
}

/** Specific kinds of Google Pay Errors */
export enum GooglePayErrorType {
  /** Google Pay didn't succeed due to a network error */
  networkError = 'NetworkError',

  /** Google Pay didn't succeed due to developer error */
  developerError = 'DeveloperError',

  /** Google Pay didn't succeed due to an internal error */
  internalError = 'InternalError',
}

export enum CardType {
  /** Visa credit card type. Pass the string value of this into the Olo Ordering API when submitting orders */
  visa = 'Visa',
  /** American Express credit card type. Pass the string value of this into the Olo Ordering API when submitting orders */
  amex = 'Amex',
  /** Mastercard credit card type. Pass the string value of this into the Olo Ordering API when submitting orders */
  mastercard = 'Mastercard',
  /** Discover credit card type. Pass the string value of this into the Olo Ordering API when submitting orders */
  discover = 'Discover',
  /** Unsupported credit card type. Passing this to the Olo Ordering API will result in an error */
  unsupported = 'Unsupported',
  /** Unknown credit card type. Passing this to the Olo Ordering API will result in an error */
  unknown = 'Unknown',
}

// SEE DOCUMENTATION IN README.MD
export enum PromiseRejectionCode {
  invalidParameter = 'InvalidParameter',
  missingParameter = 'MissingParameter',
  sdkUninitialized = 'SdkUninitialized',
  applePayUnsupported = 'ApplePayUnsupported',
  googlePayUninitialized = 'GooglePayUninitialized',
  googlePayNotReady = 'GooglePayNotReady',
  uninmplemented = 'UNIMPLEMENTED',
  viewNotFound = 'ViewNotFound',
  apiError = 'ApiError',
  invalidRequest = 'InvalidRequest',
  connectionError = 'ConnectionError',
  cancellationError = 'CancellationError',
  authenticationError = 'AuthenticationError',
  invalidCardDetails = 'InvalidCardDetails',
  invalidNumber = 'InvalidNumber',
  invalidExpiration = 'InvalidExpiration',
  invalidCvv = 'InvalidCVV',
  invalidPostalCode = 'InvalidPostalCode',
  expiredCard = 'ExpiredCard',
  cardDeclined = 'CardDeclined',
  processingError = 'ProcessingError',
  unknownCardError = 'UnknownCardError',
  generalError = 'generalError',
}

// SEE DOCUMENTATION IN README.MD
export type PromiseRejection = {
  code: string;
  message: string;
};

// SEE DOCUMENTATION IN README.MD
export const DigitalWalletReadyEvent = 'digitalWalletReadyEvent';

// SEE DOCUMENTATION IN README.MD
export type PaymentCardDetailsPlaceholders = {
  number?: string;
  expiration?: string;
  cvv?: string;
  postalCode?: string;
};

// SEE DOCUMENTATION IN README.MD
export type PaymentCardDetailsViewStyles = {
  borderWidth?: number;
  borderColor?: string;
  backgroundColor?: string;
  cornerRadius?: number;
  cursorColor?: string;
  errorTextColor?: string;
  fontSize?: number;
  fontFamily?: string;
  fontWeight?: FontWeight;
  italic?: boolean;
  textPaddingLeft?: number;
  textPaddingRight?: number;
  placeholderColor?: string;
  styles?: StyleProp<ViewStyle>;
  textColor?: string;
};

// SEE DOCUMENTATION IN README.MD
export type PaymentCardDetailsFormStyles = {
  styles?: StyleProp<ViewStyle>;
  backgroundColor?: string;
  cursorColor?: string;
  borderColor?: string;
  borderWidth?: number;
  cornerRadius?: number;
  textPaddingLeft?: number;
  textPaddingRight?: number;
  fieldDividerWidth?: number;
  fieldDividerColor?: string;
  cardElevation?: number;
  textColor?: string;
  placeholderColor?: string;
  focusedPlaceholderColor?: string;
  fontSize?: number;
  fontFamily?: string;
  fontWeight?: FontWeight;
  italic?: boolean;
};

// SEE DOCUMENTATION IN README.MD
export interface PaymentCardDetailsViewProps {
  componentStyles?: StyleProp<ViewStyle>;
  errorStyles?: StyleProp<TextStyle>;
  cardStyles?: PaymentCardDetailsViewStyles;
  customErrorMessages?: CustomErrorMessages;
  viewProps?: ViewProps;
  postalCodeEnabled?: boolean;
  isEnabled?: boolean;
  displayErrorMessages?: boolean;
  placeholders?: PaymentCardDetailsPlaceholders;
  onCardChange?(card: CardDetails): void;
  onFocus?(): void;
  onBlur?(): void;
  onFocusField?(field: CardField): void;
}

// SEE DOCUMENTATION IN README.MD
export interface PaymentCardDetailsFormProps {
  cardStyles?: PaymentCardDetailsFormStyles;
  componentStyles?: StyleProp<ViewStyle>;
  isEnabled?: boolean;
  onFormComplete?(cardValidationStatus: CardValidationStatus): void;
  viewProps?: ViewProps;
  placeholders?: PaymentCardDetailsPlaceholders;
}

// SEE DOCUMENTATION IN README.MD
export type PaymentCardCvvViewStyles = {
  backgroundColor?: string;
  borderColor?: string;
  borderWidth?: number;
  cornerRadius?: number;
  cursorColor?: string;
  fontFamily?: string;
  fontSize?: number;
  placeholderColor?: string;
  textColor?: string;
  errorTextColor?: string;
  textPaddingLeft?: number;
  textPaddingRight?: number;
  fontWeight?: FontWeight;
  italic?: boolean;
  styles?: StyleProp<ViewStyle>;
  textAlign?: 'left' | 'center' | 'right';
};

// SEE DOCUMENTATION IN README.MD
export interface PaymentCardCvvViewProps {
  componentStyles?: StyleProp<ViewStyle>;
  cvvStyles?: PaymentCardCvvViewStyles;
  errorStyles?: StyleProp<TextStyle>;
  customErrorMessages?: CustomFieldError;
  displayErrorMessages?: boolean;
  isEnabled?: boolean;
  placeholder?: string;
  viewProps?: ViewProps;
  onCvvChange?(cvvDetails: CvvDetails): void;
  onFocus?(cvvDetails: CvvDetails): void;
  onBlur?(cvvDetails: CvvDetails): void;
}

// SEE DOCUMENTATION IN README.MD
export interface PaymentCardDetailsViewMethods {
  focus(): Promise<void>;
  blur(): Promise<void>;
  clear(): Promise<void>;
  createPaymentMethod(): Promise<PaymentMethod>;
}

// SEE DOCUMENTATION IN README.MD
export interface PaymentCardDetailsFormMethods {
  blur(): Promise<void>;
  clear(): Promise<void>;
  createPaymentMethod(): Promise<PaymentMethod>;
  focus(): Promise<void>;
}

// SEE DOCUMENTATION IN README.MD
export interface PaymentCardCvvViewMethods {
  blur(): Promise<void>;
  clear(): Promise<void>;
  createCvvUpdateToken(): Promise<CvvUpdateToken>;
  focus(): Promise<void>;
}

// SEE DOCUMENTATION IN README.MD
export enum CardField {
  number = 'number',
  expiration = 'expiration',
  cvv = 'cvv',
  postalCode = 'postalCode',
}

// SEE DOCUMENTATION IN README.MD
export interface CardDetails extends CardValidationStatus {
  cardType: CardType;
  invalidFields?: CardField[];
  emptyFields?: CardField[];
  errors?: {
    editedFieldsError?: string;
    allFieldsError?: string;
  };
}

// SEE DOCUMENTATION IN README.MD
export interface FieldState {
  isValid: boolean;
  isFocused: boolean;
  isEmpty: boolean;
  wasEdited: boolean;
  wasFocused: boolean;
}

// SEE DOCUMENTATION IN README.MD
export interface CvvDetails {
  state: FieldState;
  errors?: {
    editedFieldError?: string;
    uneditedFieldError?: string;
  };
}

// SEE DOCUMENTATION IN README.MD
export interface CardValidationStatus {
  isValid: boolean;
}

// SEE DOCUMENTATION IN README.MD
export interface CustomErrorMessages {
  number?: CustomFieldError;
  expiration?: CustomFieldError;
  cvv?: CustomFieldError;
  postalCode?: CustomFieldError;
  unsupportedCardError?: string;
}

// SEE DOCUMENTATION IN README.MD
export interface CustomFieldError {
  emptyError?: string;
  invalidError?: string;
}

// SEE DOCUMENTATION IN README.MD
export enum FontWeight {
  ultraLight = '100',
  thin = '200',
  light = '300',
  regular = '400',
  medium = '500',
  semiBold = '600',
  bold = '700',
  extraBold = '800',
  black = '900',
}
