/// <reference types="applepayjs" />
/// <reference types="googlepay" />

import { PaymentRequest as PaymentRequest_2 } from '@stripe/stripe-js';
import { StripeLinkAuthenticationElement } from '@stripe/stripe-js';
import { Subject } from 'rxjs';
import { z } from 'zod';

declare type AffirmFlowCustomParams = {
    processor?: PaymentProcessor;
    useRedirectFlow?: boolean;
};

declare type AfterpayFlowCustomParams = {
    processor?: PaymentProcessor;
    useRedirectFlow?: boolean;
};

declare type AirwallexACHFlowParams = {
    verificationMethod?: 'plaid' | 'micro_deposit';
    defaultFieldValues?: DefaultFieldValues;
    isBusinessAccount?: boolean;
};

declare type AirwallexBACSFlowParams = {
    verificationMethod?: 'truelayer' | 'micro_deposit';
    defaultFieldValues?: DefaultFieldValues;
    // sortCode?: string;
    // accountNumber?: string;
};

declare type AirwallexEFTFlowParams = {
    verificationMethod?: 'plaid' | 'micro_deposit';
    defaultFieldValues?: DefaultFieldValues;
    isBusinessAccount?: boolean;
};

declare type AirwallexSEPAFlowParams = {
    verificationMethod?: 'truelayer' | 'micro_deposit';
    defaultFieldValues?: DefaultFieldValues;
};

declare type AllCallbacks = {
    onFocus?: (elementId: string, field: AllFieldNames) => void;
    onBlur?: (
    elementId: string,
    field: AllFieldNames,
    extraArgs?: {
        obfuscatedValue?: string;
    }
    ) => void;
    onChange?: (elementId: string, field: AllFieldNames, errors?: string[]) => void;
    onLoad?: (totalAmountAtoms?: number, currency?: string) => void;
    onLoadError?: (message: string) => void;
    onValidationError?: OnValidationError;
    onCheckoutStarted?: OnCheckoutStarted;
    onCheckoutSuccess?: OnCheckoutSuccess;
    onSetupPaymentMethodSuccess?: OnSetupPaymentMethodSuccess;
    onCheckoutError?: OnCheckoutError;
    /**
     * @deprecated - use `getAvailablePaymentMethods` and `generalSubmit` instead
     */
    onPaymentRequestLoad?: (paymentRequests: PRStatuses) => void;
};

declare const AllFieldNames = z.union([FieldNameEnum, PrivateFieldNameEnum]);

declare type AllFieldNames = z.infer<typeof AllFieldNames>;

/**
 * Payment requests
 */
declare const Amount = z.object({
    amountAtom: z.number(),
    /**
     * The three-letter ISO 4217 currency code for the payment.
     */
    currency: RequiredString,
});

declare type Amount = z.infer<typeof Amount>;

declare type ApplePayFlowCustomParams = {
    /**
     * In case of multiple processors available for ApplePay, the processor setting will
     * determine which processor to use.
     * If `processor` is not set or is not available, the first available processor will be used
     *
     * **Note**: this processor choice is for the whole process of showing PaymentRequest UI, verification and charging.
     * It won't perform fallback processor charging when payment failed.
     */
    processor?: PaymentProcessor;
    overridePaymentRequest?: {
        amount?: Amount;
        pending?: boolean;
        label?: string;
        applePayPaymentRequest?: ApplePayPaymentRequest;
    };
    defaultFieldValues?: DefaultFieldValues;
};

declare type ApplePayPaymentRequest =
| ((current: ApplePayJS.ApplePayPaymentRequest) => Partial<ApplePayJS.ApplePayPaymentRequest>)
| Partial<ApplePayJS.ApplePayPaymentRequest>;

/**
 * Styles
 */
declare const BaseElementsStyle = z.object({
    backgroundColor: OptionalString,
    color: OptionalString,
    fontFamily: OptionalString,
    fontSize: OptionalString,
    fontWeight: OptionalString,
    margin: OptionalString,
    padding: OptionalString,
    letterSpacing: OptionalString,
    lineHeight: OptionalString,
    hideIcon: OptionalString,
    placeholderStyle: z
    .object({
        color: OptionalString,
        fontSize: OptionalString,
        fontWeight: OptionalString,
        fontFamily: OptionalString,
        letterSpacing: OptionalString,
        lineHeight: OptionalString,
    })
    .optional(),
});

declare type BaseElementsStyle = z.infer<typeof BaseElementsStyle>;

declare enum Blockchain {
    EVM = 'EVM',
    SOL = 'Solana',
}

declare type BlockchainNetwork = keyof typeof Blockchain;

declare const CardCvcElementStyle = BaseElementsStyle.extend({
    disableAutoFill: OptionalString,
    placeholder: z.string().optional(),
});

declare const CardElementStyle = BaseElementsStyle.extend({
    disableAutoFill: OptionalString,
    placeholder: z
    .object({
        cardNumber: OptionalString,
        expiry: OptionalString,
        cvc: OptionalString,
    })
    .optional(),
});

declare const CardExpiryElementStyle = BaseElementsStyle.extend({
    disableAutoFill: OptionalString,
    placeholder: z.string().optional(),
});

declare const CardNumberElementStyle = BaseElementsStyle.extend({
    disableAutoFill: OptionalString,
    placeholder: z.string().optional(),
    /**
     * The card icon at the start of the card number input field
     * It won't affect after the icon changed to card brand after the user input the card number
     */
    icon: z
    .object({
        color: z.string().optional(),
    })
    .optional(),
});

