import { createFromProduct, ProductInfo } from "@insite/client-framework/Common/ProductInfo";
import throwErrorIfTesting from "@insite/client-framework/Common/ThrowErrorIfTesting";
import { SafeDictionary } from "@insite/client-framework/Common/Types";
import waitFor from "@insite/client-framework/Common/Utilities/waitFor";
import { getIsWebCrawler } from "@insite/client-framework/Common/WebCrawler";
import {
    createHandlerChainRunner,
    executeAwaitableHandlerChain,
    Handler,
} from "@insite/client-framework/HandlerCreator";
import { sendProductDetailViewEvent } from "@insite/client-framework/OdpEvents/ProductDetailViewEvent";
import { getProductByPath } from "@insite/client-framework/Services/ProductServiceV2";
import { recentlyViewedGetProductCollectionParameter } from "@insite/client-framework/Store/Components/ProductCarousel/Handlers/LoadCarouselProducts";
import prepareProduct from "@insite/client-framework/Store/Components/ProductRecommendations/Handlers/PrepareProduct";
import { getSettingsCollection, getShouldLoadCatalog } from "@insite/client-framework/Store/Context/ContextSelectors";
import loadProductByPath from "@insite/client-framework/Store/Data/Products/Handlers/LoadProductByPath";
import loadVariantChildren from "@insite/client-framework/Store/Data/Products/Handlers/LoadVariantChildren";
import {
    getProductStateByPath,
    getVariantChildrenDataView,
} from "@insite/client-framework/Store/Data/Products/ProductsSelectors";
import loadProductPricingAndInventory from "@insite/client-framework/Store/Pages/ProductDetails/Handlers/LoadProductPricingAndInventory";
import updateVariantSelection from "@insite/client-framework/Store/Pages/ProductDetails/Handlers/UpdateVariantSelection";
import { ProductModel } from "@insite/client-framework/Types/ApiModels";

type Parameter = {
    path: string;
    styledOption?: string;
};
type Props = {
    product?: ProductModel;
    productInfosById?: SafeDictionary<ProductInfo>;
    variantChildren?: ProductModel[];
    variantChildrenProductInfos?: ProductInfo[];
    variantSelection?: SafeDictionary<string>;
    pricingLoaded?: true;
    inventoryLoaded?: true;
};

type HandlerType = Handler<Parameter, Props>;

export const DeferCallIfNecessary: HandlerType = props => {
    if (!getShouldLoadCatalog(props.getState())) {
        props.dispatch({
            type: "Context/AddDeferredApiCall",
            deferredCallType: "catalog",
        });
        return false;
    }
};

export const DispatchBeginLoadProduct: HandlerType = props => {
    throwErrorIfTesting();

    props.dispatch({
        type: "Pages/ProductDetails/BeginLoadProduct",
        path: props.parameter.path,
        styledOption: props.parameter.styledOption,
    });
};

export const LoadExistingProduct: HandlerType = props => {
    if (!props.parameter.path) {
        props.dispatch({
            type: "Pages/ProductDetails/CancelLoadProduct",
        });
        return false;
    }

    props.product = getProductStateByPath(props.getState(), props.parameter.path).value;
};

export const LoadProductIfNeeded: HandlerType = async props => {
    // if variantTypeId is not null this has variantTraits, and we need those loaded fully for this handler chain to work correctly
    // if product is discontinued then it may have replacement product, and we wait for it to be loaded
    if (!props.product || props.product.variantTypeId || props.product.isDiscontinued) {
        props.product = await executeAwaitableHandlerChain(
            loadProductByPath,
            { ...props.parameter, addToRecentlyViewed: true },
            props,
        );
    } else if (
        !props.product.detail ||
        !props.product.content ||
        !props.product.images ||
        !props.product.documents ||
        !props.product.specifications ||
        !props.product.attributeTypes ||
        !props.product.variantTraits
    ) {
        // we don't want to wait for this if we already partially have the product, because then we can't show the partially loaded product page
        props.dispatch(
            loadProductByPath({
                ...props.parameter,
                addToRecentlyViewed: true,
                onComplete: productProps => {
                    const configuration = productProps?.apiResult?.detail?.configuration;
                    if (configuration) {
                        props.dispatch({
                            type: "Pages/ProductDetails/InitConfigurationSelection",
                            configuration,
                        });
                    }

                    props.dispatch({
                        type: "Data/Products/ReplaceDataView",
                        parameter: recentlyViewedGetProductCollectionParameter,
                        dataView: {},
                    });
                },
            }),
        );
    } else {
        getProductByPath({ ...props.parameter, addToRecentlyViewed: true, expand: ["attributes"] }).then(result => {
            props.dispatch({
                type: "Data/Products/CompleteLoadProduct",
                model: result,
            });
        });
    }
};

