declare const SKIKClient: SKIKClient.Static;
export default SKIKClient;
export as namespace SKIKClient;

/**
 * Type declarations for the skik-client JS package used to integrate a SKIK
 * closet system configurator into another web page. The configurator is
 * externally hosted, and will be implemented through an embedded i-frame.
 * This package makes integration easy by providing an expressive interface
 * to communicate with the application, and subscribe to events and changes
 * to the configuration state.
 *
 * @author Jeffrey Westerkamp <jeffrey@pixelindustries.com> (https://github.com/JJWesterkamp)
 */
declare namespace SKIKClient {

    // -----------------------------------------------------------------------
    // Type aliases (for semantics)
    // -----------------------------------------------------------------------

    /**
     * A SKIK Configuration hash is a string representing a certain SKIK
     * closet configuration. Such a hash can after retrieval be used to query
     * for the relevant product data server-side (e.g. for adding items to cart)
     * or simply to associate it as a 'favorite' of a certain user account.
     * Hashes represent persisted, immutable SKIK closet configurations.
     */
    type SKIKConfigurationHash = string;

    /**
     * Valid languages the configurator is able to run in, in ISO 639-1 format.
     */
    type AvailableLocale = 'nl' | 'en';

    // -----------------------------------------------------------------------
    // Mapping of public event names to their associated payload signatures.
    // -----------------------------------------------------------------------

    interface EventPayloadMap {

        /**
         * Event fired when the height of the SKIK Configurator document
         * changed. The new height in pixels is the event's payload.
         * This event is mostly relevant for custom i-frame size management.
         */
        'child-document-height-changed': number;

        /**
         * Event fired when the height of the i-frame was explicitly set by
         * the SKIK Client instance. The new height of the frame is the
         * event's payload.
         */
        'frame-height-set': number;

        /**
         * Event fired when the price of the active configuration changed.
         * A price-mutation report object is the event's payload. This event
         * can be used to display the price of the currently active closet
         * configuration. (The configurator itself does not display prices)
         */
        'price-changed': PriceMutationReport;


        /**
         * Event fired when the display locale (language) changed in the
         * configurator. Locale is in ISO 639-1 format.
         */
        'locale-changed': AvailableLocale;
    }

    // -----------------------------------------------------------------------
    // Event channel and payload types
    // -----------------------------------------------------------------------

    type EventChannel = keyof EventPayloadMap;
    type EventPayload<E extends EventChannel> = EventPayloadMap[E];

    // -----------------------------------------------------------------------
    // Event subscription interface
    // -----------------------------------------------------------------------

    interface EventSubscription<E extends EventChannel> {

        /**
         * The channel name of the event this subscription belongs to.
         */
        readonly channelName: E;

        /**
         * Boolean to check if the event-subscriber unsubscribed. If true,
         * the subscription is effectively dead.
         */
        readonly isUnsubscribed: boolean;

        /**
         * Boolean indicating whether handling of events is disabled. if
         * true, events will not be handled, until resume() is called.
         */
        readonly isPaused: boolean;

        /**
         * Pauses this event subscription. New events will not be handled,
         * until resume() is called.
         */
        pause(): this;

        /**
         * Resumes this event subscription. New events will be handled again.
         */
        resume(): this;

        /**
         * Un-subscribes from the event, essentially killing this subscription.
         * After unsubscribe is called, the subscription object can be
         * disposed of (i.e. out of scope / unset reference).
         */
        unsubscribe(): void;
    }

    // ---------------------------------------------------------------------
    // Constructor options signature for creating new SKIKClient instances.
    // ---------------------------------------------------------------------

    /**
     * Constructor options interface for SKIKClient.Client instance creation.
     *
     * @property {string} containerId
     * @property {string} apiKey
     * @property {boolean} [autoResizeFrame = true]
     * @property {string | null} [configurationHash = null]
     * @property {string} [configuratorOrigin = 'ht']
     */
    interface ConstructorOptions {

        /**
         * The ID attribute value of the DOM element in which to build the
         * SKIK-Configurator i-frame.
         */
        containerId: string;

        /**
         * Your API key for the SKIK Configurator application
         */
        apiKey: string;

        /**
         * Boolean indicating whether the i-frame should resize automatically.
         * In most cases this is desired, to prevent double scrollbars in the
         * browser window. Can be disabled if necessary.
         *
         * @default false
         */
        autoResizeFrame?: boolean;

        /**
         * Optionally provide a SKIK-Configuration hash that should be used
         * to boot the configurator with. The configuration that's represented
         * by the hash will be shown initially. If no hash is given, a default
         * configuration will be shown.
         *
         * @default null
         */
        configurationHash?: string | null;

        /**
         * Optionally define a language for the configurator to run in.
         *
         * @default 'nl'
         */
        locale?: SKIKClient.AvailableLocale;

        /**
         * Optional boolean indicating whether or not to display a locale / language
         * switch within the configurator header.
         *
         * @default true
         */
        showLocaleSwitch?: boolean;

        /**
         * Optional boolean indicating whether or not to display a button to activate
         * display of a product list modal.
         *
         * @default true
         */
        showProductManifestButton?: boolean;
    }

