import useAppDispatch from "@insite/client-framework/Common/Hooks/useAppDispatch";
import useAppSelector from "@insite/client-framework/Common/Hooks/useAppSelector";
import { SafeDictionary } from "@insite/client-framework/Common/Types";
import { StorefrontAccess } from "@insite/client-framework/Services/SettingsService";
import ApplicationState from "@insite/client-framework/Store/ApplicationState";
import { getSettingsCollection } from "@insite/client-framework/Store/Context/ContextSelectors";
import ContextState from "@insite/client-framework/Store/Context/ContextState";
import {
    getCategoryDepthLoaded as actualGetCategoryDepthLoaded,
    getCategoryState,
} from "@insite/client-framework/Store/Data/Categories/CategoriesSelectors";
import { CategoriesState } from "@insite/client-framework/Store/Data/Categories/CategoriesState";
import loadCategory from "@insite/client-framework/Store/Data/Categories/Handlers/LoadCategory";
import { getById } from "@insite/client-framework/Store/Data/DataState";
import LinksState, { HasLinksState } from "@insite/client-framework/Store/Links/LinksState";
import { LinkFieldValue } from "@insite/client-framework/Types/FieldDefinition";
import { useEffect } from "react";
import { createSelector } from "reselect";

export const getHomePageUrl = (state: HasLinksState) => {
    return getPageLinkByPageType(state, "HomePage")?.url ?? "/";
};

const internalGetPageLinkByNodeId = (linkState: LinksState, contextState: ContextState, nodeId: string) => {
    const path = linkState.nodeIdToPageLinkPath[nodeId];
    if (!path) {
        return;
    }

    let result = linkState.pageLinks[path[0]];
    for (let index = 1; index < path.length; index = index + 1) {
        if (result.children) {
            result = result.children[path[index]];
        }
    }

    if (result.type === "OrderUploadPage" || result.type === "QuickOrderPage") {
        if (contextState?.settings != null) {
            const settings = contextState.settings.settingsCollection;
            const productSettings = settings.productSettings;
            if (
                (productSettings.storefrontAccess === StorefrontAccess.SignInRequiredToAddToCart ||
                    productSettings.storefrontAccess === StorefrontAccess.SignInRequiredToAddToCartOrSeePrices) &&
                !(contextState?.session.isAuthenticated ?? false)
            ) {
                return;
            }
        }
    }

    return result;
};

const memoizedGetPageLinkByNodeId = createSelector(
    [
        (state: ApplicationState, nodeId: string) => state.links,
        (state: ApplicationState, nodeId: string) => state.context,
        (state: ApplicationState, nodeId: string) => nodeId,
    ],
    internalGetPageLinkByNodeId,
);

export const getPageLinkByNodeId = (state: HasLinksState, nodeId: string) => {
    return memoizedGetPageLinkByNodeId(state as ApplicationState, nodeId);
};

export const getPageLinkByPageType = (state: HasLinksState, pageType: string) => {
    const nodeId = state.links?.pageTypesToNodeId[pageType];
    if (!nodeId) {
        return;
    }

    return getPageLinkByNodeId(state, nodeId);
};

type LinkItem = { url?: string; title?: string };
const frozenEmptyLink = Object.freeze<LinkItem>({ url: undefined, title: undefined });

const internalGetLink = (
    links: LinksState,
    context: ContextState,
    categories: CategoriesState,
    { type, value }: LinkFieldValue,
) => {
    if (type === "Page") {
        return internalGetPageLinkByNodeId(links, context, value) || frozenEmptyLink;
    }

    if (type === "Category") {
        const category = getById(categories, value).value;

        if (!category) {
            return frozenEmptyLink;
        }
        return {
            url: category.path,
            title: category.shortDescription,
        };
    }

    if (type === "Url") {
        return {
            url: value,
            title: value,
        };
    }

    return frozenEmptyLink;
};

const memoizedGetLink = createSelector(
    [
        (state: ApplicationState, linkField: LinkFieldValue) => state.links,
        (state: ApplicationState, linkField: LinkFieldValue) => state.context,
        (state: ApplicationState, linkField: LinkFieldValue) => state.data.categories,
        (state: ApplicationState, linkField: LinkFieldValue) => linkField,
    ],
    internalGetLink,
);

