/**
 * Printerval Variants
 * @module PrintervalVariants
 * @exports PrintervalVariants
 * @type {{init: (function(*): PrintervalVariants)}}
 */

import {
    Variant,
    ProductVariant,
    Galleries,
    PrintervalVariantsOptions,
    VariantStatistic,
    ProductVariants
} from './types';

class PrintervalVariants {
    variants: Variant[];
    productVariants: ProductVariant[];
    galleries: Galleries;
    variantStatistic: VariantStatistic;
    products: ProductVariants;
    variantByOption: { [key: number]: number };
    groupVariants: Variant[];
    currentVariant: ProductVariant | null;

    constructor(options: PrintervalVariantsOptions) {
        this.variants = options.variants;
        this.productVariants = options.productVariants;
        this.galleries = options.galleries;
        this.variantStatistic = this.buildVariantStatistic(this.variants, this.productVariants);
        this.products = this.buildProductVariants(this.productVariants);
        this.variantByOption = this.buildVariantByOption(this.variants);
        this.groupVariants = this.buildGroupVariants(this.variants);
        this.currentVariant = null;
    }

    rebuildVariants(
        variants: Variant[],
        currentVariant: ProductVariant,
        variantsStatistic: VariantStatistic,
        variantByOption: { [key: number]: number },
        products: ProductVariants,
        groupVariants: Variant[]
    ){
        for (let index = 0; index < variants.length; index++) {
            const variant = variants[index];
            let variantKeyMap = new Map();
            currentVariant.variants.forEach(id => {
                variantKeyMap.set(variantByOption[id], id);
            })
            let otherVariants = currentVariant.variants.filter(id => variantByOption[id] != variant.id);
            otherVariants.forEach(id => {
                variantKeyMap.set(variantByOption[id], id);
            });
            let optionGroup: { id: number; variant_id?: number; variant_slug?: string; }[] = [];
            variant.values.forEach(item => {
                let tmpKeyMap = new Map(variantKeyMap);
                tmpKeyMap.set(variant.id, item.id);
                let itemVariantKey = [...tmpKeyMap.values()].join('-');
                let prefixVariant = '';
                let variantIsOk = true;
                if (variant.slug == 'style' && index >= 0) {
                    if (otherVariants && otherVariants.length >= 2) {
                        prefixVariant = otherVariants[0] + '-' + item.id;
                        variantIsOk = false;
                        if (prefixVariant && variantsStatistic) {
                            if (variantsStatistic[prefixVariant]) {
                                variantIsOk = true;
                            } else if (variantsStatistic[item.id + '-' + otherVariants[0]]) {
                                variantIsOk = true;
                            }
                        }
                        if (index == 0 && !variantIsOk && variantsStatistic[item.id]) {
                            variantIsOk = true;
                        }
                    }
                }
                if (variantIsOk && ((products.productByUniqId[itemVariantKey] && products.productByUniqId[itemVariantKey].id) || variant.show_invalid)) {
                    optionGroup.push(item);
                } else if (variantIsOk && variant.show_invalid_above) {
                    let aboveKey = currentVariant.variants.filter((id, idx) => idx < index).join('-');
                    if (products.productByUniqIdAbove[aboveKey]) {
                        optionGroup.push(item);
                    }
                }
            });
            groupVariants.forEach(item => {
                if (item.id == variant.id) {
                    item.values = optionGroup;
                }
            });
        }
    }