declare type CCSubmitParams = {
    paymentRouteId?: string;
};

declare interface CdeConnection {
    send: (data: CdeMessage) => Promise<unknown>;
    tracingContext?: TracingContext;
}

declare type CdeMessage = {
    type: string;
} & Record<string, unknown>;

/**
 * Core models
 */
declare const CheckoutPaymentMethod = z.object({
    provider: z.string(),
    processor_name: nullOrUndefOr(z.string()),
    metadata: nullOrUndefOr(z.record(z.string(), z.any())),
});

declare type CheckoutPaymentMethod = z.infer<typeof CheckoutPaymentMethod>;

declare type CommonSubmitSettings = {
    defaultFieldValues?: DefaultFieldValues;
};

export declare type Config = ElementsFormProps & AllCallbacks;

declare type CurrentStatus<T> = InitialStatus | SuccessStatus<T> | ErrorStatus;

declare interface CustomerResponse {
    /**
     * The unique identifier that represents the customer
     * @example "1234567890abcdef"
     */
    customerId: string;
    /**
     * The external customer reference ID used to tie this customer to a customer in an external system.
     * @example "1234567890abcdef"
     */
    customerRefId: string | null;
    /**
     * The payment methods configured for this customer to make payments with.
     */
    paymentMethods: Omit<PaymentMethodResponse, 'entityId' | 'customer' | 'dateCreated'>[];
    /**
     * The date the customer record was created, represented as a Unix timestamp in seconds.
     * @example 1716211200
     */
    dateCreated: number;
}

declare type CustomInitParams = {
    // You can put custom params for your init flows here

    // For stripe link
    stripeLink?: {
        /**
         * The height of the Stripe Link button. By default, the height of the buttons are 44px.
         * You can override this to specify a custom button height in the range of 40px-55px.
         *
         * See more: https://docs.stripe.com/js/elements_object/create_express_checkout_element#express_checkout_element_create-options-buttonHeight
         */
        buttonHeight?: number;

        /**
         * If this function returns false, the stripe link submit process is aborted.
         * This can be used for additional pre-submit checks (e.g. additional form validation).
         * Note that this function must complete within 1 second, or the submission will fail.
         */
        overrideLinkSubmit?: () => Promise<boolean>;

        /**
         * By default, the stripe link button is mounted on OJS initialization.
         * If this value is true, the stripe link is not mounted on init, and should instead be manually mounted.
         */
        doNotMountOnInit?: boolean;

        /**
         * If this value is true, the link authentication element is checked for login status silently.
         *
         * ⚠️ [**Warning**]: This is a workaround for checking Link authentication status without user gesture.
         * Stripe don't expose API for this and it's recommended to mount the `linkAuthenticationElement` manually and check the authentication status via `.on('change')`
         * You can mount the `linkAuthenticationElement` manually by calling `stripeLink.mountLinkAuthenticationElement(selector)`
         */
        silentlyCheckAuthentication?: boolean;
        /**
         * This callback is called when the authentication status changes when `silentlyCheckAuthentication` is true,
         *
         * ⚠️ [**Warning**]: This is a workaround for checking authentication status without user gesture.
         * This is working with current version of Stripe element (v3). This is not guaranteed to work with future versions of Stripe element.
         */
        onAuthenticationStatusChange?: (isAuthenticated: boolean) => void;
        /**
         * This will be called once the `linkAuthenticationElement` is ready.
         * This is just a wrapper for `linkAuthenticationElement.once('ready')`.
         */
        onAuthenticationElementReady?: () => void;
    };

    // For stripe klarna
    stripeKlarna?: {
        /**
         * Additional payment data to be passed to stripe.confirmKlarnaPayment
         * This will be merged with the billing details from the form
         */
        paymentData?: {
            payment_method?: {
                billing_details?: {
                    email?: string;
                    name?: string;
                    phone?: string;
                    address?: {
                        line1?: string;
                        line2?: string;
                        city?: string;
                        state?: string;
                        country?: string;
                        postal_code?: string;
                    };
                };
            };
            return_url?: string;
            setup_future_usage?: 'off_session' | 'on_session';
            save_payment_method?: boolean;
        };

        /**
         * Options to be passed to stripe.confirmKlarnaPayment
         */
        options?: {
            handleActions?: boolean;
        };
    };

    googlePay?: {
        env: 'demo' | 'prod';
        doNotMountOnInit?: boolean;
    };

    applePay?: {
        env: 'demo' | 'prod';
        doNotMountOnInit?: boolean;
    };
};

declare type CustomStyles = string;

declare type DefaultFieldValues = Partial<Record<FieldNameEnum, string>> & {
    payment_method?: string | SubmitMethod; // This is not coming from checkout setting. TODO: Define a type in future if there are more attrs
    show_all_prices?: boolean;
    exclusive_prices?: boolean;
    price_id_product_mapping?: Record<string, string>;
    require_zip_code?: boolean;
    require_full_billing_address?: boolean;
    require_full_shipping_address?: boolean;
};

declare type ElementProps<T extends ElementType> = {
    styles?: ElementsStyle<T>;
};

