import getJsonLdScriptId from "@insite/client-framework/Common/Utilities/getJsonLdScriptId";
import parseQueryString from "@insite/client-framework/Common/Utilities/parseQueryString";
import prepareBreadcrumbStructuredPageData from "@insite/client-framework/Common/Utilities/prepareBreadcrumbStructuredPageData";
import setPageMetadata from "@insite/client-framework/Common/Utilities/setPageMetadata";
import { trackPageChange } from "@insite/client-framework/Common/Utilities/tracking";
import { CategoryContext } from "@insite/client-framework/Components/CategoryContext";
import { HasDeferredApiContext, withCallDeferredApi } from "@insite/client-framework/Components/DeferredApiContext";
import { HasShellContext } from "@insite/client-framework/Components/IsInShell";
import Zone from "@insite/client-framework/Components/Zone";
import { redirectTo, setStatusCode } from "@insite/client-framework/ServerSideRendering";
import { getContentByVersionPath } from "@insite/client-framework/Services/ContentService";
import ApplicationState from "@insite/client-framework/Store/ApplicationState";
import setBreadcrumbs from "@insite/client-framework/Store/Components/Breadcrumbs/Handlers/SetBreadcrumbs";
import {
    getSearchDataModeActive,
    getSelectedCategoryPath,
    getSettingsCollection,
} from "@insite/client-framework/Store/Context/ContextSelectors";
import { getCatalogPageStateByPath } from "@insite/client-framework/Store/Data/CatalogPages/CatalogPagesSelectors";
import { getCategoryState } from "@insite/client-framework/Store/Data/Categories/CategoriesSelectors";
import {
    getAlternateLanguageUrls,
    getCanonicalUrlByPath,
    getCurrentPage,
    getLocation,
} from "@insite/client-framework/Store/Data/Pages/PageSelectors";
import { getProductsDataView } from "@insite/client-framework/Store/Data/Products/ProductsSelectors";
import clearAllProductFilters from "@insite/client-framework/Store/Pages/ProductList/Handlers/ClearAllProductFilters";
import clearProducts from "@insite/client-framework/Store/Pages/ProductList/Handlers/ClearProducts";
import displayProducts from "@insite/client-framework/Store/Pages/ProductList/Handlers/DisplayProducts";
import loadProductsPricingAndInventory from "@insite/client-framework/Store/Pages/ProductList/Handlers/LoadProductsPricingAndInventory";
import prepareProductRecs from "@insite/client-framework/Store/Pages/ProductList/Handlers/PrepareProductRecs";
import sendOdpEvents from "@insite/client-framework/Store/Pages/ProductList/Handlers/SendOdpEvents";
import translate from "@insite/client-framework/Translate";
import { HideFooter, HideHeader, OpenGraphImage, OpenGraphTitle } from "@insite/client-framework/Types/FieldDefinition";
import PageModule from "@insite/client-framework/Types/PageModule";
import PageProps from "@insite/client-framework/Types/PageProps";
import Modals from "@insite/content-library/Components/Modals";
import ProductCompareFlyOut from "@insite/content-library/Components/ProductCompareFlyOut";
import Page from "@insite/mobius/Page";
import { HasHistory, withHistory } from "@insite/mobius/utilities/HistoryContext";
import { getPrevCurrentNextQueryString } from "@insite/mobius/utilities/linkElementsManipulation";
import * as React from "react";
import { connect, ResolveThunks } from "react-redux";

