import { useEffect, useState, memo } from "react";
import { closestCenter, DndContext, useSensor, useSensors, PointerSensor } from "@dnd-kit/core";
import type { DragEndEvent, DragStartEvent } from "@dnd-kit/core";
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";

import type { IComponent, IModule, INotification, ISchemaField, ITemplate, ModuleCategoryInfo } from "@ax/types";
import { ComponentContainer, SideModal, Toast } from "@ax/components";
import { useBulkSelection, useModal, useToast } from "@ax/hooks";

import { getComponentProps, containerToComponentArray, getTypefromKey, getModulesToPaste } from "../helpers";
import AddItemButton from "./AddItemButton";
import PasteModuleButton from "../PasteModuleButton";
import BulkHeader from "../BulkHeader";

import * as S from "./style";

const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element => {
	const {
		whiteList,
		title,
		value,
		goTo,
		selectedContent,
		editorID,
		actions,
		categories,
		maxItems,
		disabled,
		activatedModules,
		objKey,
		field,
		mandatory,
		theme,
		moduleCopy,
		availableDataPacks,
		template,
		setHistoryPush,
		scrollEditorID,
	} = props;

	const {
		addModuleAction,
		addComponentAction,
		deleteModuleAction,
		replaceElementsInCollectionAction,
		pasteModuleAction,
		setNotificationAction,
		copyModuleAction,
		duplicateModuleAction,
		setSelectedFormFieldAction,
	} = actions || {};

	// fix for old not array values
	const fixedValue = Array.isArray(value) ? value : containerToComponentArray(value);
	const componentIDs: number[] = fixedValue.map((element) => element.editorID);

	const type = getTypefromKey(objKey);
	const { contentType = type } = field;

	const { modulesToPaste, unavailableModules } = getModulesToPaste(
		moduleCopy,
		whiteList,
		availableDataPacks,
		template,
		field,
	);

	const selectedID = parseInt(localStorage.getItem("selectedID") || "0", 10);
	const isComponentModule = contentType === "components";
	const isModuleArr = contentType === "modules" || contentType === "fields";
	const isFormArr = contentType === "fields";

	const isAbletoAdd = (!maxItems || fixedValue.length < maxItems) && !disabled && whiteList.length > 0;
	const showAddItemButton = isAbletoAdd && (!isFormArr || (isFormArr && !!setSelectedFormFieldAction));
	const showPasteModuleButton = isAbletoAdd && modulesToPaste.length > 0;

	const canReplace = maxItems === 1 && whiteList.length > 1;
	const displayReplaceSideModal = fixedValue.length > 0 && canReplace;
	const optionsType = isModuleArr ? "modules" : "components";

	const { isOpen, toggleModal } = useModal(false, false);
	const [isBulkOpen, setIsBulkOpen] = useState(false);
	const [draggingId, setDraggingId] = useState<number | null>(null);
	const { isVisible, toggleToast, setIsVisible, state: toastState } = useToast();

	const { resetBulkSelection, selectedItems, isSelected, checkState, addToBulkSelection, selectAllItems } =
		useBulkSelection(componentIDs);

	useEffect(() => {
		if (selectedItems.all.length) {
			setIsBulkOpen(true);
		}
	}, [selectedItems.all]);

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

	const getText = (name: string, index: number) => {
		return fixedValue.length > 1 ? `#${index + 1} ${name}` : name;
	};

	const handleAddModule = (moduleType: string) => addModuleAction?.(moduleType, objKey, editorID, isComponentModule);

	const handleAddComponent = (componentType: string) => addComponentAction?.(componentType, objKey);

	const handleAdd = isModuleArr ? handleAddModule : handleAddComponent;

	const handleModuleReplace = (moduleType: string | IComponent) => {
		const { modules } = selectedContent;
		if (typeof moduleType !== "string") return;
		if (isModuleArr && deleteModuleAction && modules?.length > 0) {
			const currentModule: IModule = modules[0];
			deleteModuleAction([currentModule.editorID], objKey);
			handleAddModule(moduleType);
		} else {
			replaceElementsInCollectionAction?.(moduleType);
		}
	};

	const selectItems = () => (checkState.isAllSelected ? resetBulkSelection() : selectAllItems());

	const handleToggleModal = () =>
		isFormArr && setSelectedFormFieldAction ? setSelectedFormFieldAction(field) : toggleModal();

	const Asterisk = () => (mandatory ? <S.Asterisk>*</S.Asterisk> : null);

	const ComponentList = memo(function ComponentList({ components }: { components: IModule[] }): JSX.Element {
		return (
			<>
				{components.map((element: IModule, i: number) => {
					const { editorID, fixed } = element;
					const componentProps = getComponentProps(element, activatedModules, isModuleArr);
					const { moduleTitle, isModuleDeactivated, componentTitle, displayName, isModuleDisabled } = componentProps;
					const text = getText(componentTitle || displayName, i);
					const isItemSelected = isSelected(editorID);
					const isDraggingSelected = selectedItems.all.includes(draggingId);
					const isGhosting = isItemSelected && !!draggingId && draggingId !== editorID && isDraggingSelected;
					const isMultiDragging = selectedItems.all.length > 1 && draggingId === editorID;
					const isActive = scrollEditorID === editorID;
					const canDelete = !isFormArr || !fixed;

					return (
						<ComponentContainer
							actionReplace={toggleModal}
							canReplace={canReplace}
							isArray={true}
							key={editorID}
							editorID={editorID}
							goTo={goTo}
							text={text}
							moduleTitle={moduleTitle}
							whiteList={whiteList}
							categories={categories}
							actions={actions}
							selectedContent={selectedContent}
							disabled={disabled || isModuleDisabled}
							canDuplicate={isAbletoAdd && !isModuleDeactivated}
							canDelete={canDelete}
							parentKey={objKey}
							theme={theme}
							arrayLength={components.length}
							isSelected={isItemSelected}
							onChange={addToBulkSelection}
							hasMenu={selectedItems.all.length > 0}
							toggleToast={toggleToast}
							isMultiDragging={isMultiDragging}
							draggingCount={selectedItems.all.length}
							isActive={selectedID === editorID}
							className={`${isGhosting ? "ghosting" : ""} ${isMultiDragging ? "dragging" : ""} ${isActive ? "active" : ""}`}
						/>
					);
				})}
			</>
		);
	});

	const handleDragStart = (event: DragStartEvent) => {
		const { active } = event;
		setDraggingId(active.id as number);
	};

	const handleOnDragEnd = (event: DragEndEvent) => {
		const { active, over } = event;
		const { moveModuleAction } = actions || {};

		if (moveModuleAction && over && active.id !== over.id) {
			const newIndex = fixedValue.map((e: IModule) => e.editorID).indexOf(over.id as number);

			if (selectedItems.all.length > 0 && selectedItems.all.includes(active.id)) {
				moveModuleAction(selectedItems.all, selectedContent, newIndex, objKey);
			} else {
				moveModuleAction([active.id as number], selectedContent, newIndex, objKey);
			}
		}

		setDraggingId(null);
	};

	const handleCopy = () => {
		const modulesCopied = copyModuleAction?.(selectedItems.all);
		modulesCopied &&
			toggleToast(`${modulesCopied} module${selectedItems.all.length > 1 ? "s" : ""} copied to clipboard`);
	};

	const handleDuplicate = () => {
		if (maxItems && maxItems - fixedValue.length < selectedItems.all.length) {
			const notification: INotification = {
				type: "error",
				text: "Unable to duplicate modules: The destination area has a limit on the number of modules allowed. Please adjust the selection accordingly.",
			};
			setNotificationAction?.(notification);
		} else {
			duplicateModuleAction?.(selectedItems.all, objKey);
			resetBulkSelection();
		}
	};

	const handleDelete = () => {
		const modulesToDelete = fixedValue
			.filter((element) => selectedItems.all.includes(element.editorID) && (!isFormArr || !element.fixed))
			.map((element) => element.editorID);
		deleteModuleAction?.(modulesToDelete, objKey);
		resetBulkSelection();
	};

	const bulkActions = [
		{
			icon: "copy",
			text: "copy",
			action: handleCopy,
		},
		{
			icon: "duplicate",
			text: "duplicate",
			action: handleDuplicate,
		},
		{
			icon: "delete",
			text: "delete",
			action: handleDelete,
		},
	];

	const toggleBulk = () => {
		setIsBulkOpen(false);
		resetBulkSelection();
	};

	return (
		<S.Wrapper>
			<S.Title>
				{title} <Asterisk />
			</S.Title>
			<S.ItemRow>
				{isBulkOpen ? (
					<BulkHeader
						selectItems={selectItems}
						checkState={checkState}
						totalItems={selectedItems.all.length}
						toggleBulk={toggleBulk}
						actions={bulkActions}
					/>
				) : (
					<S.Subtitle>{fixedValue?.length || 0} items</S.Subtitle>
				)}
				<S.ActionsWrapper data-testid="mixableComponentWrapper">
					{showPasteModuleButton && pasteModuleAction && (
						<PasteModuleButton
							editorID={editorID}
							isModuleCopyUnavailable={unavailableModules.length > 0}
							pasteModule={pasteModuleAction}
							setNotification={setNotificationAction}
							setHistoryPush={setHistoryPush}
							arrayKey={objKey}
							modulesToPaste={modulesToPaste}
							slots={maxItems ? maxItems - fixedValue.length : null}
						/>
					)}
					{showAddItemButton && !disabled && (
						<AddItemButton
							isOpen={isOpen}
							toggleModal={handleToggleModal}
							whiteList={whiteList}
							categories={categories}
							handleClick={handleAdd}
							theme={theme}
							contentType={contentType}
						/>
					)}
				</S.ActionsWrapper>
			</S.ItemRow>
			{fixedValue && (
				<DndContext
					collisionDetection={closestCenter}
					onDragEnd={handleOnDragEnd}
					onDragStart={handleDragStart}
					sensors={sensors}
				>
					<SortableContext items={componentIDs} strategy={verticalListSortingStrategy}>
						<ComponentList components={fixedValue} />
					</SortableContext>
				</DndContext>
			)}
			{displayReplaceSideModal && isOpen && (
				<SideModal
					optionsType={optionsType}
					whiteList={whiteList}
					categories={categories}
					toggleModal={toggleModal}
					isOpen={isOpen}
					onClick={handleModuleReplace}
					theme={theme}
					showSearch
				/>
			)}
			{isVisible && <Toast message={toastState} setIsVisible={setIsVisible} />}
		</S.Wrapper>
	);
};