declare type ElementsFormProps = {
    className?: string;
    checkoutSecureToken: string;
    baseUrl?: string;
    formTarget?: string;
    customInitParams?: CustomInitParams;
    /**
     * Whether to enable tracing. Defaults to false.
     * Set to true to opt-in to tracing functionality.
     */
    allowTracing?: boolean;
    preventNavigationDuringSubmission?: boolean;
};

declare type ElementsStyle<T extends ElementType = ElementType> = {
    card: z.infer<typeof CardElementStyle>;
    'card-number': z.infer<typeof CardNumberElementStyle>;
    'card-expiry': z.infer<typeof CardExpiryElementStyle>;
    'card-cvc': z.infer<typeof CardCvcElementStyle>;
    'cde-bridge': z.infer<typeof BaseElementsStyle>;
}[T];

export declare type ElementType = z.infer<typeof _ElementTypeEnumZod>;

export declare enum ElementTypeEnum {
    CARD = 'card',
    CARD_NUMBER = 'card-number',
    CARD_EXPIRY = 'card-expiry',
    CARD_CVC = 'card-cvc',
    CDE_BRIDGE = 'cde-bridge',
}

declare const _ElementTypeEnumZod = z.enum(['card', 'card-number', 'card-expiry', 'card-cvc', 'cde-bridge']);

declare enum Environment {
    Local = 'local',
    Development = 'development',
    Staging = 'staging',
    Demo = 'demo',
    Production = 'production',
}

declare type ErrorStatus = {
    status: 'error';
    isSuccess: false;
    error: unknown; // Anything can be thrown, so we don't want to be strict here
    errMsg: string;
};

/**
 * Expected input fields
 */

// Supplied by the user
export declare enum FieldName {
    FIRST_NAME = 'firstName',
    LAST_NAME = 'lastName',
    PHONE = 'phone',
    EMAIL = 'email',
    LINE1 = 'line1',
    LINE2 = 'line2',
    LINE3 = 'line3',
    CITY = 'city',
    STATE = 'state',
    COUNTRY = 'country',
    ZIP_CODE = 'zipCode',
    SHIPPING_LINE1 = 'shippingAddressLine1',
    SHIPPING_LINE2 = 'shippingAddressLine2',
    SHIPPING_LINE3 = 'shippingAddressLine3',
    SHIPPING_CITY = 'shippingAddressCity',
    SHIPPING_STATE = 'shippingAddressState',
    SHIPPING_COUNTRY = 'shippingAddressCountry',
    SHIPPING_ZIP_CODE = 'shippingAddressZipCode',
    IS_SHIPPING_SAME_AS_BILLING = 'isShippingSameAsBilling',
    PROMOTION_CODE = 'promotionCode',
    IS_BUSINESS_PURCHASE = 'isBusinessPurchase',
    BUSINESS_NAME = 'businessName',
    TAX_ID = 'taxId',
    TAX_ID_TYPE = 'taxIdType',
}

declare const FieldNameEnum = z.nativeEnum(FieldName);

declare type FieldNameEnum = z.infer<typeof FieldNameEnum>;

declare class FormCallbacks {
    private static readonly NOOP = () => {};
    private _callbacks: Required<AllCallbacks>;

    constructor() {
        this._callbacks = FormCallbacks.createEmptyCallbacks();
    }

    private static createEmptyCallbacks = (): Required<AllCallbacks> => {
        const x: AllCallbacks = {};
        Object.keys(ZodFormCallbacks.keyof().enum).forEach((key) => {
            // @ts-expect-error - trust the process
            x[key] = FormCallbacks.NOOP;
        });
        return x as Required<AllCallbacks>;
    };

    static fromObject = (obj: unknown) => {
        const instance = new FormCallbacks();
        instance.setCallbacks(ZodFormCallbacks.parse(obj));
        return instance;
    };

    /**
     * Sets ALL form callbacks. Note that all old callbacks are removed.
     */
    setCallbacks = (rawCallbacks: AllCallbacks) => {
        // Making sure to reinitialize
        this._callbacks = FormCallbacks.createEmptyCallbacks();
        Object.entries(rawCallbacks).forEach(([key, rawCallback]) => {
            const safeCallback = makeCallbackSafe(key, rawCallback ?? FormCallbacks.NOOP, err__);
            // @ts-expect-error - trust the process
            this._callbacks[key] = safeCallback;
        });
    };

    /**
     * Returns a read-only version of the callbacks object.
     */
    get get() {
        return { ...this._callbacks };
    }
}

declare type GeneralSubmit = typeof _generalSubmit;

declare const _generalSubmit = async <T extends SubmitMethod>(method: T, settings?: SubmitSettings<T>) => {
    console.log('generalSubmit', method, settings);
};

declare type GooglePayFlowCustomParams = {
    /**
     * In case of multiple processors available for GooglePay, the processor setting will
     * determine which processor to use.
     * If `processor` is not set or is not available, the first available processor will be used
     *
     * **Note**: this processor choice is for the whole process of showing PaymentRequest UI, verification and charging.
     * It won't perform fallback processor charging when payment failed.
     */
    processor?: PaymentProcessor;
    overridePaymentRequest?: {
        amount?: Amount;
        pending?: boolean;
        label?: string;
        googlePayPaymentRequest?: GooglePayPaymentRequest;
    };
    defaultFieldValues?: DefaultFieldValues;
};

declare type GooglePayPaymentRequest =
| ((current: PaymentDataRequest) => Partial<PaymentDataRequest>)
| Partial<PaymentDataRequest>;