/**
 * @deprecated Use the `useGetLink` hook instead to ensure category links get loaded.
 */
export const getLink = (state: ApplicationState, linkField: LinkFieldValue): LinkItem => {
    return memoizedGetLink(state, linkField) || frozenEmptyLink;
};

const getLinksSelector = createSelector(
    (state: ApplicationState, linkField: LinkFieldValue[]) => state.links,
    (state: ApplicationState, linkField: LinkFieldValue[]) => state.context,
    (state: ApplicationState, linkField: LinkFieldValue[]) => state.data.categories,
    (state: ApplicationState, linkField: LinkFieldValue[]) => linkField,
    (links, context, categories, linkFields) => linkFields.map(o => internalGetLink(links, context, categories, o)),
);

const getCategoryStatesSelector = createSelector(
    [
        (state: ApplicationState) => state.data?.categories,
        (state: ApplicationState, linkFields: LinkFieldValue[]) => linkFields,
    ],
    (categoriesState: CategoriesState, linkFields) =>
        linkFields.map(linkField => {
            if (linkField.type !== "Category") {
                return;
            }

            return getById(categoriesState, linkField.value);
        }),
);

export const useGetLinks = <T>(list: T[], linkSelector: (item: T) => LinkFieldValue) => {
    const linkFields = list.map(linkSelector);

    const links = useAppSelector(state => getLinksSelector(state, linkFields));

    const categoryStates = useAppSelector(state => getCategoryStatesSelector(state, linkFields));

    const missingCategoryIds: string[] = [];

    for (let x = 0; x < links.length; x++) {
        const linkField = linkFields[x];
        const link = links[x];
        const categoryState = categoryStates[x];

        if (
            linkField.type === "Category" &&
            linkField.value &&
            !link.url &&
            !categoryState?.isLoading &&
            !categoryState?.errorStatusCode
        ) {
            missingCategoryIds.push(linkField.value);
        }
    }

    const dispatch = useAppDispatch();
    useEffect(() => {
        if (missingCategoryIds.length > 0) {
            missingCategoryIds.forEach(categoryId => {
                dispatch(loadCategory({ id: categoryId }));
            });
        }
    }, [missingCategoryIds]);

    return { links, hasMissingCategories: missingCategoryIds.length > 0 };
};

export const useGetLink = (linkField: LinkFieldValue) => {
    linkField = linkField || { type: "Page", value: "" };
    return useGetLinks([linkField], o => o).links[0];
};

export interface LinkModel {
    fields: {
        openInNewWindow: boolean;
        overriddenTitle: string;
        destination: LinkFieldValue;
    };
}

/**
 * @deprecated Use the `useGetLinks` hook instead to ensure category links get loaded. Mapping can be done calling component
 */
export function mapLinks<L extends LinkModel, R = {}>(
    state: ApplicationState,
    links?: L[],
    optionalReturnFunction?: (widgetLink: L, stateLink?: ReturnType<typeof getLink>) => R,
) {
    if (!links || links.length < 1) {
        return [];
    }
    return links.map(widgetLink => {
        const stateLink = getLink(state, widgetLink.fields.destination);
        const optionalReturnValues =
            typeof optionalReturnFunction === "function" ? optionalReturnFunction(widgetLink, stateLink) : {};
        return {
            url: stateLink?.url,
            title: stateLink?.title,
            ...optionalReturnValues,
        } as ReturnType<typeof getLink> & R;
    });
}

/**
 * @deprecated Use getCategoryState instead
 */
export function getCategoryLink(state: HasLinksState, categoryId: string) {
    return getCategoryState(state as ApplicationState, categoryId).value;
}

/**
 * @deprecated Use @insite/client-framework/Store/Data/Categories/CategoriesSelectors/getCategoryDepthLoaded instead
 */
export function getCategoryDepthLoaded(state: HasLinksState, categoryId: string) {
    return actualGetCategoryDepthLoaded(state as ApplicationState, categoryId);
}
