import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { connect } from "react-redux";

import { EmptyState, ErrorToast, FilterTagsBar, MainWrapper, SearchTagsBar, TableList, Toast } from "@ax/components";
import { appActions } from "@ax/containers/App";
import { structuredDataActions } from "@ax/containers/StructuredData";
import { sortBy } from "@ax/helpers";
import { useBulkSelection, useModals, usePermissions, useToast } from "@ax/hooks";
import { LOCALE, toTitleCase } from "@ax/locales";
import type {
	ICategoryGroup,
	IDataPack,
	IGetStructuredDataParams,
	ILanguage,
	IOrderCategoryParams,
	IQueryValue,
	IRootState,
	IStructuredData,
	IStructuredDataCategory,
} from "@ax/types";

import {
	closestCenter,
	DndContext,
	type DragEndEvent,
	type DragMoveEvent,
	type DragOverEvent,
	type DragStartEvent,
	KeyboardSensor,
	MeasuringStrategy,
	PointerSensor,
	type UniqueIdentifier,
	useSensor,
	useSensors,
} from "@dnd-kit/core";
import {
	arrayMove,
	SortableContext,
	sortableKeyboardCoordinates,
	verticalListSortingStrategy,
} from "@dnd-kit/sortable";

import { DeleteGroupModal, DeleteModal } from "./atoms";
import BulkHeader from "./BulkHeader";
import CategoryItem from "./CategoryItem";
import CategoryNav from "./CategoryNav";
import CategoryPanel from "./CategoryPanel";
import { buildTree, type FlattenedItem, flattenTree, getProjection, removeChildrenOf, setProperty } from "./helpers";
import { useFilterQuery } from "./hooks";
import { filterCategoriesAndGroups, getAllLangCategoriesIds } from "./utils";

import * as S from "./style";