declare type InitApplePayFlowResult = {
    processor: PaymentProcessor;
    isAvailable: boolean;
    isLoading: boolean;
    startFlow: (customParams?: ApplePayFlowCustomParams) => Promise<void>;
};

declare interface InitFailedEvent {
    type: 'initFailed';
    message: string;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    data: Record<string, any>;
}

declare type InitGooglePayFlowResult = {
    processor: PaymentProcessor;
    isAvailable: boolean;
    isLoading: boolean;
    startFlow: (customParams?: GooglePayFlowCustomParams) => Promise<void>;
};

declare interface InitializedEvent {
    entityId: string;
}

declare type InitialStatus = {
    status: 'initial';
    isSuccess: false;
};

declare type InitLoopCryptoFlowSuccess =
| {
    isAvailable: true;
    widgetProps: LoopCryptoWidgetProps;
    initLoopConnectProps: LoopConnectConfig;
}
| {
    isAvailable: false;
};

declare type InitOjsFlow<T extends InitOjsFlowResult> = (params: InitOjsFlowParams) => Promise<T>;

declare type InitOjsFlowParams = {
    /**
     * Contains the context where OJS is run.
     * Ideally this only contains "background" context (OJS-level objects), and not flow-level objects.
     */
    context: OjsContext;

    /**
     * Lifecycle callbacks for OJS flows.
     */
    formCallbacks: FormCallbacks;
};

declare type InitOjsFlowResult = {
    isAvailable: boolean;
};

declare type InitStripeAffirmFlowResult = {
    isAvailable: boolean;
    startFlow: (customParams?: AffirmFlowCustomParams) => Promise<void>;
};

declare type InitStripeAfterpayFlowResult = {
    isAvailable: boolean;
    startFlow: (customParams?: AfterpayFlowCustomParams) => Promise<void>;
};

declare type InitStripeKlarnaFlowResult = {
    isAvailable: boolean;
    startFlow: (customParams?: KlarnaFlowCustomParams) => Promise<void>;
};

declare type InitStripeLinkFlowResult =
| {
    isAvailable: true;
    controller: StripeLinkController;
}
| {
    isAvailable: false;
};

declare type InitStripePrFlowResult =
| InitStripePrFlowSuccess
| {
    isAvailable: false;
    reason: string;
};

declare type InitStripePrFlowSuccess = {
    processor: 'stripe';
    isAvailable: true;
    // Passed to RunFlow
    pr: PaymentRequest_2;
    // Passed to the user
    availableProviders: {
        applePay: boolean;
        googlePay: boolean;
    };
    startFlow: (provider: PaymentRequestProvider, params?: PaymentRequestStartParams) => Promise<void>;
};

declare type KlarnaFlowCustomParams = {
    /**
     * In case of multiple processors available for Klarna, the processor setting will
     * determine which processor to use.
     * If `processor` is not set or is not available, the first available processor will be used
     *
     * **Note**: this processor choice is for the whole process of showing payment UI, verification and charging.
     * It won't perform fallback processor charging when payment failed.
     */
    processor?: PaymentProcessor;
    isAnonymous?: boolean;
    useRedirectFlow?: boolean;
};

declare const LoadedEventPayload = z.object({
    type: z.literal(EventType.enum.LOADED),
    sessionId: RequiredString,
    totalAmountAtoms: z.number(),
    currency: OptionalString,
    checkoutPaymentMethods: z.array(CheckoutPaymentMethod),
});

declare type LoadedEventPayload = z.infer<typeof LoadedEventPayload>;

declare class LoadedOncePublisher<T> {
    private _current: CurrentStatus<T>;
    private _subject: Subject<T>;

    constructor() {
        this._current = { status: 'initial', isSuccess: false };
        this._subject = new Subject<T>();
    }

    set = (value: T) => {
        if (this._current.status === 'success') {
            throw new Error('LoadedOnce is already in success state');
        }
        this._current = { status: 'success', isSuccess: true, loadedValue: value };
        this._subject.next(value);
        this._subject.complete();
    };

    throw = (error: unknown, errMsg: string) => {
        if (this._current.status === 'success') {
            throw new Error('LoadedOnce is already in success state');
        }
        this._current = { status: 'error', isSuccess: false, error, errMsg };
        this._subject.error(error);
        // Do not complete the subject since a set() call might be made after this
    };

    get current() {
        return this._current;
    }

    getValueIfLoadedElse = <T_ELSE>(valueIfNotLoaded: T_ELSE): T | T_ELSE => {
        if (this._current.status === 'success') return this._current.loadedValue;
        return valueIfNotLoaded;
    };

    subscribe = (fn: (value: Exclude<CurrentStatus<T>, InitialStatus>) => void) => {
        if (this._current.status === 'success') {
            fn(this._current);
            return;
        }

        if (this._current.status === 'error') {
            fn(this._current);
            // Do not return, as we will still have the fn subscribed to the subject
        }

        const subscription = this._subject.subscribe({
            next: () => {
                if (this._current.status !== 'success') {
                    throw new Error('Invalid state (next): please make sure to update _current before the subject');
                }
                fn(this._current);
                subscription.unsubscribe();
            },
            error: () => {
                if (this._current.status !== 'error') {
                    throw new Error('Invalid state (error): please make sure to update _current before the subject');
                }
                fn(this._current);
                // Do not unsubscribe
            },
        });
    };