const mapStateToProps = (state: ApplicationState) => {
    const location = getLocation(state);
    const pathnameLowerCase = location.pathname.toLowerCase();
    const path =
        getSelectedCategoryPath(state) ||
        (location.pathname === getContentByVersionPath
            ? parseQueryString<{ categoryPath: string }>(location.search).categoryPath
            : "") ||
        (pathnameLowerCase.startsWith("/content/") || pathnameLowerCase.startsWith("/.spire/")
            ? ""
            : location.pathname);
    const {
        pages: {
            productList: {
                isLoading,
                productFilters,
                parameter,
                isSearchPage,
                filterQuery,
                productInfosByProductId,
                isCanceled,
            },
        },
    } = state;
    const catalogPage = getCatalogPageStateByPath(state, path).value;
    const productsDataView = getProductsDataView(state, parameter);
    const page = getCurrentPage(state);
    return {
        isLoading,
        stockedItemsOnly: productFilters.stockedItemsOnly,
        previouslyPurchasedProducts: productFilters.previouslyPurchasedProducts,
        query: productFilters.query,
        productsDataView,
        firstProductDetailPath: productsDataView.value
            ? productInfosByProductId[productsDataView.value[0]?.id]?.productDetailPath
            : undefined,
        filterQuery,
        isSearchPage,
        search: location.search,
        path,
        breadcrumbLinks: state.components.breadcrumbs.links,
        productListCatalogPage: getCatalogPageStateByPath(state, path).value,
        websiteName: state.context.website.name,
        pages: state.pages,
        location: getLocation(state),
        category: getCategoryState(state, catalogPage?.categoryIdWithBrandId ?? catalogPage?.categoryId).value,
        websiteSettings: getSettingsCollection(state).websiteSettings,
        searchDataModeActive: getSearchDataModeActive(state),
        searchPath: getSettingsCollection(state).searchSettings.searchPath,
        alternateLanguageUrls: getAlternateLanguageUrls(state, page.id),
        page,
        deferredApis: state.context.deferredApiCalls,
        productInfosByProductId,
        parameter,
        unfilteredApiParameter: state.pages.productList.unfilteredApiParameter,
        canonicalUrl: getCanonicalUrlByPath(state, path),
        enableServerSideRendering: getSettingsCollection(state).websiteSettings.enableServerSideRendering,
        isCanceled,
    };
};

const mapDispatchToProps = {
    clearAllProductFilters,
    displayProducts,
    setBreadcrumbs,
    clearProducts,
    loadProductsPricingAndInventory,
    sendOdpEvents,
    prepareProductRecs,
};

export const ProductListPageDataContext = React.createContext<{ ref?: React.RefObject<HTMLSpanElement | null> }>({
    ref: undefined,
});

type Props = HasHistory &
    ReturnType<typeof mapStateToProps> &
    ResolveThunks<typeof mapDispatchToProps> &
    HasShellContext &
    PageProps &
    HasDeferredApiContext;

class ProductListPage extends React.Component<Props, { sentEventFor?: string }> {
    constructor(props: Props) {
        super(props);
        this.state = {};
    }

    afterFilters = React.createRef<HTMLSpanElement | null>();

    loadProducts() {
        const { displayProducts, path, search } = this.props;

        displayProducts({
            path,
            queryString: search,
            isSearch: this.isSearchPath(),
            relevancy: true,
        });
    }

    isSearchPath() {
        return !!this.props.path.toLowerCase().startsWith(`/${this.props.searchPath.toLowerCase()}`);
    }

