'use strict';

import {createWithEqualityFn} from "zustand/traditional";
import {DATA as shared_data, itemsUnfoldedList, API_RFQ} from "./utils";
import {FillExtCrateData, FillExtOrderData, true_type_of} from "./options/utils";
import {v4 as uuidv4} from "uuid";
import {FillResources} from "./count_resources";
import {FillExtCardData} from "./options/utils";
import {TriggerCrateWarnings, TriggerWarnings} from "./warnings";
import {Validation, validateEmail, validateNote, validateJSONInput} from "./validate";
import {CratesToJSON, JSONToCrates} from "./json_porter";
import {ProcessOptionsToData} from "./options/Options";
import {DomainedRFQMessages} from "./Domained";


const cards_to_pn_map = (cards) => {
    let result = {};
    Object.entries(cards).forEach(([key, card], _i) => { result[card.name_number] = key})
    return result;
};

const useCatalog = ((set, get) => ({
    cards: shared_data.items,
    groups: shared_data.columns.catalog,
    cards_list: itemsUnfoldedList,
    currency: shared_data.currency,
    pn_to_cards: cards_to_pn_map(shared_data.items),
    getCardDescription: index => get().cards[get().cards_list[index]],
    getCardDescriptionByPn: pn => get().cards[get().pn_to_cards[pn]],
    cardIndexById: card_id => get().cards_list.findIndex((element) => (card_id === element))
}));

const useOptionsNotification = ((set, get) => ({
    notificationCrateId: null,
    notificationCardIndex: null,
    notificationTimer: null,
    _showNotification: (crate_id, card_index) => set(state => ({
        notificationCrateId: crate_id,
        notificationCardIndex: card_index,
        notificationTimer: setTimeout(() => {
            state.hideNotification()
        }, 5000)
    })),
    showNotification: (crate_id, card_index) => {
        get().hideNotification()
        setTimeout(() => get()._showNotification(crate_id, card_index), 100);
    },
    hideNotification: () => set(state => ({
        notificationCrateId: null,
        notificationCardIndex: null,
        notificationTimer: (state.notificationTimer && clearTimeout(state.notificationTimer)) || null,
    }))
}));

const useSearch = ((set, get) => ({
    search_index: Array.from(Object.values(shared_data.items)
        .map((card, _) => (
            [(card.name + " " + card.name_number + " " + card.name_codename).toLowerCase(), card.id]
    ))),
    search_bar_value: "",
    listed_cards: [],
    updateSearchBar: text => set(state => ({
        search_bar_value: text,
        listed_cards: text.length > 0 ? Array.from(get().search_index
            .filter((card, _) => card[0].includes(text.toLowerCase()))
            .map(([index, card_id], _) => get().cards_list.findIndex(elem => elem === card_id))) : []
    }))
}));

const useCrateModes  = ((set, get) => ({
    crate_modes: shared_data.crateModes,
    modes_order: shared_data.crateModeOrder,
    crateParams: mode => get().crate_modes[mode],
}));

const useCrateOptions  = ((set, get) => ({
    crate_options: shared_data.crateOptions.options,
    crate_prices: shared_data.crateOptions.prices,

    fillExtCrateData: (crate_id) => set(state => ({
        crates: state.crates.map((crate, _i) => {
            if (crate_id === crate.id) {
                const previous_options = crate.options_data || {};
                return {
                    ...crate,
                    options_data: {
                        ...previous_options,
                        ext_data: FillExtCrateData(crate)
                    }
                }
            }
            else return crate;
        })
    })),

    _updateCrateOption: (crate_id, new_options) => set(state => ({
        crates: state.crates.map((crate, _i) => {
            if (crate_id === crate.id) {
                const previous_options = crate.options_data || {};
                return {
                    ...crate,
                    options_data: {
                        ...previous_options,
                        ...new_options
                    }
                }
            }
            else return crate;
        })
    })),

    updateCrateOptions: (crate_id, new_options) => {
        get().fillExtCrateData(crate_id);
        get().fillOrderExtData();
        get()._updateCrateOption(crate_id, new_options);
        get()._updateTotalOrderPrice();
    }
}));