    waitForLoad = (timeoutConfig: { timeoutSec: number; timeoutErrMsg: string }): Promise<T> => {
        if (this._current.status === 'success') return Promise.resolve(this._current.loadedValue);
        if (this._current.status === 'error') return Promise.reject(this._current.error);
        if (this._current.status !== 'initial') assertNever(this._current);

        const timeoutParams = {
            first: timeoutConfig.timeoutSec * 1000,
            with: () => throwError(() => new Error(timeoutConfig.timeoutErrMsg)),
        };

        /*
        * Note: lastValueFrom converts the observable to a promise (https://rxjs.dev/api/index/function/lastValueFrom)
        *
        * We use lastValueFrom to wait for the observable to close successfully before resolving the Promise.
        * To avoid hanging threads forever, we use timeoutParams to throw an error if it takes too long.
        * firstValueFrom is theoretically also usable, but it might result in bugs
        * if we do decide to emit more values in this Observable/Subject in the future.
        *
        * For more details, see: https://rxjs.dev/deprecations/to-promise
        */
        return lastValueFrom(this._subject.pipe(timeout(timeoutParams)));
    };
}

declare interface LoopConnectConfig {
    apiToken: string;
    entityId: string;
    merchantId?: string;
    environment?: Environment | `${Environment}`;
    customStyles?: CustomStyles;
    onInitialized?: (detail: InitializedEvent) => void;
    onInitFailed?: (detail: InitFailedEvent) => void;
    onWalletChange?: (detail: WalletChangeEvent) => void;
    onNetworkChange?: (detail: NetworkChangeEvent) => void;
}

declare type LoopCryptoFlowCustomParams =
| {
    customer: Omit<CustomerResponse, 'paymentMethods' | 'merchants' | 'dateCreated'>;
    paymentMethod: Omit<PaymentMethodResponse, 'customer' | 'dateCreated'>;
    mode: 'setup' | 'payment' | 'subscription';
    success: true;
    payinId?: string;
}
| {
    failureDetails: PayInFailedEvent;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    debugInfo?: Record<string, any>;
    success: false;
}
| undefined;

declare type LoopCryptoWidgetProps = {
    paymentUsdAmount: number;
    suggestedAuthorizationUsdAmount: number;
    minAuthorizationUsdAmount: number;
    subscriptionRefId?: string;
    customerRefId?: string;
    invoiceRefId?: string;
    allowCustomerToChooseAuth?: boolean;
};

declare interface NetworkChangeEvent {
    id: number;
    name: string;
    chain: BlockchainNetwork;
}

declare type OjsContext = {
    /**
     * The secure token for the checkout session.
     */
    checkoutSecureToken: string;

    /**
     * The form element for the OJS form (non-CDE form).
     */
    formDiv: HTMLElement;

    /**
     * The elements/partial session ID.
     */
    elementsSessionId: string;

    /**
     * All the checkout payment methods available in the session.
     */
    checkoutPaymentMethods: CheckoutPaymentMethod[];

    /**
     * All the CDE connection objects (one for each CDE iframe).
     */
    anyCdeConnection: CdeConnection;

    /**
     * Custom init params for init flows.
     */
    customInitParams: CustomInitParams;

    /**
     * The base URL of the CDE iframe.
     */
    baseUrl: string;

    /**
     * Whether to prevent navigation during submission.
     */
    preventNavigationDuringSubmission?: boolean;
};

declare type OjsFlow<T_PARAMS = unknown, T_INIT_RESULT extends InitOjsFlowResult = InitOjsFlowResult> = {
    init?: InitOjsFlow<T_INIT_RESULT>;
    run: RunOjsFlow<T_PARAMS, T_INIT_RESULT>;
};

declare type OjsFlowParams<T_PARAMS = void, T_INIT_RESULT = void> = {
    /**
     * Contains the context where OJS is run.
     * Ideally this only contains "background" context (OJS-level objects), and not flow-level objects.
     */
    context: OjsContext;

    /**
     * The checkout payment method object to be used for the flow.
     */
    checkoutPaymentMethod: CheckoutPaymentMethod;

    /**
     * Inputs from non-CDE fields (i.e. those which are part of the OJS form, but not the sensitive CDE form).
     */
    nonCdeFormInputs: Record<string, unknown>;

    /**
     * Form callbacks. Take note that these can be dynamically updated (but the object remains)
     */
    formCallbacks: FormCallbacks;

    /**
     * Custom parameters for the flow.
     */
    customParams: T_PARAMS;

    /**
     * The result of the InitOjsFlow function.
     */
    initResult: T_INIT_RESULT;
};