    UNSAFE_componentWillMount(): void {
        if (!IS_SERVER_SIDE) {
            // cleanup state for future redirect captures
            window?.localStorage?.removeItem("searchTermRedirectUrl");
        }

        const { productsDataView, breadcrumbLinks } = this.props;
        if (
            ((!productsDataView.value && !productsDataView.isLoading) || this.props.isCanceled) &&
            !(IS_SERVER_SIDE && this.props.deferredApis.catalog > 0)
        ) {
            this.loadProducts();
        } else if (!IS_SERVER_SIDE && productsDataView.value) {
            if (this.checkSearchTermRedirectUrl()) {
                return;
            }
        } else if (this.props.isSearchPage) {
            this.redirectToProductDetailIfExactMatch();
        }

        this.props.callDeferredApis({
            pricingHandler: () => {
                this.props.loadProductsPricingAndInventory({
                    products: this.props.productsDataView.value,
                    productInfosByProductId: this.props.productInfosByProductId,
                });
            },
            inventoryHandler: () => {
                this.props.loadProductsPricingAndInventory({
                    products: this.props.productsDataView.value,
                    productInfosByProductId: this.props.productInfosByProductId,
                });
            },
        });

        if (this.props.productListCatalogPage) {
            this.setMetadata();
            if (
                breadcrumbLinks
                    ?.slice(1)
                    ?.map(o => o.children?.toString())
                    .join(",") !==
                    this.props.productListCatalogPage.breadCrumbs
                        ?.slice(1)
                        ?.map(o => o.text)
                        .join(",") ||
                (breadcrumbLinks?.length && breadcrumbLinks[0].children !== translate("Home_Breadcrumb"))
            ) {
                this.setProductListBreadcrumbs();
            }
        } else if (this.props.isSearchPage) {
            this.setSearchPageMetadata(translate("Search Results"));
            if (!this.props.breadcrumbLinks) {
                this.setSearchBreadcrumbs();
            }
        }

        this.sendOdpEventsIfNeeded();
    }

    componentDidMount(): void {
        if (this.props.enableServerSideRendering) {
            this.props.prepareProductRecs({
                isSearch: this.isSearchPath(),
                catalogPage: this.props.productListCatalogPage,
                products: this.props.productsDataView.value,
            });
        }
    }

    componentWillUnmount(): void {
        const { productsDataView } = this.props;
        if (!productsDataView.isLoading) {
            this.props.clearProducts();
            this.props.setBreadcrumbs({ links: undefined });
        }
    }

    componentDidUpdate(prevProps: Props): void {
        const {
            isLoading,
            filterQuery,
            location: { pathname, search },
            productListCatalogPage,
            searchDataModeActive,
        } = this.props;

        if (productListCatalogPage) {
            this.setMetadata();
            trackPageChange();
            if (
                productListCatalogPage.categoryId !== prevProps.productListCatalogPage?.categoryId ||
                !this.props.breadcrumbLinks
            ) {
                this.setProductListBreadcrumbs();
            }
        } else if (this.props.isSearchPage && search && search.length > 0) {
            // For pages that do not have a productListCatalogPage, mainly the Search Page
            this.setSearchPageMetadata(translate("Search Results"));
            trackPageChange();
            if (this.props.query !== prevProps.query || !this.props.breadcrumbLinks) {
                this.setSearchBreadcrumbs();
            }
        }

        if (this.props.query !== prevProps.query) {
            window?.localStorage?.removeItem("searchTermRedirectUrl");
        }

        // handle the query string change requests initiated by the filtering widget setQueryFilter calls
        // Make sure we are fully loaded, since a loading product list will change the filteQuery to match the current pathname/search
        // This can trigger a loop with the pathname/search updating and the filterQuery not updated till the productList is loaded
        if (!isLoading && filterQuery !== prevProps.filterQuery) {
            const currentUrl = `${pathname}${search}`;
            const newUrl = filterQuery ? `${pathname}?${filterQuery}` : pathname;
            if (newUrl !== currentUrl) {
                this.props.history.push(newUrl);
                return;
            }
        }

        if (!isLoading && this.props.productsDataView.value && pathname === prevProps.location.pathname) {
            if (this.checkSearchTermRedirectUrl()) {
                return;
            }

            this.redirectToProductDetailIfExactMatch();
        }

        if (
            !isLoading &&
            (search !== prevProps.search || this.props.path !== prevProps.path) &&
            search?.indexOf("criteria=") === -1
        ) {
            if (this.props.path !== prevProps.path) {
                this.props.clearAllProductFilters({
                    removeAll: true,
                });
            }
            this.loadProducts();
        }

        if (searchDataModeActive !== prevProps.searchDataModeActive) {
            // refresh the search results when search data mode has been toggled in the cms shell
            this.loadProducts();
        }

        this.sendOdpEventsIfNeeded();
    }