const useOrderOptions  = ((set, get) => ({
    order_options: shared_data.orderOptions.options,
    order_prices: shared_data.orderOptions.prices,
    shipping_summary: shared_data.orderOptions.shippingSummary,
    order_options_data: {},

    fillOrderExtData: () => set(state => ({
        order_options_data: {
            ...state.order_options_data,
            ext_data: FillExtOrderData(state.crates, state.modes_order)
        }
    })),

    _updateOrderOptions: (new_options) => set(state => ({
        order_options_data: {
            ...state.order_options_data,
            ...new_options
        }
    })),

    updateOrderOptions: (new_options) => {
        get()._updateOrderOptions(new_options);
        get()._updateTotalOrderPrice();
        get().fillOrderExtData();
    }
}));

const useLayout = ((set, get) => ({
    isTouch: window.isTouchEnabled(),
    isMobile: window.deviceIsMobile(),
    sideMenuIsOpen: false,
    showCardAddedFeedback: false,
    showNoDestination: false,
    timerAdded: null,

    _switchSideMenu: () => set(state => ({
        sideMenuIsOpen: !state.sideMenuIsOpen
    })),
    switchSideMenu: () => {
        if (!get().sideMenuIsOpen) {
            get().hideNotification()
        }
        get()._switchSideMenu();
    },
    cardAdded: () => set(state => ({
        showCardAddedFeedback: true,
        showNoDestination: false,
        timerAdded: (!!state.timerAdded ? clearTimeout(state.timerAdded) : null) || (state.isMobile && setTimeout(() => {
            get()._endCardAdded()
        }, 2000))
    })),
    noDestinationWarning: () => set(state => ({
        showCardAddedFeedback: true,
        showNoDestination: true,
        timerAdded: (!!state.timerAdded ? clearTimeout(state.timerAdded) : null) || (setTimeout(() => {
            get()._endCardAdded()
        }, 2000))
    })),
    _endCardAdded: () => set(state => ({
        showCardAddedFeedback: false,
        timerAdded: !!state.timerAdded ? clearTimeout(state.timerAdded) : null
    }))
}));

const useImportJSON = ((set, get) => ({
    importShouldOpen: false,
    importValue: {
        value: "",
        error: Validation.OK
    },
    openImport: () => set(state => ({
        importShouldOpen: true
    })),
    closeImport: () => set(state => ({
        importShouldOpen: false
    })),
    _loadDescription: () => set(state => {
        const parsed = JSONToCrates(state.importValue.value);
       // if (parsed.crates[-1].crate_mode !== "")
        return {
            importShouldOpen: false,
            // additional fields go here
            crates: parsed.crates,
            order_options_data: parsed.order_options_data
    }}),
    loadDescription: () => {
        get()._loadDescription()
        get().fillOrderExtData();
        get().crates.forEach((crate, _i) => {
            get().fillExtData(crate.id);
            get().fillWarnings(crate.id);
            get().fillExtCrateData(crate.id);
        });
        get()._updateTotalOrderPrice();
        get().showNotification(get().active_crate, null);
    },
    updateImportDescription: (new_description) => set(state => ({
        importValue: {
            value: new_description,
            error: validateJSONInput(new_description)
        }
    }))

}));