declare const OjsFlows = {
    // ✋ Note: For flows that require initialization, please add them to `init-flows.ts`

    // Common
    commonCC: {
        init: async () => {},
        run: runCommonCcFlow,
    },

    // Stripe
    stripePR: {
        init: initStripePrFlow,
        run: runStripePrFlow,
    },
    stripeLink: {
        init: initStripeLinkFlow,
        run: runStripeLinkFlow,
    },
    stripeKlarna: {
        init: initStripeKlarnaFlow,
        run: runStripeKlarnaFlow,
    },
    stripeAffirm: {
        init: initStripeAffirmFlow,
        run: runStripeAffirmFlow,
    },
    stripeAfterpay: {
        init: initStripeAfterpayFlow,
        run: runStripeAfterpayFlow,
    },

    // Pockyt
    pockytPaypal: {
        init: initPockytPaypalFlow,
        run: runPockytPaypalFlow,
    },

    // PayPal
    paypal: {
        init: initPaypalFlow,
        run: runPaypalFlow,
    },

    // Klarna
    airwallexKlarna: {
        init: initAirwallexKlarnaFlow,
        run: runAirwallexKlarnaFlow,
    },

    // Airwallex
    airwallexGooglePay: {
        init: initAirwallexGooglePayFlow,
        run: runAirwallexGooglePayFlow,
    },

    airwallexApplePay: {
        init: initAirwallexApplePayFlow,
        run: runAirwallexApplePayFlow,
    },

    airwallexACH: {
        init: async () => {},
        run: runAirwallexACHFlow,
    },

    airwallexEFT: {
        init: async () => {},
        run: runAirwallexEFTFlow,
    },

    airwallexBACS: {
        init: async () => {},
        run: runAirwallexBACSFlow,
    },

    airwallexSEPA: {
        init: async () => {},
        run: runAirwallexSEPAFlow,
    },

    // Loop
    loopCrypto: {
        init: initLoopFlow,
        run: runLoopFlow,
    },

    authorizeNetApplePay: {
        init: initAuthnetApplePayFlow,
        run: runAuthnetApplePayFlow,
    },

    authorizeNetGooglePay: {
        init: initAuthnetGooglePayFlow,
        run: runAuthnetGooglePayFlow,
    },

    adyenGooglePay: {
        init: initAdyenGooglePayFlow,
        run: runAdyenGooglePayFlow,
    },

    adyenApplePay: {
        init: initAdyenApplePayFlow,
        run: runAdyenApplePayFlow,
    },

    // 👉 Add more flows here

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
} satisfies Record<string, OjsFlow<any, any>>;

declare type OnCheckoutError = (message: string, errorCode?: string) => void;

declare type OnCheckoutStarted = () => void;

declare type OnCheckoutSuccess = (
invoiceUrls: string[],
subscriptionIds: string[],
customerId: string,
processorsUsed: string[],
paymentMethodId: string
) => void;

declare type OnSetupPaymentMethodSuccess = (paymentMethodId: string) => void;

declare type OnValidationError = (field: AllFieldNames, errors: string[], elementId?: string) => void;