    sendOdpEventsIfNeeded = () => {
        if (IS_SERVER_SIDE) {
            return;
        }

        const { parameter, sendOdpEvents, productListCatalogPage, productsDataView, unfilteredApiParameter } =
            this.props;

        if (!unfilteredApiParameter) {
            return;
        }

        const nextParameter = JSON.stringify(parameter);
        if (nextParameter !== this.state.sentEventFor && productsDataView?.value) {
            this.setState({
                sentEventFor: nextParameter,
            });

            let lastParameter = this.state.sentEventFor ? JSON.parse(this.state.sentEventFor) : undefined;
            sendOdpEvents({
                catalogPage: productListCatalogPage,
                lastParameter,
            });
        }
    };

    checkSearchTermRedirectUrl() {
        if (!window || !this.props.productsDataView.value) {
            return false;
        }

        let searchTermRedirectUrl = this.props.productsDataView.searchTermRedirectUrl;
        if (!searchTermRedirectUrl) {
            return false;
        }

        const startsWithProtocol = searchTermRedirectUrl.lastIndexOf("http", 0) === 0;
        if (!startsWithProtocol && searchTermRedirectUrl[0] !== "/") {
            searchTermRedirectUrl = `/${searchTermRedirectUrl}`;
        }

        const locationPath = startsWithProtocol
            ? window.location.toString()
            : `${window.location.pathname}${window.location.search}`;

        if (locationPath === searchTermRedirectUrl) {
            window.localStorage.setItem("searchTermRedirectUrl", searchTermRedirectUrl);
            return false;
        }

        if (window.localStorage.getItem("searchTermRedirectUrl") === searchTermRedirectUrl) {
            return false;
        }

        if (startsWithProtocol) {
            window.location.replace(searchTermRedirectUrl);
        } else {
            this.props.history.replace(searchTermRedirectUrl);
        }

        window.localStorage.setItem("searchTermRedirectUrl", searchTermRedirectUrl);

        return true;
    }

    redirectToProductDetailIfExactMatch() {
        if (!this.props.productsDataView.value) {
            return;
        }

        const {
            location: { search },
            firstProductDetailPath,
        } = this.props;

        const { exactMatch, value: products } = this.props.productsDataView;
        if (exactMatch && products.length) {
            let productDetailUrl = firstProductDetailPath ?? products[0].canonicalUrl;
            const parsedQuery = parseQueryString<{ criteria: string; query: string }>(search);
            const query = parsedQuery.criteria || parsedQuery.query;
            if (!query) {
                return;
            }
            if (productDetailUrl.indexOf("?") !== -1) {
                productDetailUrl += `&criteria=${encodeURIComponent(query)}`;
            } else {
                productDetailUrl += `?criteria=${encodeURIComponent(query)}`;
            }

            if (IS_SERVER_SIDE) {
                setStatusCode(302);
                redirectTo(productDetailUrl);
            } else {
                this.props.history.replace(productDetailUrl);
            }
        }
    }

    getQueryStrings() {
        if (this.props.productsDataView.value && this.props.productsDataView.pagination) {
            const { prevPageUri, nextPageUri, currentPage } = this.props.productsDataView.pagination;
            const { prevQueryString, currentQueryString, nextQueryString } = getPrevCurrentNextQueryString(
                prevPageUri,
                nextPageUri,
                currentPage,
                this.props.filterQuery ?? "",
            );
            return { prevQueryString, currentQueryString, nextQueryString };
        }

        const { prevQueryString, currentQueryString, nextQueryString } = getPrevCurrentNextQueryString("", "", 1, "");
        return { prevQueryString, currentQueryString, nextQueryString };
    }