    buildVariantStatistic (variants: Variant[], productVariants: ProductVariant[]): VariantStatistic {
        let variantsStatistic: { [key: string]: {id: number, sku_key: string, count: number, price: number, high_price: number} } = {};
        if (variants.length < 2) {
            return variantsStatistic;
        }

        let firstOptionIndex = 0;
        for (let key in variants) {
            if (variants[key].type == 'OPTION') {
                firstOptionIndex = parseInt(key);
                break;
            }
        }

        for (let item of productVariants) {
            let vKeyArr = item.variants.filter((value, index) => index <= firstOptionIndex).map(item => item);
            for (let i = firstOptionIndex + 1; i < item.variants.length; i++) {
                let vKey = vKeyArr.join('-') + '-' + item.variants[i];
                if (!variantsStatistic[vKey]) {
                    variantsStatistic[vKey] = {
                        id: item.id,
                        sku_key: item.variants.join('-'),
                        count: 1,
                        price: item.price,
                        high_price: item.high_price
                    }
                } else {
                    variantsStatistic[vKey].count++;
                    if (item.price < variantsStatistic[vKey].price) {
                        variantsStatistic[vKey].id = item.id;
                        variantsStatistic[vKey].sku_key = item.variants.join('-');
                        variantsStatistic[vKey].price = item.price;
                        variantsStatistic[vKey].high_price = item.high_price;
                    }
                }
            }

            let vKey = vKeyArr.join('-');

            if (!variantsStatistic[vKey]) {
                variantsStatistic[vKey] = {
                    id: item.id,
                    sku_key: item.variants.join('-'),
                    count: 1,
                    price: item.price,
                    high_price: item.high_price
                }
            } else {
                variantsStatistic[vKey].count++;
                if (item.price < variantsStatistic[vKey].price) {
                    variantsStatistic[vKey].id = item.id;
                    variantsStatistic[vKey].sku_key = item.variants.join('-');
                    variantsStatistic[vKey].price = item.price;
                    variantsStatistic[vKey].high_price = item.high_price;
                }
            }
        }

        return variantsStatistic;
    }

    buildProductVariants(productVariants: ProductVariant[]): ProductVariants {
        const productById: { [key: number]: {id: number, sku: string, price: number, high_price: number, variants: number[]} } = {};
        const productByUniqId: { [key: string]: any } = {};
        const productByUniqIdAbove: { [key: string]: any } = {};

        if (productVariants) {
            productVariants.forEach(function(item) {
                productById[item.id] = {
                    id: item.id,
                    sku: item.sku,
                    price: item.price,
                    high_price: item.high_price,
                    variants: item.variants
                };
                const variantOptionIds = item.variants;
                if (variantOptionIds.length > 0) {
                    const key = variantOptionIds.join("-");
                    productByUniqId[key] = item;
                    if (item.variants.length > 2) {
                        for (let j = 1; j < variantOptionIds.length - 2; j++) {
                            let aboveKey = variantOptionIds.slice(0, j).join('-');
                            productByUniqIdAbove[aboveKey] = item;
                        }
                    }
                }
            });
        }

        return {
            productById: productById,
            productByUniqId: productByUniqId,
            productByUniqIdAbove: productByUniqIdAbove
        };
    }

    buildVariantByOption(variants: Variant[]): { [key: number]: number } {
        let variantByOption: { [key: number]: number } = {};
        let optionById: { [key: number]: any } = {};
        variants.forEach(variant => {
            variant.values.forEach(item => {
                variantByOption[item.id] = variant.id
                item.variant_id = variant.id;
                item.variant_slug = variant.slug;
                optionById[item.id] = item;
            })
        });

        return variantByOption;
    }

    buildGroupVariants(variants: Variant[]): Variant[] {
        variants.forEach((element, index) => {
            element.show_invalid = index == 0;
            if (variants[0].type != 'OPTION') {
                if (index <= variants.length - 3) {
                    element.show_invalid = true;
                }
                if (index <= variants.length - 2 && element.type != "IMAGE") {
                    element.show_invalid_above = true;
                }
            } else if (index && index == variants.length - 2 && element.type == "OPTION") {
                element.show_invalid_above = true;
            }
        });

        return variants;
    }

    getProductSkuDetails(productSkuId: number): {
        product: ProductVariant;
        variants: Variant[];
        galleries: string[];
        price: number;
        highPrice: number;
    }
    {
        if (!this.variants) {
            throw new Error('Variants Data not set');
        }

        if (!this.productVariants) {
            throw new Error('Product Variants Data not set');
        }

        if (!this.galleries) {
            throw new Error('Galleries not set');
        }

        this.currentVariant = this.productVariants.find(v => v.id === productSkuId) || null;

        if (!this.currentVariant) {
            throw new Error(`Product SKU ${productSkuId} not found`);
        }

        this.rebuildVariants(
            this.variants,
            this.currentVariant,
            this.variantStatistic,
            this.variantByOption,
            this.products,
            this.groupVariants
        );

        let galleries = this.galleries[this.currentVariant.id] ?? [];

        let price = this.currentVariant.price;
        let highPrice = this.currentVariant.high_price;

        return {
            product: this.currentVariant,
            variants: this.groupVariants,
            galleries: galleries,
            price: price,
            highPrice: highPrice
        }
    }

    static init(options: PrintervalVariantsOptions) {
        return new PrintervalVariants(options);
    }
}

export default PrintervalVariants;