export interface IMixableComponentArrayProps {
	maxItems?: number;
	title: string;
	whiteList: string[];
	value?: IModule[];
	selectedContent: any;
	editorID: number;
	goTo: (editorID: number) => void;
	actions?: {
		addComponentAction: (componentType: IComponent | string, key?: string) => void;
		addModuleAction: (moduleType: string, key: string, selectedID: number, isComponentModule?: boolean) => void;
		deleteModuleAction: (editorID: number[], key?: string) => void;
		duplicateModuleAction: (editorID: number[], key?: string) => number;
		copyModuleAction: (editorID: number[]) => boolean | number;
		replaceElementsInCollectionAction: (newValue: string, reference?: string) => void;
		moveModuleAction: (moduleID: number[], selectedContent: any, newIndex: number, key: string) => void;
		pasteModuleAction: (editorID: number, key: string, modulesToPaste: IModule[]) => Promise<{ error?: INotification }>;
		setNotificationAction: (notification: INotification) => void;
		replaceModuleAction: (module: IComponent, parent: any, objKey: string) => void;
		setSelectedFormFieldAction: (field: ISchemaField | null) => void;
	};
	categories?: ModuleCategoryInfo[];
	disabled?: boolean;
	activatedModules: string[];
	objKey: string;
	field: ISchemaField;
	mandatory?: boolean;
	theme: string;
	moduleCopy: { date: Date; elements: IModule[] } | null;
	availableDataPacks: Record<string, any>[];
	template: ITemplate;
	setHistoryPush?: (path: string, isEditor: boolean) => void;
	scrollEditorID: number | null;
}

export default MixableComponentArray;