    setMetadata() {
        const { productListCatalogPage, websiteName, location, websiteSettings } = this.props;
        if (!productListCatalogPage) {
            return;
        }

        const {
            metaDescription,
            metaKeywords,
            openGraphImage,
            openGraphTitle,
            openGraphUrl,
            title,
            primaryImagePath,
            canonicalPath,
            alternateLanguageUrls,
        } = productListCatalogPage;

        const { prevQueryString, currentQueryString, nextQueryString } = this.getQueryStrings();

        setPageMetadata(
            {
                metaDescription,
                metaKeywords,
                openGraphImage: openGraphImage || primaryImagePath,
                openGraphTitle,
                openGraphUrl,
                title,
                currentPath: location.pathname,
                canonicalPath,
                alternateLanguageUrls: alternateLanguageUrls ?? undefined,
                websiteName,
                currentQueryString,
                prevQueryString,
                nextQueryString,
                structuredPageData: websiteSettings.enableBreadcrumbStructuredData
                    ? prepareBreadcrumbStructuredPageData(this.props.breadcrumbLinks)
                    : undefined,
                jsonLdScriptId: getJsonLdScriptId(this.props.page.type),
            },
            websiteSettings,
        );
    }

    setSearchPageMetadata(title: string) {
        const {
            websiteName,
            location,
            websiteSettings,
            fields,
            productsDataView,
            alternateLanguageUrls,
            canonicalUrl,
        } = this.props;
        const firstProduct = productsDataView.value && productsDataView.value[0];
        const { prevQueryString, currentQueryString, nextQueryString } = this.getQueryStrings();

        setPageMetadata(
            {
                title,
                currentPath: location.pathname,
                websiteName,
                openGraphImage:
                    fields.openGraphImage ||
                    firstProduct?.largeImagePath ||
                    firstProduct?.mediumImagePath ||
                    firstProduct?.smallImagePath,
                openGraphTitle: fields.openGraphTitle,
                openGraphUrl: location.pathname + location.search,
                alternateLanguageUrls,
                currentQueryString,
                prevQueryString,
                nextQueryString,
                structuredPageData: websiteSettings.enableBreadcrumbStructuredData
                    ? prepareBreadcrumbStructuredPageData(this.props.breadcrumbLinks)
                    : undefined,
                jsonLdScriptId: getJsonLdScriptId(this.props.page.type),
                canonicalPath: canonicalUrl,
            },
            websiteSettings,
        );
    }

    setProductListBreadcrumbs() {
        this.props.setBreadcrumbs({
            links: this.props.productListCatalogPage!.breadCrumbs!.map(o => ({ children: o.text, href: o.url })),
        });
    }

    setSearchBreadcrumbs() {
        this.props.setBreadcrumbs({
            links: [
                { children: translate("Home_Breadcrumb"), href: "/" },
                { children: `${translate("Search Results")}: ${this.props.query}` },
            ],
        });
    }

    render() {
        const windowIfDefined = typeof window === "undefined" ? null : window;
        if (
            this.props.productsDataView.value?.length &&
            (this.props.productsDataView.exactMatch ||
                (this.props.productsDataView.searchTermRedirectUrl &&
                    !windowIfDefined?.localStorage?.getItem("searchTermRedirectUrl")))
        ) {
            // prevent a flash of data when PLP immediately redirects to PDP
            return null;
        }

        return (
            <Page>
                <CategoryContext.Provider value={this.props.category}>
                    <ProductListPageDataContext.Provider value={{ ref: this.afterFilters }}>
                        <Zone contentId={this.props.id} zoneName="Content" />
                        <Modals />
                        <ProductCompareFlyOut />
                    </ProductListPageDataContext.Provider>
                </CategoryContext.Provider>
            </Page>
        );
    }
}

const pageModule: PageModule = {
    component: connect(mapStateToProps, mapDispatchToProps)(withCallDeferredApi(withHistory(ProductListPage))),
    definition: {
        hasEditableUrlSegment: false,
        hasEditableTitle: false,
        supportsCategorySelection: true,
        pageType: "System",
        fieldDefinitions: [HideHeader, HideFooter, OpenGraphTitle, OpenGraphImage],
    },
};

export default pageModule;

/**
 * @deprecated Use string literal "ProductListPage" instead of this constant.
 */
export const ProductListPageContext = "ProductListPage";