const useSubmitForm = ((set, get) => ({
    isProcessing: false,
    shouldShowRFQFeedback: false,
    processingResult: {
        status: Validation.OK,
        message: ""
    },
    API_RFQ: API_RFQ,
    email: {
        value: "",
        error: null
    },
    note: {
        value: "",
        error: Validation.OK
    },

    description: "",
    shouldShowDescription: false,

    updateEmail: (new_email) => set(state => ({
        email: {
            value: new_email,
            error: validateEmail(new_email)
        }
    })),
    updateNote: (new_notes) => set(state => ({
        note: {
            value: new_notes,
            error: validateNote(new_notes)
        }
    })),
    resetEmailValidation: () => set(state => ({
        email: {
            value: state.email.value,
            error: Validation.OK
        }
    })),

    _revalidateForm: () => set(state => ({
        email: {
            value: state.email.value,
            error: validateEmail(state.email.value)
        },
        note: {
            value: state.note.value,
            error: validateEmail(state.note.value)
        },
    })),

    updateDescription: () => set(state => ({
        description: CratesToJSON(state.crates)
    })),
    showDescription: () => set(state => ({
        description: CratesToJSON(state.crates),
        shouldShowDescription: true
    })),
    closeDescription: () => set(state => ({
        shouldShowDescription: false
    })),
    _submitForm: () => set(state => ({isProcessing: true})),
    finishSubmitForm: (result) => set(state => ({
        isProcessing: false,
        shouldShowRFQFeedback: true,
        processingResult: result})),

    submitDisabled: () => (get().email.error !== Validation.OK),

    submitForm: () => {
        get().updateDescription();
        get()._revalidateForm();
        get()._submitForm();
        if (get().submitDisabled()) return;
        fetch(get().API_RFQ, {
            method: "POST",
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({
                email: get().email.value,
                note: get().note.value,
                configuration: get().description
            })
        }).then(response => {
            if (response.status !== 200) {
                throw Error("Response status is not OK: " + response.status + ".\n" + response);
            }
            get().finishSubmitForm({status: Validation.OK, message: DomainedRFQMessages.OK})
        }, reason => {
            console.error("Request rejected, reason:", reason)
            get().finishSubmitForm({
                status: Validation.Invalid,
                message: DomainedRFQMessages.ERROR
            })
        }).catch(err => {
            console.error("Request failed, reason:", err)
            get().finishSubmitForm({
                status: Validation.Invalid,
                message: DomainedRFQMessages.ERROR
            })
        })
    },
    closeRFQFeedback: () => set(state => ({shouldShowRFQFeedback: false}))
}));

const useHighlighted = ((set, get) => ({
    highlighted: {
        crate: "",
        card: 0
    },
    highlightedTimer: null,

    // #!if disable_card_highlight === false
    highlightCard: (crate_id, index) => set(state => ({
        highlighted: {
            crate: crate_id,
            card: index
        },
        highlightedTimer: (!!state.highlightedTimer ? clearTimeout(state.highlightedTimer) : null) || (state.isTouch && setTimeout(() => {
            get().highlightReset()
        }, 2000))
    })),
    highlightReset: () => set(state => ({
        highlighted: {
            crate: "",
            card: 0
        },
        highlightedTimer: !!state.highlightedTimer ? clearTimeout(state.highlightedTimer) : null
    })),
    // #!else
    highlightCard: () => {return null;},
    highlightReset: () => {return null;},
    // #!endif
}));