export declare class OpenPayForm {
    readonly config: Config;
    readonly formId: string;
    readonly formTarget: string;
    readonly ojsVersion: string;
    readonly ojsReleaseVersion: string;
    readonly formProperties: {
        height: string;
    };
    readonly referrer: string;
    readonly baseUrl: string;
    readonly formCallbacks: FormCallbacks;
    private registeredElements;
    private isDestroyed;
    private eventHandler;
    private connectionManager;
    static ojsFlows: typeof OjsFlows;
    private readonly cdeLoadEvent;
    private readonly anyCdeConn;
    private readonly context;
    readonly initFlows: {
        readonly stripePR: {
            publisher: LoadedOncePublisher<InitStripePrFlowResult>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
        readonly stripeLink: {
            publisher: LoadedOncePublisher<InitStripeLinkFlowResult>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
        readonly stripeKlarna: {
            publisher: LoadedOncePublisher<InitStripeKlarnaFlowResult>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
        readonly stripeAffirm: {
            publisher: LoadedOncePublisher<InitStripeAffirmFlowResult>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
        readonly stripeAfterpay: {
            publisher: LoadedOncePublisher<InitStripeAfterpayFlowResult>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
        readonly airwallexGooglePay: {
            publisher: LoadedOncePublisher<InitGooglePayFlowResult>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
        readonly airwallexApplePay: {
            publisher: LoadedOncePublisher<InitApplePayFlowResult>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
        readonly loop: {
            publisher: LoadedOncePublisher<InitLoopCryptoFlowSuccess>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
        readonly paypal: {
            publisher: LoadedOncePublisher<{
                isLoading: boolean;
                isAvailable: boolean;
                startFlow: () => Promise<void>;
            }>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
        readonly airwallexKlarna: {
            publisher: LoadedOncePublisher<{
                isLoading: boolean;
                isAvailable: boolean;
                startFlow: (customParams?: KlarnaFlowCustomParams) => Promise<void>;
            }>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
        readonly pockytPaypal: {
            publisher: LoadedOncePublisher<{
                isLoading: boolean;
                isAvailable: boolean;
                startFlow: () => Promise<void>;
            }>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
        readonly authorizeNetGooglePay: {
            publisher: LoadedOncePublisher<InitGooglePayFlowResult>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
        readonly authorizeNetApplePay: {
            publisher: LoadedOncePublisher<InitApplePayFlowResult>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
        readonly adyenGooglePay: {
            publisher: LoadedOncePublisher<InitGooglePayFlowResult>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
        readonly adyenApplePay: {
            publisher: LoadedOncePublisher<InitApplePayFlowResult>;
            initialize: (initParams: InitOjsFlowParams) => Promise<void>;
        };
    };
    private deviceToken;
    private isSubmitting;
    constructor(config: Config);
    /**
     * Starts the form initialization process
     */
    private startFormInit;
    /**
     * Assign the instance to the window as a singleton
     */
    private static assignAsSingleton;
    /**
     * Get the singleton instance of OpenPayForm
     */
    static getInstance: () => OpenPayForm | null;
    get checkoutSecureToken(): string;
    setFormHeight: (height: string) => void;
    onCdeLoaded: (payload: LoadedEventPayload) => void;
    onCdeLoadError: (errMsg: string) => void;
    createElement: <T extends ElementType>(elementType: T, options?: ElementProps<T>) => RegisteredElement;
    registerIframe: (type: ElementType, frame: HTMLIFrameElement) => RegisteredElement;
    private buildQueryString;
    private connectToElement;
    private getFormDiv;
    /**
     * Builds the OJS context object. Take note that this is a pure function.
     */
    private static buildOjsFlowContext;
    /**
     * Runs the common credit card flow
     */
    submitCard: (params?: CCSubmitParams) => Promise<void>;
    private checkValidFormInstance;
    getAvailablePaymentMethods: () => Promise<{
        name: SubmitMethods;
        processors: PaymentProcessor[];
        isAvailable: boolean;
        controller?: unknown;
    }[]>;
    /**
     * @deprecated
     * We're normalizing the submit method to be the same in both React and Vanilla versions.
     * Please use `submitWith` instead.
     */
    generalSubmit: GeneralSubmit;
    submitWith: GeneralSubmit;
    /**
     * Alias for submitCard
     */
    submit: (params?: CCSubmitParams) => Promise<void>;
    destroy: () => void;
    /**
     * Updates form callbacks after initialization
     */
    updateCallbacks: (newCallbacks: AllCallbacks) => void;
}

declare enum PayInFailed {
    // Create payment method
    METHOD_CREATION_FAILED = 'methodCreationFailed',
    INSUFFICIENT_BALANCE = 'insufficientBalance',
    INSUFFICIENT_AUTHORIZATION = 'insufficientAuthorization',
    SIGNED_MESSAGE_REQUIRED = 'signedMessageRequired',
    CUSTOMER_CREATION_FAILED = 'customerCreationFailed',
    // Create pay-in
    PAYMENT_FAILED = 'paymentFailed',
    TRANSACTION_FAILED = 'transactionFailed',
}

declare interface PayInFailedData {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    error: Record<string, any> | undefined;
}

declare interface PayInFailedEvent {
    type: PayInFailed;
    message: string;
    data: PayInFailedData;
}

declare type PaymentDataRequest = google.payments.api.PaymentDataRequest;

declare interface PaymentMethodPreAuthorization {
    /**
     * The balance of the payment method formatted in the token amount
     * @example "100"
     */
    balance: string;
    /**
     * The authorization amount of the payment method formatted in the token amount
     * @example "49.9"
     */
    authorization: string;
}

declare interface PaymentMethodResponse {
    /**
     * The unique identifier for the payment method
     * @example "1234567890abcdef"
     */
    paymentMethodId: string;
    /**
     * The unique identifier of the merchant this payment method is associated with
     * @example "67e55044-10b1-426f-9247-bb680e5fe0c8"
     */
    merchantId: string;
    /**
     * The name of the payment method
     * @example "My Crypto Wallet"
     */
    paymentMethodName: string;
    /**
     * The customers that are associated with the payment method
     */
    customer: Omit<CustomerResponse, 'paymentMethods' | 'merchants' | 'dateCreated'>;
    /**
     * The blockchain network ID the payment method is associated with
     * @example 1
     */
    networkId: number;
    /**
     * The blockchain wallet address where payments will be sent from
     * @example "0x1234567890abcdef"
     */
    walletAddress: string;
    /**
     * Whether the payment method is the default payment method for wallet address
     * @example true
     */
    isDefault: boolean;
    /**
     * The token associated with the payment method
     */
    token: PaymentMethodToken;
    /**
     * The status of the payment method, including the wallet's balance and authorization status
     */
    preAuthorization: PaymentMethodPreAuthorization | null;
    /**
     * The date the payment method record was created, represented as a Unix timestamp in seconds.
     * @example 1716211200
     */
    dateCreated: number;
}

declare interface PaymentMethodToken extends Omit<TokenResponse, 'networkId'> {
    /**
     * The exchange rate of the token
     */
    exchangeRates: PaymentMethodTokenExchangeRate[];
}

declare interface PaymentMethodTokenExchangeRate {
    /**
     * The currency code. Only "USD" is supported at this time.
     * @example "USD"
     */
    currency: string;
    /**
     * The price of the token in the specified currency code. Accurate to 4 decimal places, e.g. a price of "1.9900" represents $1.99
     * @example "10000"
     */
    price: string;
    /**
     * The Unix timestamp (in seconds) when this exchange rate was last updated
     * @example 1715731200
     */
    timestamp: number;
}

declare const PaymentProcessor = z.enum(['stripe', 'airwallex', 'authorize_net', 'adyen', 'paypal', 'pockyt', 'loop']);

declare type PaymentProcessor = z.infer<typeof PaymentProcessor>;

declare const PaymentRequestProvider = z.enum(['apple_pay', 'google_pay']);

declare type PaymentRequestProvider = z.infer<typeof PaymentRequestProvider>;

declare type PaymentRequestStartParams = {
    overridePaymentRequest?: {
        amount?: Amount;
        pending?: boolean;
        label?: string;
        applePayPaymentRequest?: ApplePayPaymentRequest;
    };
};

declare type PaymentRequestStatus = {
    isLoading: boolean;
    isAvailable: boolean;
    startFlow: (params?: PaymentRequestStartParams) => Promise<void>;
};

declare type PaypalFlowCustomParams = {
    isAnonymous?: boolean;
    useRedirectFlow?: boolean;
};

declare type ProcessorSpecificSubmitSettings<T extends SubmitMethod = SubmitMethod> = {
    [SubmitMethods.card]: CCSubmitParams;
    [SubmitMethods.pockytPaypal]: { useRedirectFlow?: boolean };
    [SubmitMethods.applePay]: ApplePayFlowCustomParams;
    [SubmitMethods.googlePay]: GooglePayFlowCustomParams;
    [SubmitMethods.loopCrypto]: LoopCryptoFlowCustomParams;
    [SubmitMethods.paypal]: PaypalFlowCustomParams;
    [SubmitMethods.klarna]: KlarnaFlowCustomParams;
    [SubmitMethods.affirm]: AffirmFlowCustomParams;
    [SubmitMethods.afterpay]: AfterpayFlowCustomParams;
    [SubmitMethods.stripeLink]: unknown;
    // TODO: The following methods are deprecated and will be removed in the future.
    // Use googlePay/applePay instead
    [SubmitMethods.airwallexGooglePay]: GooglePayFlowCustomParams;
    [SubmitMethods.airwallexApplePay]: ApplePayFlowCustomParams;
    [SubmitMethods.authorizenetGooglePay]: GooglePayFlowCustomParams;
    [SubmitMethods.authorizenetApplePay]: ApplePayFlowCustomParams;
    [SubmitMethods.adyenGooglePay]: GooglePayFlowCustomParams;
    [SubmitMethods.adyenApplePay]: ApplePayFlowCustomParams;

    [SubmitMethods.airwallexACH]: AirwallexACHFlowParams;
    [SubmitMethods.airwallexEFT]: AirwallexEFTFlowParams;
    [SubmitMethods.airwallexBACS]: AirwallexBACSFlowParams;
    [SubmitMethods.airwallexSEPA]: AirwallexSEPAFlowParams;
}[T];

declare type PRStatuses = Record<PaymentRequestProvider, PaymentRequestStatus>;

declare type RegisteredElement = {
    type: ElementType;
    node: HTMLIFrameElement;
    mount: (selector: string) => void;
};

declare type RunOjsFlow<T_PARAMS = undefined, T_INIT_RESULT = undefined> = (
params: OjsFlowParams<T_PARAMS, T_INIT_RESULT>
) => Promise<void>;

export declare type StripeLinkController = {
    mountButton: (selector?: string) => void;
    dismountButton: () => void;
    waitForButtonToMount: () => Promise<HTMLElement>;
    mountLinkAuthenticationElement: (selector: string) => StripeLinkAuthenticationElement;
};

declare const SubmitMethod = z.nativeEnum(SubmitMethods);

declare type SubmitMethod = z.infer<typeof SubmitMethod>;

export declare enum SubmitMethods {
    card = 'card',
    pockytPaypal = 'pockyt-paypal',
    applePay = 'apple-pay',
    googlePay = 'google-pay',
    loopCrypto = 'loop-crypto',
    paypal = 'paypal',
    klarna = 'klarna',
    affirm = 'affirm',
    afterpay = 'afterpay',
    /**
     * This submit method only for Available payment methods.
     * Manual submission for StripeLink is not available
     */
    stripeLink = 'stripe-link',
    // TODO: The following methods are deprecated and will be removed in the future.
    /**
     * @deprecated Use googlePay instead
     */
    airwallexGooglePay = 'airwallex-google-pay',
    /**
     * @deprecated Use applePay instead
     */
    airwallexApplePay = 'airwallex-apple-pay',
    /**
     * @deprecated Use googlePay instead
     */
    authorizenetGooglePay = 'authorizenet-google-pay',
    /**
     * @deprecated Use applePay instead
     */
    authorizenetApplePay = 'authorizenet-apple-pay',
    /**
     * @deprecated Use googlePay instead
     */
    adyenGooglePay = 'adyen-google-pay',
    /**
     * @deprecated Use applePay instead
     */
    adyenApplePay = 'adyen-apple-pay',

    // DD
    airwallexACH = 'airwallex-ach',
    airwallexEFT = 'airwallex-eft',
    airwallexBACS = 'airwallex-bacs',
    airwallexSEPA = 'airwallex-sepa',
}

export declare type SubmitSettings<T extends SubmitMethod = SubmitMethod> = ProcessorSpecificSubmitSettings<T> &
CommonSubmitSettings;

declare type SuccessStatus<T> = {
    status: 'success';
    isSuccess: true;
    loadedValue: T;
};

declare interface TokenResponse {
    /**
     * The unique identifier for the token used by the payment type.
     * @example "123e4567-e89b-12d3-a456-426614174000"
     */
    tokenId: string;
    /**
     * The blockchain network ID the token is associated with
     * @example 1
     */
    networkId: number;
    /**
     * The token symbol that identifies the token on the blockchain network
     * @example "USDC"
     */
    symbol: string;
    /**
     * The token contract address for the payment type
     * @example "0x1234567890abcdef"
     */
    address: string;
    /**
     * The number of decimal places used to represent token amounts
     * @example 6
     */
    decimals: number;
}

declare type TracingContext = {
    checkoutSecureToken?: string;
    deviceToken?: string;
    formId?: string;
    version?: string;
    releaseVersion?: string;
    baseUrl?: string;
    referrer?: string;
};

declare interface WalletChangeEvent {
    address: string;
    ens: string | undefined;
}

export { }