    // ---------------------------------------------------------------------
    //      Static main module interface
    // ---------------------------------------------------------------------

    /**
     * The static interface of this package.
     */
    interface Static {

        /**
         * Creates a new SKIKClient instance, and returns a public API object
         * to manage the configurator with, and listen for external events.
         */
        create(options: SKIKClient.ConstructorOptions): SKIKClient.Client;
    }

    // ---------------------------------------------------------------------
    //      Client instance interface
    // ---------------------------------------------------------------------

    /**
     * The public interface for a SKIKClient instance.
     */
    interface Client {

        /**
         * Express interest in a certain event channel, and provide a function
         * to call whenever that event fires, receiving the event's payload as
         * first and only argument.
         *
         * @param {string} eventChannel
         * @param {function(payload: *): void} handler
         * @return {SKIKClient.EventSubscription}
         */
        on<E extends EventChannel>(eventChannel: E, handler: (payload: EventPayload<E>) => void): SKIKClient.EventSubscription<E>;

        /**
         * Returns a promise that will in time resolve in the currently active
         * configuration of the SKIK Configurator.
         *
         * @return {Promise<SKIKConfigurationHash>}
         */
        getConfigurationHash(): Promise<SKIKConfigurationHash>;

        /**
         * Returns the IFrame element object in which the configurator is loaded.
         *
         * @return {HTMLIFrameElement}
         */
        frameElement(): HTMLIFrameElement;

        // ---------------------------------------------------------------------
        //      Display settings
        // ---------------------------------------------------------------------

        /**
         * Enables auto-resizing of the i-frame, to keep its height a match
         * with the contained document.
         *
         * @return {Client}
         */
        enableAutoResize(): this;

        /**
         * Disables auto-resizing of the i-frame, to keep its height a match
         * with the contained document.
         *
         * @return {Client}
         */
        disableAutoResize(): this;

        /**
         * Sets the locale (language) of the configurator to given value. Text of
         * the configurator is updated in real-time.
         *
         * @return {Client}
         */
        setLocale(locale: SKIKClient.AvailableLocale): this;

        /**
         * Shows the locale switch at the top right, if it was not already shown.
         */
        showLocaleSwitch(): this;

        /**
         * Hides the locale switch at the top right, if it was not already hidden.
         */
        hideLocaleSwitch(): this;

        /**
         * Shows the locale switch at the top right, if it was not already shown.
         */
        showProductManifestButton(): this;

        /**
         * Hides the locale switch at the top right, if it was not already hidden.
         */
        hideProductManifestButton(): this;

        // ---------------------------------------------------------------------
        //      Decorating product data
        // ---------------------------------------------------------------------

        /**
         * Returns all EAN codes of products for which data is required, before the
         * application can ensure proper calculation of configuration prices.
         */
        getEANCodes(): Promise<string[]>;

        /**
         * Stores given product prices, and then uses it to calculate / publish the
         * total price of the current configuration.
         */
        setProductPrices(prices: ProductPrices[]): Promise<boolean>;

        /**
         * Stores given article numbers, and then uses them instead of EAN codes for
         * display purposes.
         */
        setArticleNumbers(articleNumbers: ArticleNumber[]): Promise<boolean>;
    }

    // ---------------------------------------------------------------------
    //      Product data interface
    // ---------------------------------------------------------------------

    /**
     * A ProductPrices interface describes vendor-specific prices
     * for a certain product, identified by its EAN product code. this
     * data is optionally injected in real-time into the configurator,
     * to activate automated price calculations.
     *
     * @see {SKIKClient.Client.setProductData}
     */
    interface ProductPrices {

        /**
         * The EAN code for the product.
         */
        EANCode: string;

        /**
         * The original selling price in cents, including VAT.
         */
        originalPrice: number;

        /**
         * A discount price in cents, including VAT. If no discount should be
         * applied to the product's price, discountPrice must equal originalPrice.
         */
        discountPrice: number;
    }


    /**
     *
     */
    interface ArticleNumber {
        EANCode: string;
        articleNumber: string;
    }

    // ---------------------------------------------------------------------
    //      Price mutation reports
    // ---------------------------------------------------------------------

    /**
     * Represents a price in cents.
     *
     * @property {number} cents
     * @property {string} formatted
     */
    interface Price {

        /**
         * The amount of cents this price represents
         */
        readonly cents: number;

        /**
         * A pre-formatted string representing the price in euros (€). if
         * a custom format is required, it should be manually derived from
         * the cents property.
         */
        readonly formatted: string;
    }

    /**
     * The payload for 'price-changed' events.
     *
     * @property {{ originalPrice: Price, discountPrice: Price }} before
     * @property {{ originalPrice: Price, discountPrice: Price }} after
     */
    interface PriceMutationReport {

        /**
         * The original and discount prices before the mutation happened.
         */
        readonly before: {
            originalPrice: Price;
            discountPrice: Price;
        };

        /**
         * The original and discount prices after the mutation happened.
         */
        readonly after: {
            originalPrice: Price;
            discountPrice: Price;
        };
    }
}