const useCart = ((set, get) => ({
    crates: shared_data.columns.crates,
    active_crate: "crate0",
    _defaultCrates: Array.from(shared_data.columns.crates),
    total_order_price: 0,

    _newCrate: (crate_id) => set((state) => ({
        crates: state.crates.toSpliced(-1, 0, {
            ...state._defaultCrates[0],
            id: crate_id || "crate" + state.crates.length,
        }),
        active_crate: crate_id || "crate" + state.crates.length
    })),
    _delCrate: (id) => set(state => ({
        crates: state.crates.filter((crate => crate.id !== id || !state.modes_order.includes(crate.crate_mode))),
        active_crate: state.active_crate === id ? null : state.active_crate,
    })),
    _setCrateMode: (id, mode) => set(state => ({
        crates: state.crates.map((crate, _i) => {
            if (crate.id === id) {
                return {
                    ...crate,
                    crate_mode: mode
                }
            } else return crate;
        })
    })),
    setActiveCrate: (id) => set(state => ({active_crate: id})),
    _addCardFromCatalog: (crate_to, index_from, index_to) => set(state => {
        const take_from = (true_type_of(index_from) === "array" ? index_from : [index_from]).map((item, _i) => (state.cards_list[item]));
        const dest = crate_to || state.active_crate;
        if (!dest) return {};
        return {
            crates: state.crates.map((crate, _i) => {
                if (dest === crate.id) {
                    index_to = index_to != null ? index_to : crate.items.length;
                    return {
                        ...crate,
                        items: crate.items.toSpliced(index_to, 0, ...take_from.map((card_name, _) => {
                            return {...state.cards[card_name], id: uuidv4()}
                        }))
                    }
                } else return crate;
            })
        }
    }),
    _moveCard: (crate_from, index_from, crate_to, index_to) => set(state => {
        const the_card = state.crates.find((crate, _) => crate_from === crate.id ).items[index_from];
        return {
            crates: state.crates.map((crate, _i) => {
                if (crate_to === crate_from && crate_to === crate.id) {
                    let items_copy = Array.from(crate.items);
                    let item = items_copy.splice(index_from, 1)[0]
                    items_copy.splice(index_to, 0, item).filter((item, _) => !!item)
                    return {
                        ...crate,
                        items: items_copy
                    }
                } else if (crate_to === crate.id) {
                    return {
                        ...crate,
                        items: crate.items.toSpliced(index_to, 0, the_card)
                    }
                } else if (crate_from === crate.id) {
                    return {
                        ...crate,
                        items: crate.items.toSpliced(index_to, 1)
                    }
                }
                else return crate;
            })
        }
    }),
    _deleteCard: (crate_id, index) => set(state => ({
        crates: state.crates.map((crate, _i) => {
            if (crate_id === crate.id) {
                return {
                    ...crate,
                    items: crate.items.toSpliced(index, 1)
                }
            }
            else return crate;
        })
    })),
    _clearCrate: (id) => set(state => ({
        crates: state.crates.map((crate, _i) => {
            if (id === crate.id) {
                return {
                    ...crate,
                    items: []
                }
            }
            else return crate;
        })
    })),
    clearAll: () => set(state => ({
        crates: state._defaultCrates
    })),
    _updateOptions: (crate_id, index, new_options) => set(state => ({
        crates: state.crates.map((crate, _i) => {
            if (crate_id === crate.id) {
                let itemsCopy = Array.from(crate.items);
                itemsCopy[index] = {
                    ...itemsCopy[index],
                    options_data: {
                        ...itemsCopy[index].options_data,
                        ...new_options
                    }};
                return {
                    ...crate,
                    items: itemsCopy
                }
            }
            else return crate;
        })
    })),

    fillWarnings: (crate_id) => set(state => ({
        crates: state.crates.map((crate, _i) => {
            if (crate_id === crate.id) {
                //console.log("--- CHECK ALERTS ---")
                let itemsCopy = Array.from(crate.items);
                const disabled = !!get().crateParams(crate.crate_mode).warnings_disabled;
                itemsCopy = FillResources(itemsCopy, disabled);
                itemsCopy = TriggerWarnings(itemsCopy, disabled);
                const [crate_warnings, occupied]  = TriggerCrateWarnings(crate);
                return {
                    ...crate,
                    items: itemsCopy,
                    warnings: crate_warnings,
                    occupiedHP: occupied
                }
            }
            else return crate;
        })
    })),

    fillExtData: (crate_id) => set(state => ({
        crates: state.crates.map((crate, _i) => {
            if (crate_id === crate.id) {
                let itemsCopy = Array.from(crate.items);

                itemsCopy = itemsCopy.map((item, index) => {
                    if (!item.options) return item;
                    if (!item.options_data) item.options_data = {};
                    item.options_data.ext_data = FillExtCardData(itemsCopy, index);
                    return item;
                });
                return {
                    ...crate,
                    items: Array.from(itemsCopy)
                }
            }
            else return crate;
        })
    })),

    _updateTotalOrderPrice: () => set(state => {
        let sum = 0;
        get().crates.forEach( (crate, i) => {
            sum += get().crate_modes[crate.crate_mode].price;
            const crate_options = ProcessOptionsToData({options: get().crate_prices, data: crate.options_data || {}});
            sum += crate_options ? crate_options.reduce((accumulator, currentValue) => accumulator+currentValue.price, 0) : 0;
            crate.items.forEach((item, _) => {
                sum += item.price;
            });
        });
        const order_options = ProcessOptionsToData({options: get().order_prices, data: get().order_options_data || {}});
        sum += order_options ? order_options.reduce((accumulator, currentValue) => accumulator+currentValue.price, 0) : 0;
        return {total_order_price: sum};
    }),

    // Composite actions that require warnings recalculation:

    newCrate: () => {
        const crate_id = "crate" + get().crates.length;
        get()._newCrate(crate_id)
        get().fillExtData(crate_id);
        get().fillExtCrateData(crate_id);
        get().fillOrderExtData();
        get().fillWarnings(crate_id);
        get()._updateTotalOrderPrice();
    },

    setCrateMode: (id, mode) => {
        get()._setCrateMode(id, mode)
        get().fillExtData(id);
        get().fillExtCrateData(id);
        get().fillOrderExtData();
        get().fillWarnings(id);
        get().setActiveCrate(id);
        get()._updateTotalOrderPrice();
    },

    delCrate: (id) => {
        get()._delCrate(id);
        get().fillOrderExtData();
        get()._updateTotalOrderPrice();
    },

    addCardFromCatalog: (crate_to, index_from, index_to, just_mounted) => {
        const dest = crate_to || get().active_crate;
        if (!dest) {
            console.warn("No destination");
            get().noDestinationWarning();
            return {};
        }
        get().showNotification(dest, index_to);
        get()._addCardFromCatalog(dest, index_from, index_to)
        get().fillExtData(dest);
        get().fillWarnings(dest);
        get().setActiveCrate(dest);
        get()._updateTotalOrderPrice();
        if (!just_mounted) {
            get().cardAdded()
        }
    },

    moveCard: (crate_from, index_from, crate_to, index_to) => {
        get()._moveCard(crate_from, index_from, crate_to, index_to);
        get().fillExtData(crate_to);
        get().fillWarnings(crate_to);
        get().setActiveCrate(crate_to);
        get()._updateTotalOrderPrice();
        if (crate_from !== crate_to) {
            get().fillExtData(crate_from);
            get().fillWarnings(crate_from);
        }
    },
    deleteCard: (crate_id, index) => {
        get()._deleteCard(crate_id, index);
        get().fillExtData(crate_id);
        get().fillWarnings(crate_id);
        get()._updateTotalOrderPrice();
        if (crate_id === get().highlighted.crate && index === get().highlighted.card) get().highlightReset()
    },
    clearCrate: (id) => {
        get()._clearCrate(id);
        get().fillWarnings(id);
        get()._updateTotalOrderPrice();
    },

    updateOptions: (crate_id, index, new_options) => {
        get()._updateOptions(crate_id, index, new_options);
        get().fillExtData(crate_id);
        get().fillWarnings(crate_id);
    },

    initExtData: () => {
        get().fillOrderExtData();
        get().crates.forEach((crate, _i) => {
            get().fillExtData(crate.id);
            get().fillExtCrateData(crate.id);
        })
        get()._updateTotalOrderPrice();
    }
}))


export const useShopStore = createWithEqualityFn((...params) => ({
    ...useOptionsNotification(...params),
    ...useCatalog(...params),
    ...useSearch(...params),
    ...useCrateModes(...params),
    ...useCart(...params),
    ...useSubmitForm(...params),
    ...useLayout(...params),
    ...useHighlighted(...params),
    ...useImportJSON(...params),
    ...useCrateOptions(...params),
    ...useOrderOptions(...params),
}))