const CategoriesList = (props: IProps): JSX.Element => {
	const {
		currentSiteID,
		currentStructuredData,
		currentDataContent,
		getStructuredDataContents,
		lang,
		siteLanguages,
		globalLangs,
		setLanguage,
		totalItems,
		categories,
		setSelectedCategory,
		activatedDataPacks,
		deleteDataContent,
		setHistoryPush,
		deleteCategoryGroup,
		orderCategory,
	} = props;

	const [isScrolling, setIsScrolling] = useState(false);
	const [isBulkLoading, setIsBulkLoading] = useState(false);
	const [deleteGroupCategories, setDeleteGroupCategories] = useState(false);
	const [searchQuery, setSearchQuery] = useState<string>("");
	const [items, setItems] = useState<TreeItem[]>(currentDataContent);
	const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
	const [overId, setOverId] = useState<UniqueIdentifier | null>(null);
	const [offsetLeft, setOffsetLeft] = useState(0);
	const { isVisible, toggleToast, setIsVisible, state: toastState } = useToast();
	const tableRef = useRef<HTMLDivElement>(null);
	const { setFiltersSelection, resetFilterQuery, filterValues, filterQuery } = useFilterQuery();
	const { isOpen, toggleModal } = useModals(["panel", "delete", "group"]);

	const isAllowedTo = usePermissions({
		createSiteCategory: "categories.createSiteTaxonomies",
		createGlobalCategory: "global.globalData.createTaxonomies",
		deleteSiteCategory: "categories.deleteSiteTaxonomies",
		deleteGlobalCategory: "global.globalData.deleteTaxonomies",
	});

	const allowedToDeleteTaxonomy = currentSiteID ? isAllowedTo.deleteSiteCategory : isAllowedTo.deleteGlobalCategory;
	const allowedToCreateTaxonomy = currentSiteID ? isAllowedTo.createSiteCategory : isAllowedTo.createGlobalCategory;

	const scope = currentSiteID ? "site" : "global";
	const currentCategories = categories[scope].sort(sortBy("title", false));
	const isEmpty = currentDataContent && currentDataContent.length === 0;
	const hasCategories = currentCategories.length > 0;
	const categoryName = currentStructuredData?.title || "";
	const availableLanguages = currentSiteID ? siteLanguages : globalLangs;
	const isTranslatable = currentStructuredData ? currentStructuredData.translate : true;
	const indentationWidth = 35;

	const flattenedItems = useMemo(() => {
		const flattenedTree = flattenTree(items);
		const collapsedItems = flattenedTree.reduce<number[]>(
			// biome-ignore lint/performance/noAccumulatingSpread: TODO: fix this
			(acc, { children, collapsed, id }) => (collapsed && children.length ? [...acc, id] : acc),
			[],
		);

		return removeChildrenOf(flattenedTree, activeId != null ? [activeId, ...collapsedItems] : collapsedItems);
	}, [activeId, items]);

	useEffect(() => {
		setItems(currentDataContent);
	}, [currentDataContent]);

	const measuring = {
		droppable: {
			strategy: MeasuringStrategy.Always,
		},
	};

	const sensors = useSensors(
		useSensor(PointerSensor, {
			activationConstraint: {
				distance: 5,
			},
		}),
		useSensor(KeyboardSensor, {
			coordinateGetter: sortableKeyboardCoordinates,
		}),
	);

	const projected =
		activeId && overId ? getProjection(flattenedItems, activeId, overId, offsetLeft, indentationWidth) : null;

	const catIds = useMemo(() => flattenedItems.map(({ id }) => id), [flattenedItems]);

	const {
		resetBulkSelection,
		selectedItems,
		isSelected,
		areItemsSelected,
		checkState,
		addToBulkSelection,
		selectAllItems,
		setHoverCheck,
	} = useBulkSelection(catIds);

	const getParams = useCallback((): IGetStructuredDataParams => {
		return {
			page: 1,
			itemsPerPage: 50,
			pagination: false,
			deleted: false,
			include_draft: false,
			groupingCategories: true,
			query: searchQuery,
			filterQuery,
		};
	}, [searchQuery, filterQuery]);

	const getContents = useCallback(
		async (id: string) => {
			const params = getParams();
			params.dataID = id;
			await getStructuredDataContents(params, currentSiteID);
		},
		[getStructuredDataContents, currentSiteID, getParams],
	);

	// biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
	useLayoutEffect(() => {
		if (currentCategories.length) {
			setSelectedCategory(currentCategories[0].id, scope);
		}
	}, []);

	// biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
	useEffect(() => {
		currentStructuredData?.taxonomy && getContents(currentStructuredData.id);
	}, [lang, currentStructuredData, searchQuery, filterValues]);

	const handleClick = (dataID: string) => {
		setSelectedCategory(dataID, scope);
	};

	const rightButtonProps = allowedToCreateTaxonomy
		? {
				label: "New",
				action: () => toggleModal("panel"),
			}
		: undefined;

	const bulkDelete = async () => {
		setIsBulkLoading(true);
		const { groups, categories } = filterCategoriesAndGroups(currentDataContent);
		const idsCatsToBeDeleted = getAllLangCategoriesIds(categories, selectedItems);
		const idsGroupsToBeDeleted = getAllLangCategoriesIds(groups, selectedItems);
		const deletedCats = idsCatsToBeDeleted.length && (await deleteDataContent(idsCatsToBeDeleted, false));
		const deletedGroups =
			idsGroupsToBeDeleted.length && (await deleteCategoryGroup(idsGroupsToBeDeleted, deleteGroupCategories, false));
		if (deletedCats || deletedGroups) {
			currentStructuredData && getContents(currentStructuredData.id);
			toggleToast(`${selectedItems.all.length} categories and/or groups deleted`);
			unselectAllItems();
			isOpen("delete") && toggleModal("delete");
			isOpen("group") && toggleModal("group");
		}
		setIsBulkLoading(false);
	};

	const handleToggleDeleteModal = () => {
		const selectedCategories = currentDataContent.filter((category) => selectedItems.all.includes(category.id));
		const hasGroups = selectedCategories.some((category) => category.type === "group");
		hasGroups ? toggleModal("group") : toggleModal("delete");
	};

	const unselectAllItems = () => resetBulkSelection();

	const selectItems = () => (checkState.isAllSelected ? unselectAllItems() : handleSelectAll());

	const handleSelectAll = () => selectAllItems();

	const onScroll = (e: any) => setIsScrolling(e.target.scrollTop > 0);

	const filterItems = async (filterPointer: string, filtersSelected: IQueryValue[]) =>
		setFiltersSelection(filterPointer, filtersSelected);

	const toastProps = {
		setIsVisible,
		message: toastState,
	};

	const bulkActions = allowedToDeleteTaxonomy
		? [
				{
					icon: "delete",
					text: "delete",
					action: handleToggleDeleteModal,
				},
			]
		: [];

	const TableHeader = (
		<BulkHeader
			showBulk={areItemsSelected(catIds)}
			selectAllItems={handleSelectAll}
			totalItems={totalItems}
			selectItems={selectItems}
			checkState={checkState}
			isScrolling={isScrolling}
			bulkActions={bulkActions}
			filterValues={filterValues}
			filterItems={filterItems}
			setHoverCheck={setHoverCheck}
			selectedItems={selectedItems}
			isLoading={isBulkLoading}
		/>
	);

	const handleDragStart = ({ active: { id: activeId } }: DragStartEvent) => {
		setActiveId(activeId);
		setOverId(activeId);
	};

	const handleDragMove = ({ delta }: DragMoveEvent) => {
		setOffsetLeft(delta.x);
	};

	const handleDragOver = ({ over }: DragOverEvent) => {
		setOverId(over?.id ?? null);
	};

	const handleDragEnd = async ({ active, over }: DragEndEvent) => {
		resetState();

		if (projected && over) {
			const { depth, parentId } = projected;
			const clonedItems: FlattenedItem[] = JSON.parse(JSON.stringify(flattenTree(items)));
			const overIndex = clonedItems.findIndex(({ id }) => id === over.id);
			const activeIndex = clonedItems.findIndex(({ id }) => id === active.id);
			const activeTreeItem = clonedItems[activeIndex];
			const parentItem = clonedItems.find((item) => item.id === parentId);

			if (!parentItem || parentItem.type === "group") {
				clonedItems[activeIndex] = { ...activeTreeItem, depth, parentId };
				const sortedItems = arrayMove(clonedItems, activeIndex, overIndex);
				const newItems = buildTree(sortedItems);
				setItems(newItems);

				const isGoingUp = overIndex < activeIndex;
				const index = isGoingUp ? overIndex - 1 : overIndex;
				const itemDestination = clonedItems[index];

				const params: IOrderCategoryParams = {
					contentId: activeTreeItem.id,
					type: activeTreeItem.type,
					position: itemDestination ? itemDestination.position + 1 : 1,
					parentGroup: (parentId as number) || 0,
					structuredData: activeTreeItem.structuredData,
					relatedSite: currentSiteID || null,
					language: lang.id,
				};

				await orderCategory(params);
			}
		}
	};

	const handleDragCancel = () => resetState();

	const resetState = () => {
		setOverId(null);
		setActiveId(null);
		setOffsetLeft(0);
	};

	const handleCollapse = (id: UniqueIdentifier) => {
		setItems((items) =>
			setProperty(items, id, "collapsed", (value) => {
				return !value;
			}),
		);
	};

	const handleLanguage = (language: ILanguage) => {
		const { locale, id } = language;
		setLanguage({ locale, id });
	};

	const emptyStateProps = {
		message: hasCategories
			? `To start using ${categoryName} categories, create as many of them as yo need.`
			: "To start using categories in your site, you must active all Content type packages as you need.",
		button: hasCategories ? `Create ${categoryName} category` : "Activate Content type packages",
		action: hasCategories ? () => toggleModal("panel") : () => setHistoryPush("/sites/settings/content-types"),
	};

	const mainDeleteModalAction = {
		title: isBulkLoading ? "deleting" : "delete",
		onClick: bulkDelete,
		disabled: isBulkLoading,
	};

	const secondaryDeleteModalAction = {
		title: "Cancel",
		onClick: () => toggleModal("delete"),
	};

	const mainDeleteGroupModalAction = {
		title: isBulkLoading ? "deleting" : "delete",
		disabled: isBulkLoading,
		onClick: bulkDelete,
	};

	const secondaryDeleteGroupModalAction = {
		title: "cancel",
		onClick: () => toggleModal("group"),
	};

	return (
		<MainWrapper
			title={toTitleCase(LOCALE.entityLabel.category.other)}
			rightButton={rightButtonProps}
			language={lang}
			availableLanguages={availableLanguages}
			languageAction={handleLanguage}
			searchAction={setSearchQuery}
			searchValue={searchQuery}
		>
			<S.CategoryListWrapper>
				<CategoryNav
					current={currentStructuredData}
					categories={currentCategories}
					onClick={handleClick}
					dataPacks={activatedDataPacks}
				/>
				<S.TableWrapper>
					<ErrorToast />
					<S.Intro>
						<S.IntroTitle>Create and manage categories</S.IntroTitle>
						<div>Define your categories by creating the elements you need and asign them to your content types.</div>
					</S.Intro>
					<TableList tableHeader={TableHeader} onScroll={onScroll} hasFixedHeader={true} tableRef={tableRef}>
						<S.SearchTags>
							<SearchTagsBar query={searchQuery} setQuery={setSearchQuery} />
							<FilterTagsBar
								filters={filterValues}
								setFilters={setFiltersSelection}
								resetFilters={resetFilterQuery}
								labels={{ translated: "Translated" }}
							/>
						</S.SearchTags>
						{isEmpty ? (
							<S.EmptyWrapper>
								<EmptyState {...emptyStateProps} />
							</S.EmptyWrapper>
						) : (
							<>
								<S.Notification>
									Arrange your category list by <strong>drag & drop</strong>. You can move categories between groups,
									but <strong>it&apos;s not possible to nest one category within another to create a new group.</strong>
								</S.Notification>
								<DndContext
									sensors={sensors}
									collisionDetection={closestCenter}
									measuring={measuring}
									onDragStart={handleDragStart}
									onDragMove={handleDragMove}
									onDragOver={handleDragOver}
									onDragEnd={handleDragEnd}
									onDragCancel={handleDragCancel}
								>
									<SortableContext items={catIds} strategy={verticalListSortingStrategy}>
										{flattenedItems.map((category) => {
											const { id, collapsed, depth, children } = category;
											const isItemSelected = isSelected(id);
											return (
												<CategoryItem
													category={category}
													key={id}
													languages={availableLanguages}
													lang={lang}
													isTranslatable={isTranslatable}
													isSelected={isItemSelected}
													onChange={addToBulkSelection}
													toggleToast={toggleToast}
													getContents={getContents}
													hoverCheck={checkState.hoverCheck}
													depth={id === activeId && projected ? projected.depth : depth}
													indentationWidth={indentationWidth}
													collapsed={Boolean(collapsed && children.length)}
													onCollapse={children?.length ? () => handleCollapse(id) : undefined}
												/>
											);
										})}
									</SortableContext>
								</DndContext>
							</>
						)}
					</TableList>
				</S.TableWrapper>
			</S.CategoryListWrapper>
			<CategoryPanel
				isOpen={isOpen("panel")}
				toggleModal={() => toggleModal("panel")}
				getContents={getContents}
				toggleToast={toggleToast}
			/>
			{isOpen("delete") && (
				<DeleteModal
					isOpen={isOpen("delete")}
					toggleModal={() => toggleModal("delete")}
					mainModalAction={mainDeleteModalAction}
					secondaryModalAction={secondaryDeleteModalAction}
				/>
			)}
			{isOpen("group") && (
				<DeleteGroupModal
					isOpen={isOpen("group")}
					toggleModal={() => toggleModal("group")}
					mainModalAction={mainDeleteGroupModalAction}
					secondaryModalAction={secondaryDeleteGroupModalAction}
					deleteGroupCategories={deleteGroupCategories}
					setDeleteGroupCategories={setDeleteGroupCategories}
				/>
			)}
			{isVisible && <Toast {...toastProps} />}
		</MainWrapper>
	);
};