export const RequestVariantChildrenFromApi: HandlerType = async props => {
    if (!props.product?.variantTraits || props.product.variantTraits.length === 0) {
        return;
    }

    props.variantChildren = getVariantChildrenDataView(props.getState(), props.product.id).value;
    if (!props.variantChildren) {
        props.variantChildren = await executeAwaitableHandlerChain(
            loadVariantChildren,
            { productId: props.product.id },
            props,
        );
    }
};

export const SetupVariantSelection: HandlerType = props => {
    props.variantSelection = getVariantSelection(props.product, props.variantChildren, props.parameter.styledOption);
};

function getVariantSelection(product?: ProductModel, variantChildren?: ProductModel[], styledOption?: string) {
    const variantSelection: SafeDictionary<string> = {};
    if (!product?.isVariantParent || !variantChildren) {
        return variantSelection;
    }

    if (styledOption || product.defaultChildProductId) {
        const variantChild = styledOption
            ? variantChildren.find(o => o.productNumber.toLowerCase() === styledOption.toLowerCase())
            : variantChildren.find(o => o.id === product.defaultChildProductId);

        variantChild?.childTraitValues?.forEach(o => {
            variantSelection[o.styleTraitId] = o.id;
        });

        if (Object.keys(variantSelection).length > 0) {
            return variantSelection;
        }
    }

    product.variantTraits?.forEach(variantTrait => {
        const defaultVariantValue = variantTrait.traitValues?.find(traitValue => traitValue.isDefault);
        variantSelection[variantTrait.id] = defaultVariantValue?.id ?? "";
    });

    return variantSelection;
}

export const SetUnitOfMeasureAndQtyOrdered: HandlerType = props => {
    if (!props.product) {
        return;
    }

    const productInfos = [createFromProduct(props.product)];
    if (props.variantChildren) {
        productInfos.push(...props.variantChildren.map(createFromProduct));
    }

    props.productInfosById = productInfos.reduce((map, productInfo) => {
        map[productInfo.productId] = productInfo;
        return map;
    }, {} as SafeDictionary<ProductInfo>);
};

export const DispatchCompleteLoadProduct: HandlerType = props => {
    if (!props.product) {
        return;
    }

    props.dispatch({
        type: "Pages/ProductDetails/CompleteLoadProduct",
        productInfosById: props.productInfosById ?? {},
        variantSelection: props.variantSelection ?? {},
        selectedProductId: Object.keys(props.variantSelection as any).length ? undefined : props.product.id,
        configuration: props.product.detail?.configuration,
    });
};

export const TrackInOdp: HandlerType = props => {
    if (!props.product) {
        return;
    }

    sendProductDetailViewEvent(props.product.id, props.getState());
};

export const LoadRealTimePricingAndInventory: HandlerType = props => {
    if (!props.product) {
        return;
    }

    props.dispatch(
        loadProductPricingAndInventory({
            product: props.product,
            variantChildren: props.variantChildren,
            productInfosById: props.productInfosById,
            styledOption: props.parameter.styledOption,
            waitForDataLoaded:
                !!props.product.variantTypeId &&
                (!getIsWebCrawler() ||
                    getSettingsCollection(props.getState()).websiteSettings.includeRealTimeInventoryAndPricingForSeo),
            onComplete: () => {
                props.pricingLoaded = props.inventoryLoaded = true;
            },
        }),
    );
};

/** @deprecated LoadRealTimePrices is no longer used in this handler and will be removed in a future release. @see LoadRealTimePricingAndInventory instead. */
export const LoadRealTimePrices: HandlerType = props => {};

/** @deprecated LoadRealTimeInventory is no longer used in this handler and will be removed in a future release. @see LoadRealTimePricingAndInventory instead. */
export const LoadRealTimeInventory: HandlerType = props => {};

export const InitVariantProduct: HandlerType = async props => {
    if (!props.product || !props.product.variantTypeId) {
        return;
    }

    if (
        !getIsWebCrawler() ||
        getSettingsCollection(props.getState()).websiteSettings.includeRealTimeInventoryAndPricingForSeo
    ) {
        const checkData = () => {
            return !!props.pricingLoaded && !!props.inventoryLoaded;
        };
        await waitFor(checkData);
    }

    props.dispatch(updateVariantSelection({ productId: props.product.id }));
};

export const PrepareProduct: HandlerType = ({ dispatch, product }) => {
    if (IS_SERVER_SIDE) {
        return;
    }

    if (product) {
        dispatch(prepareProduct({ product }));
    }
};

export const chain = [
    DeferCallIfNecessary,
    DispatchBeginLoadProduct,
    LoadExistingProduct,
    LoadProductIfNeeded,
    RequestVariantChildrenFromApi,
    SetupVariantSelection,
    SetUnitOfMeasureAndQtyOrdered,
    DispatchCompleteLoadProduct,
    TrackInOdp,
    LoadRealTimePricingAndInventory,
    InitVariantProduct,
    PrepareProduct,
];

const displayProduct = createHandlerChainRunner(chain, "DisplayProduct");
export default displayProduct;
