import { useEffect, useState, memo } from "react";
import { closestCenter, DndContext, PointerSensor, useSensor, useSensors } 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, Toast } from "@ax/components";
import { useBulkSelection, useToast } from "@ax/hooks";
import { replaceElements } from "@ax/forms";

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

import * as S from "./style";

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

	const type = getTypefromKey(objKey);
	const { contentType = type } = field;
	const componentIDs: number[] = value?.length ? value.map((element) => element.editorID) : [];
	const componentType = field.reference ? selectedContent[field.reference] : selectedContent.kind;
	const isModuleArr = contentType === "modules";
	const isComponentModule = contentType === "components";
	const showAddItemButton = !maxItems || (value && value.length < maxItems);

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

	const [isBulkOpen, setIsBulkOpen] = useState(false);
	const [draggingId, setDraggingId] = useState<number | null>(null);
	const { resetBulkSelection, selectedItems, isSelected, checkState, addToBulkSelection, selectAllItems } =
		useBulkSelection(componentIDs);
	const { isVisible, toggleToast, setIsVisible, state: toastState } = useToast();

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

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

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

	const replacedElementsToPaste = replaceElements(modulesToPaste, componentType);
	const showPasteModuleButton = showAddItemButton && replacedElementsToPaste.length > 0;

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

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

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

	const handleAdd = isModuleArr ? handleAddModule : handleAddComponent;

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

	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 = value.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 ComponentList = memo(function ComponentList({ components }: { components: IModule[] }): JSX.Element {
		return (
			<>
				{components.map((element, i: number) => {
					const { editorID } = element;
					const componentProps = getComponentProps(element, activatedModules, isModuleArr);
					if (!componentProps) return null;
					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;

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

	const handleDuplicate = () => {
		if (maxItems && maxItems - value.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 = () => {
		deleteModuleAction?.(selectedItems.all, objKey);
		resetBulkSelection();
	};

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

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

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

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

	return (
		<S.Wrapper data-testid="sameComponentWrapper">
			<S.Title>
				{title} <Asterisk />
			</S.Title>
			<S.ItemRow>
				{isBulkOpen ? (
					<BulkHeader
						selectItems={selectItems}
						checkState={checkState}
						totalItems={selectedItems.all.length}
						toggleBulk={toggleBulk}
						actions={bulkActions}
					/>
				) : (
					<S.Subtitle>{value?.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={replacedElementsToPaste}
							slots={maxItems ? maxItems - value.length : null}
						/>
					)}
					{showAddItemButton && !disabled && (
						<AddItemButton handleClick={handleAdd} tooltipText={isModuleArr ? "Add module" : "Add component"} />
					)}
				</S.ActionsWrapper>
			</S.ItemRow>
			{value && Array.isArray(value) && (
				<DndContext
					collisionDetection={closestCenter}
					onDragEnd={handleOnDragEnd}
					onDragStart={handleDragStart}
					sensors={sensors}
				>
					<SortableContext items={componentIDs} strategy={verticalListSortingStrategy}>
						<ComponentList components={value} />
					</SortableContext>
				</DndContext>
			)}
			{isVisible && <Toast message={toastState} setIsVisible={setIsVisible} />}
		</S.Wrapper>
	);
};

export interface ISameComponentArrayProps {
	maxItems: number;
	title: string;
	whiteList: string[];
	value?: IModule[];
	elementUniqueSelection: boolean;
	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;
	};
	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;
}

export default SameComponentArray;