const mapStateToProps = (state: IRootState) => ({
	categories: state.structuredData.categories,
	currentSiteID: state.sites.currentSiteInfo?.id ?? null,
	siteLanguages: state.sites.currentSiteLanguages,
	globalLangs: state.app.globalLangs,
	currentDataContent: state.structuredData.currentDataCategory,
	currentStructuredData: state.structuredData.currentStructuredData,
	lang: state.app.lang,
	totalItems: state.sites.totalItems,
	activatedDataPacks: state.dataPacks.activated,
});

interface IDispatchProps {
	getStructuredDataContents(params: IGetStructuredDataParams, siteID: number | null): Promise<void>;
	setLanguage(lang: { locale: string; id: number | null }): void;
	setSelectedCategory(id: string, scope: string): void;
	deleteDataContent(catID: number[], refresh?: boolean): Promise<boolean>;
	setHistoryPush(path: string): void;
	deleteCategoryGroup(id: number | number[], deleteChildren: boolean, refresh?: boolean): Promise<boolean>;
	orderCategory(params: IOrderCategoryParams): Promise<boolean>;
}

interface ICategoriesProps {
	currentSiteID: number | null;
	currentDataContent: (IStructuredDataCategory | ICategoryGroup)[];
	currentStructuredData: IStructuredData | null;
	lang: { locale: string; id: number };
	siteLanguages: ILanguage[];
	globalLangs: ILanguage[];
	totalItems: number;
	categories: { global: IStructuredData[]; site: IStructuredData[] };
	activatedDataPacks: IDataPack[];
}

type IProps = ICategoriesProps & IDispatchProps;
type TreeItem = IStructuredDataCategory | ICategoryGroup;

const mapDispatchToProps = {
	getStructuredDataContents: structuredDataActions.getStructuredDataContents,
	setLanguage: appActions.setLanguage,
	setSelectedCategory: structuredDataActions.setSelectedCategory,
	deleteDataContent: structuredDataActions.deleteStructuredDataContent,
	deleteCategoryGroup: structuredDataActions.deleteCategoryGroup,
	setHistoryPush: appActions.setHistoryPush,
	orderCategory: structuredDataActions.orderCategory,
};

export default connect(mapStateToProps, mapDispatchToProps)(CategoriesList);
