import React, { useEffect, useRef, useState } from 'react';

import {
	Item,
	Menu,
	Separator,
	Submenu,
	useContextMenu,
} from 'react-contexify';
import 'react-contexify/ReactContexify.css';

import Fuse from 'fuse.js';
import clone from 'just-clone';
import { NumberSize, Resizable } from 're-resizable';
import { Direction } from 're-resizable/lib/resizer';
import SimpleBar from 'simplebar-react';
import 'simplebar-react/dist/simplebar.min.css';
import { v4 as uuidv4 } from 'uuid';

import {
	faArrowDown,
	faArrowUp,
	faCopy,
	faPaste,
	faTrashAlt,
} from '@fortawesome/pro-regular-svg-icons';
import {
	faCaretDown,
	faCaretUp,
	faClone,
	faEdit,
	faGrid,
	faPlus,
	faSpinnerThird,
	faTimes,
} from '@fortawesome/pro-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

// @ts-ignore
import conf from '../../../../nitrogen.config.js';
import type { NitrogenConfig } from '../@types/config.js';
import type {
	BuilderModule,
	ControlProps,
	Module,
	Prop,
	PropArray,
	PropEnum,
	PropFile,
	PropUrl,
	PropWysiwyg,
} from '../@types/settings.js';

import './App.scss';
import './Bob.scss';
import Gallery from './components/Gallery/Gallery.js';
import IFrame from './components/IFrame.js';
import Navigator from './components/Navigator/Navigator.js';
import ResponsiveWidget, {
	ResponsiveMode,
} from './components/ResponsiveWidget/ResponsiveWidget.js';
import { FlattenedItem } from './components/Tree/types.js';
import { buildTree, flattenTree } from './components/Tree/utilities.js';
import CtrlBoolean from './controls/CtrlBoolean/CtrlBoolean.js';
import CtrlColor from './controls/CtrlColor/CtrlColor.js';
import CtrlEnum from './controls/CtrlEnum/CtrlEnum.js';
import CtrlEnumInline from './controls/CtrlEnumInline/CtrlEnumInline.js';
import CtrlFile from './controls/CtrlFile/CtrlFile.js';
import CtrlNumber from './controls/CtrlNumber/CtrlNumber.js';
import CtrlUrl from './controls/CtrlSearch/CtrlUrl.js';
import CtrlText from './controls/CtrlText/CtrlText.js';
import CtrlWysiwyg from './controls/CtrlWysiwyg/CtrlWysiwyg.js';
import './tw.scss';
import clearEmpties from './utils/clearEmpties.js';
import getFnName from './utils/getFnName.js';
import getProvider from './utils/getProvider.js';
import resolve from './utils/resolve.js';
import { responsiveModes } from './utils/responsiveModes.js';
import set from './utils/set.js';
import unset from './utils/unset.js';
import useKeyPress from './utils/useKeyPress.js';

const config: NitrogenConfig = conf;

const PROVIDER = getProvider();

const CONTEXT_MENU_ID = 'nitrogen-context-menu';

function getCoords(elem: HTMLElement) {
	// crossbrowser version
	let box = elem.getBoundingClientRect();

	let body = document.body;
	let docEl = document.documentElement;

	let scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
	let scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;

	let clientTop = docEl.clientTop || body.clientTop || 0;
	let clientLeft = docEl.clientLeft || body.clientLeft || 0;

	let top = box.top + scrollTop - clientTop;
	let left = box.left + scrollLeft - clientLeft;

	return { top: Math.round(top), left: Math.round(left) };
}

const customControls = config.controls ?? [];

const modules = config.modules();

function recursiveGetPropValue(id: string, path: string, item: any) {
	if (item?.id === id) {
		return resolve(path, item.props);
	}

	if (item?.props?.children) {
		for (let child of item.props.children) {
			const value: any = recursiveGetPropValue(id, path, child);
			if (value !== undefined) return value;
		}
	}
}

function getPropValue(id: string, path: string, page: any[]) {
	let value: any;

	page.some((item) => {
		value = recursiveGetPropValue(id, path, item);
		return value !== undefined;
	});

	return value;
}

function recursiveGetLastPropValue(
	splitUpdatePath: string[],
	currMod: BuilderModule,
	page: any[]
): any {
	let mode = splitUpdatePath.pop()!;
	let modeI = responsiveModes.indexOf(mode);

	let newMode = false;

	if (modeI < responsiveModes.length - 1) {
		newMode = true;
		mode = responsiveModes[modeI + 1];
		splitUpdatePath.push(mode);
	}

	let value = newMode
		? getPropValue(currMod.id, splitUpdatePath.join('.'), page)
		: null;

	if ((value === undefined || value === null) && mode !== 'mobile') {
		return recursiveGetLastPropValue(splitUpdatePath, currMod, page);
	}

	return value;
}

function CardsContainer({
	closedCategories,
	cat,
	fuseResults,
	addModule,
}: any) {
	const [styles, setStyles] = useState<any[]>([]);
	const ref = useRef<HTMLDivElement>(null);

	function onCardsContainerMouseMove(e: React.MouseEvent<HTMLDivElement>) {
		if (!ref.current) {
			return;
		}

		const cards = ref.current.querySelectorAll(
			'.card-container'
		) as NodeListOf<HTMLDivElement>;

		let newStyles = Array(cards.length).fill({});

		for (let i = 0; i < cards.length; ++i) {
			const cardRect = cards[i].getBoundingClientRect();

			const x = e.clientX - cardRect.left;
			const y = e.clientY - cardRect.top;

			newStyles[i] = {
				'--mouse-x': `${x}px`,
				'--mouse-y': `${y}px`,
			};
		}

		setStyles(newStyles);
	}

	return (
		<div
			ref={ref}
			className={`cards-container ${
				closedCategories.includes(cat.categoryLabel) ? '' : 'is-open'
			}`}
			style={{
				height: closedCategories.includes(cat.categoryLabel)
					? 0
					: Math.ceil(cat.modules.length / 2) * 108,
			}}
			onMouseMove={onCardsContainerMouseMove}
		>
			{cat.modules.map((modM: Module, i: number) => {
				// @ts-ignore - we know this exists because the fuse search returns the item
				const mod = fuseResults.length > 0 ? modM.item : modM;
				return (
					<Card
						key={mod.name}
						style={styles[i]}
						mod={mod}
						addModule={addModule}
					/>
				);
			})}
		</div>
	);
}

function Card({ mod, addModule, style }: any) {
	return (
		<div
			className='card-container'
			style={style}
			onClick={() => addModule(mod.name)}
		>
			<div className='card-border'></div>
			<div className='card-content'>
				{mod?.options?.icon && <i className={mod.options.icon} />}
				{mod.name}
			</div>
		</div>
	);
}

const defaultInspectorWidthItem = 'nitrogen-inspector-width';
const defaultNavigatorWidthItem = 'nitrogen-navigator-width';
const defaultNavigatorOpenItem = 'nitrogen-navigator-open';
const defaultClosedCategoriesItem = 'nitrogen-closed-categories';

const defaultInspectorWidth = parseFloat(
	localStorage.getItem(defaultInspectorWidthItem) || '380'
);
const defaultNavigatorWidth = parseFloat(
	localStorage.getItem(defaultNavigatorWidthItem) || '250'
);
const defaultNavigatorOpen =
	localStorage.getItem(defaultNavigatorOpenItem) !== 'false';
const defaultClosedCategories =
	JSON.parse(localStorage.getItem(defaultClosedCategoriesItem) || '[]') || [];

function App() {
	const [mods, setMods] = useState<Module[]>([]);
	const [modsSearch, setModsSearch] = useState<string>('');

	const [fuse, setFuse] = useState<any>(null);
	const [fuseResults, setFuseResults] = useState<any[]>([]);

	const [openArrayPropItems, setOpenArrayPropItems] = useState<{
		[key: string]: number;
	}>({});

	function toggleArrayPropItem(
		prop: string,
		item: number,
		action?: 'open' | 'close'
	) {
		const newArray = { ...openArrayPropItems };

		const arrayKeys = Object.keys(openArrayPropItems);

		if (arrayKeys.includes(prop)) {
			if (newArray[prop] === item && action !== 'open') {
				delete newArray[prop];
			} else if (action !== 'close') {
				newArray[prop] = item;
			}
		} else if (action !== 'close') {
			newArray[prop] = item;
		}

		setOpenArrayPropItems(newArray);
	}

	function isArrayPropItemOpen(prop: string, item: number) {
		const arrayKeys = Object.keys(openArrayPropItems);

		if (arrayKeys.includes(prop)) {
			return openArrayPropItems[prop] === item;
		}

		return false;
	}

	const iframeRef = useRef<any>(null);

	async function loadModules() {
		const modArray: Module[] = await Promise.all(
			Object.keys(modules).map(async (key) => {
				const mod: any = await modules[key]();

				const name = getFnName(mod.default)
					.replace(/([A-Z])/g, ' $1')
					.trim();

				const Component = React.forwardRef(mod.default);

				if (!mod.settings.options) {
					mod.settings.options = {
						category: 'General',
					};
				} else if (!mod.settings.options.category) {
					mod.settings.options.category = 'General';
				}

				return {
					name,
					Component,
					...mod.settings,
				};
			})
		);
		setMods(modArray);
	}

	useEffect(() => {
		loadModules();
	}, []);

	useEffect(() => {
		if (mods.length) {
			const fuse = new Fuse(mods, {
				keys: ['name'],
			});
			setFuse(fuse);
		}
	}, [mods]);

	function handleModSearch(e: React.ChangeEvent<HTMLInputElement>) {
		if (!fuse) {
			console.error('Fuse not ready');
			return;
		}

		const searchParam = e.target.value;

		setModsSearch(searchParam);

		const fuseResults = fuse.search(searchParam);

		setFuseResults(fuseResults);
	}

	function clearModSearch() {
		setModsSearch('');
		setFuseResults([]);
	}

	const [initTitle, setInitTitle] = useState('');
	const [pageTitle, setPageTitle] = useState('');
	const [page, setPage] = useState<any[]>([]);

	const [madeChanges, setMadeChanges] = useState(false);
	const [isSaving, setIsSaving] = useState(false);

	const canSave = !!pageTitle && (initTitle !== pageTitle || madeChanges);

	const queryString = window.location.search;
	const urlParams = new URLSearchParams(queryString);
	const [pageId, setPageId] = useState(urlParams.get('pageId') ?? '');

	const [loading, setLoading] = useState(true);

	const [responsiveMode, setResponsiveMode] = useState<
		'desktop' | 'laptop' | 'tablet' | 'mobileLandscape' | 'mobile'
	>('mobile');

	// #region Load Page

	function recursiveSwapNameToModule(item: any) {
		const name = item.module;

		const module = mods.find((mod) => mod.name === name);

		if (item.props?.children) {
			item.props.children = item.props.children.map((child: any) =>
				recursiveSwapNameToModule(child)
			);
		}

		return {
			...item,
			module: {
				...module,
				ref: React.createRef(),
			},
		};
	}

	async function loadPage() {
		const loadedPage = await PROVIDER.loadPage(pageId);

		try {
			let page = JSON.parse(loadedPage.data);

			// convert page component names to actual components
			page = page.map((item: any) => {
				return recursiveSwapNameToModule(item);
			});

			setInitTitle(loadedPage.title);
			setPageTitle(loadedPage.title);
			setPage(page);
		} catch (e) {
			console.error(e);
		}

		setLoading(false);
	}

	useEffect(() => {
		if (
			pageId !== null &&
			pageId !== undefined &&
			pageId.trim() !== '' &&
			mods.length > 0 &&
			loading
		) {
			loadPage();
		}
	}, [pageId, mods]);

	// #endregion

	useEffect(() => {
		const handler = (event: BeforeUnloadEvent) => {
			event.preventDefault();
			event.returnValue = '';
		};

		if (madeChanges) {
			window.addEventListener('beforeunload', handler);

			return () => {
				window.removeEventListener('beforeunload', handler);
			};
		}

		return () => {};
	}, [madeChanges]);

	const [currParentMod, setCurrParentMod] = useState<BuilderModule | null>(
		null
	);
	const [currMod, setCurrMod] = useState<BuilderModule | null>(null);
	const [currModIndex, setCurrModIndex] = useState<number>(0);
	const [inspectorOpen, setInspectorOpen] = useState(false);
	const [settingsOpen, setSettingsOpen] = useState(false);
	const [isPreviewing, setIsPreviewing] = useState(false);

	// #region Keyboard Shortcuts

	useKeyPress(['s'], 'ctrl', savePage, [
		document,
		iframeRef.current?.contentWindow.document,
	]);
	useKeyPress(
		['Escape'],
		false,
		() => {
			if (isPreviewing) {
				setIsPreviewing(false);
			}
		},
		[document, iframeRef.current?.contentWindow.document]
	);
	useKeyPress(
		['c'],
		'ctrl',
		() => {
			if (currMod) {
				copyModule(currMod.id);
			}
		},
		[document, iframeRef.current?.contentWindow.document]
	);
	useKeyPress(
		['v'],
		'ctrl',
		() => {
			if (currMod) {
				pasteModule(currMod.id);
			}
		},
		[document, iframeRef.current?.contentWindow.document]
	);

	// #endregion

	const { show: showContextMenu, hideAll: hideContextMenu } = useContextMenu({
		id: CONTEXT_MENU_ID,
	});

	function selectModule(
		mod: BuilderModule,
		index: number = 0,
		parentMod: BuilderModule | null = null,
		e?: React.MouseEvent<HTMLDivElement, MouseEvent>
	) {
		e && e.stopPropagation();
		setCurrParentMod(parentMod);
		setCurrMod(mod);
		setCurrModIndex(index);
		setInspectorOpen(true);
		setSettingsOpen(false);
	}

	function getModuleIndex(
		mod: BuilderModule,
		page: BuilderModule[],
		parentMod?: BuilderModule | null
	) {
		if (!parentMod) {
			return page.findIndex((item) => item.id === mod.id);
		}

		return parentMod.props.children.findIndex(
			(item: BuilderModule) => item.id === mod.id
		);
	}

	function addModule(name: string) {
		const module = mods.find((mod) => mod.name === name);

		if (!module) return;

		const moduleId = 'f' + uuidv4();

		setMadeChanges(true);

		if (!currMod?.module?.options?.children) {
			const newModule: BuilderModule = {
				id: `${moduleId}`,
				module: { ...module, ref: React.createRef() },
				props: {},
			};

			let parentMod = undefined;

			function recursiveAddNextModule(item: any) {
				if (currParentMod && item && currParentMod.id === item.id) {
					item.props.children.splice(currModIndex + 1, 0, newModule);
					parentMod = item;
					return item;
				}
				if (item.id === currMod?.id && !currParentMod) {
					setPage((page) => {
						page.splice(currModIndex + 1, 0, newModule);
						return [...page];
					});
				}

				if (item.props?.children) {
					item.props.children = item.props.children.map((child: any) =>
						recursiveAddNextModule(child)
					);
				}

				return item;
			}

			let newPage = [...page];

			if (currMod) {
				if (currParentMod) {
					newPage = newPage.map((item: any) => recursiveAddNextModule(item));
				} else {
					newPage.splice(currModIndex + 1, 0, newModule);
				}
			} else {
				newPage.push(newModule);
			}

			setPage(newPage);
			selectModule(newModule, currModIndex + 1, parentMod);
			return;
		}

		const newModule = {
			id: `${moduleId}`,
			module: { ...module, ref: React.createRef() },
			props: {},
		};

		let newModIndex = 0;
		let newParentMod = undefined;

		function recursiveAddModule(item: any) {
			if (!currMod) {
				return item;
			}

			if (item.id === currMod.id) {
				if (!item.props.children) {
					item.props.children = [];
				}
				newModIndex = item.props.children.length;
				newParentMod = item;
				item.props.children.push(newModule);
				return item;
			}

			if (item.props?.children) {
				item.props.children = item.props.children.map((child: any) =>
					recursiveAddModule(child)
				);
			}

			return item;
		}

		const newPage = page.map((item: any) => {
			return recursiveAddModule(item);
		});

		setPage(newPage);
		selectModule(newModule, newModIndex, newParentMod);
	}

	// #region Inspector

	function recursiveUpdateProps(
		id: string,
		path: string,
		value: any,
		item: any
	) {
		if (item.id === id) {
			set(path, value, item.props);
			return item;
		}

		if (item.props?.children) {
			return {
				...item,
				props: {
					...item.props,
					children: item.props.children.map((child: any) =>
						recursiveUpdateProps(id, path, value, child)
					),
				},
			};
		}

		return item;
	}

	function updateProps(id: string, path: string, value: any) {
		const newPage = page.map((item) => {
			return recursiveUpdateProps(id, path, value, item);
		});
		setMadeChanges(true);
		setPage(newPage);
	}

	function recursiveDeleteProp(id: string, path: string, item: any) {
		if (item.id === id) {
			unset(path, item.props);
			return item;
		}

		if (item.props?.children) {
			return {
				...item,
				props: {
					...item.props,
					children: item.props.children.map((child: any) =>
						recursiveDeleteProp(id, path, child)
					),
				},
			};
		}

		return item;
	}

	function deleteProp(path: string) {
		if (!currMod) {
			console.warn('No current module selected');
			return;
		}

		const newPage = page.map((item) => {
			return recursiveDeleteProp(currMod.id, path, item);
		});
		setMadeChanges(true);
		setPage(newPage);
	}

	function recursiveGetLastDefaultValue(
		prop: Prop,
		responsiveMode: ResponsiveMode
	): any {
		if (!prop.default) return '';

		if (prop.default[responsiveMode]) {
			return prop.default[responsiveMode];
		}

		if (responsiveMode === responsiveModes[responsiveModes.length - 1])
			return '';

		return recursiveGetLastDefaultValue(
			prop,
			responsiveModes[
				responsiveModes.indexOf(responsiveMode) + 1
			] as ResponsiveMode
		);
	}

	function renderPropInput(prop: Prop, key: string, path?: string) {
		const updatePath = path
			? `${path}.${key}${prop?.responsive ? `.${responsiveMode}` : ''}`
			: `${key}${prop?.responsive ? `.${responsiveMode}` : ''}`;

		if (!currMod) {
			return;
		}

		let propValue = getPropValue(currMod.id, updatePath, page);
		let inheritVal = false;

		if (typeof prop === 'object' && prop.responsive) {
			let splitUpdatePath = updatePath.split('.');
			if (
				splitUpdatePath.length > 1 &&
				(propValue === undefined || propValue === null)
			) {
				inheritVal = true;
				propValue = recursiveGetLastPropValue(splitUpdatePath, currMod, page);
			}
		}

		if (propValue === undefined || propValue === null) {
			inheritVal = true;
		}

		const value =
			propValue ??
			(typeof prop === 'object'
				? prop?.responsive
					? recursiveGetLastDefaultValue(prop, responsiveMode)
					: prop.default ?? ''
				: '');

		if (typeof prop === 'object' && !Array.isArray(prop) && prop !== null) {
			let renderControl: JSX.Element | null = null;
			customControls.some((control) => {
				if (prop.type === control.type) {
					renderControl = control.component({
						currMod,
						prop,
						value,
						updatePath,
						inheritVal,
						updateProps,
						deleteProp,
					} as ControlProps);
					return true;
				}
			});
			if (renderControl !== null) {
				return renderControl;
			}

			if (prop.type === 'enum') {
				return (
					<CtrlEnum
						key={currMod.id + updatePath}
						prop={prop as PropEnum}
						updateProps={updateProps}
						deleteProp={deleteProp}
						value={value}
						inheritVal={inheritVal}
						currMod={currMod}
						updatePath={updatePath}
					/>
				);
			}
			if (prop.type === 'enum-inline') {
				return (
					<CtrlEnumInline
						key={currMod.id + updatePath}
						prop={prop as PropEnum}
						updateProps={updateProps}
						deleteProp={deleteProp}
						value={value}
						inheritVal={inheritVal}
						currMod={currMod}
						updatePath={updatePath}
					/>
				);
			}
			if (prop.type === 'array') {
				const subprops = (prop as PropArray).props;

				if (!subprops) {
					return <div>Missing props for array</div>;
				}
				return (
					<div className={`ctrl-array`}>
						{(value ? value : []).map((item: any, index: any) => {
							return (
								<div
									key={index}
									className={`ctrl-array__container ${
										isArrayPropItemOpen(updatePath, index)
											? 'ctrl-array__container--open'
											: ''
									}`}
								>
									<div
										className={`ctrl-array__container__title`}
										onClick={() => toggleArrayPropItem(updatePath, index)}
									>
										{/* TODO: Allow to use a field to display as title opposed to just item number */}
										<div className={`ctrl-array__container__title-text`}>
											Item {index + 1}
										</div>
										<div
											className={`ctrl-array__container__btn`}
											onClick={(e) => {
												e.stopPropagation();
												toggleArrayPropItem(updatePath, index, 'close');
												deleteProp(`${updatePath}.${index}`);
											}}
										>
											<i className='ctrl-array__container__btn__icon fa-solid fa-sharp fa-times' />
										</div>
									</div>
									<div className={`ctrl-array__container__item-container`}>
										{Object.keys(subprops).map((subPropKey: string) => {
											let fieldTitle =
												subprops[subPropKey]?.label ??
												subPropKey.replace(/([A-Z])/g, ' $1');

											fieldTitle =
												fieldTitle.charAt(0).toUpperCase() +
												fieldTitle.slice(1);

											if (
												subprops[subPropKey].isVisible &&
												subprops[subPropKey].isVisible!(currMod.props) === false // not sure why this is throwing ts error when we're checking for undefined above
											) {
												return;
											}

											return (
												<div
													key={subPropKey}
													className='ctrl-array__container__item'
												>
													<div className=''>{fieldTitle}</div>
													{renderPropInput(
														subprops[subPropKey],
														subPropKey,
														`${updatePath}.${index}`
													)}
												</div>
											);
										})}
									</div>
								</div>
							);
						})}
						<div
							className={`ctrl-array__section-btn`}
							onClick={() => {
								const newPropValue =
									getPropValue(currMod.id, updatePath, page) ?? [];
								newPropValue.push({});
								updateProps(currMod.id, updatePath, newPropValue);
							}}
						>
							Add Item{' '}
							<FontAwesomeIcon
								className='ctrl-array__section-btn__icon'
								icon={faPlus}
							/>
						</div>
					</div>
				);
			}
		}

		if (prop.type === 'boolean') {
			return (
				<CtrlBoolean
					key={currMod.id + updatePath}
					prop={prop}
					updateProps={updateProps}
					deleteProp={deleteProp}
					value={value}
					inheritVal={inheritVal}
					currMod={currMod}
					updatePath={updatePath}
				/>
			);
		}

		if (prop.type === 'number') {
			return (
				<CtrlNumber
					key={currMod.id + updatePath}
					prop={prop}
					updateProps={updateProps}
					deleteProp={deleteProp}
					value={value}
					inheritVal={inheritVal}
					currMod={currMod}
					updatePath={updatePath}
				/>
			);
		}

		if (prop.type === 'file') {
			return (
				<CtrlFile
					key={currMod.id + updatePath}
					prop={prop as PropFile}
					updateProps={updateProps}
					deleteProp={deleteProp}
					value={value}
					inheritVal={inheritVal}
					currMod={currMod}
					updatePath={updatePath}
					setGalleryOpen={setGalleryOpen}
					setCurrentFile={setCurrentFile}
					setOnGalleryChange={setOnGalleryChange}
				/>
			);
		}

		if (prop.type === 'color') {
			return (
				<CtrlColor
					key={currMod.id + updatePath}
					prop={prop}
					updateProps={updateProps}
					deleteProp={deleteProp}
					value={value}
					inheritVal={inheritVal}
					currMod={currMod}
					updatePath={updatePath}
				/>
			);
		}

		if (prop.type === 'wysiwyg') {
			return (
				<CtrlWysiwyg
					key={currMod.id + updatePath}
					prop={prop as PropWysiwyg}
					updateProps={updateProps}
					deleteProp={deleteProp}
					value={value}
					inheritVal={inheritVal}
					currMod={currMod}
					updatePath={updatePath}
					saveShortcut={savePage}
				/>
			);
		}

		if (prop.type === 'url') {
			return (
				<CtrlUrl
					key={currMod.id + updatePath}
					prop={prop as PropUrl}
					updateProps={updateProps}
					deleteProp={deleteProp}
					value={value}
					inheritVal={inheritVal}
					currMod={currMod}
					updatePath={updatePath}
				/>
			);
		}

		return (
			<CtrlText
				key={currMod.id + updatePath}
				prop={prop}
				updateProps={updateProps}
				deleteProp={deleteProp}
				value={value}
				inheritVal={inheritVal}
				currMod={currMod}
				updatePath={updatePath}
			/>
		);
	}

	// #endregion

	// #region Save Page Functions

	function recursiveSwapModuleToName(item: BuilderModule) {
		let children: any = [];

		if (item.props?.children) {
			item.props.children.forEach((child: BuilderModule) =>
				children.push(recursiveSwapModuleToName(child))
			);
		}

		if (item.module) {
			return {
				id: item.id,
				module: item.module.name,
				props: {
					...item.props,
					children: children.length > 0 ? children : undefined,
				},
			};
		}

		return item;
	}

	async function savePage() {
		if (!canSave) {
			console.warn('Cannot save page');
			return;
		}

		setIsSaving(true);

		let savePage = page.map((item) => {
			return recursiveSwapModuleToName(item);
		});

		clearEmpties(savePage);

		try {
			await PROVIDER.savePage(pageTitle, savePage, setPageId, pageId);

			setInitTitle(pageTitle);
			setMadeChanges(false);
		} catch (e) {
			console.error(e);
		}

		setIsSaving(false);
	}

	// #endregion

	// #region Module Actions

	function moveModuleUp(id: string) {
		const newPage = [...page];

		function recursiveMoveUp(id: string, item: any, parentItem?: any) {
			if (item.id === id) {
				if (parentItem) {
					const index = parentItem.props.children.indexOf(item);
					if (index > 0) {
						parentItem.props.children.splice(index, 1);
						parentItem.props.children.splice(index - 1, 0, item);
					}
				} else {
					const index = newPage.indexOf(item);
					if (index > 0) {
						newPage.splice(index, 1);
						newPage.splice(index - 1, 0, item);
					}
				}
				return true;
			}

			if (item.props?.children) {
				item.props.children.some((child: any) =>
					recursiveMoveUp(id, child, item)
				);
			}

			return false;
		}

		newPage.some((item) => recursiveMoveUp(id, item));

		setMadeChanges(true);
		setCurrModIndex(currModIndex - 1);
		setPage(newPage);
	}

	function moveModuleDown(id: string) {
		const newPage = [...page];

		function recursiveMoveDown(id: string, item: any, parentItem?: any) {
			if (item.id === id) {
				if (parentItem) {
					const index = parentItem.props.children.indexOf(item);
					if (index > -1) {
						parentItem.props.children.splice(index, 1);
						parentItem.props.children.splice(index + 1, 0, item);
					}
				} else {
					const index = newPage.indexOf(item);
					if (index > -1) {
						newPage.splice(index, 1);
						newPage.splice(index + 1, 0, item);
					}
				}

				return true;
			}

			if (item.props?.children) {
				item.props.children.some((child: any) =>
					recursiveMoveDown(id, child, item)
				);
			}

			return false;
		}

		newPage.some((item) => recursiveMoveDown(id, item));

		setMadeChanges(true);
		setCurrModIndex(currModIndex + 1);
		setPage(newPage);
	}

	function deselectModule() {
		setCurrParentMod(null);
		setCurrMod(null);
		setCurrModIndex(0);
		setInspectorOpen(false);
	}

	function updateCurrBob(currMod: BuilderModule | null) {
		if (
			!currMod ||
			!currMod.module.ref ||
			!currMod.module.ref.current ||
			!iframeRef.current
		) {
			setCurrBob(null);
			return;
		}

		const coords = getCoords(currMod.module.ref.current);
		const scrollY = iframeRef.current.contentWindow.document.querySelector(
			'.simplebar-content-wrapper'
		).scrollTop;

		setCurrBob({
			x: coords.left,
			y: coords.top + scrollY,
			width: currMod.module.ref.current.offsetWidth,
			height: currMod.module.ref.current.offsetHeight,
		});
	}

	useEffect(() => {
		if (iframeRef.current && iframeRef.current.contentWindow) {
			iframeRef.current.contentWindow.onresize = () => {
				updateCurrBob(currMod);
			};
		}

		return () => {
			if (iframeRef.current && iframeRef.current.contentWindow) {
				iframeRef.current.contentWindow.onresize = null;
			}
		};
	}, [currMod, iframeRef.current]);

	useEffect(() => {
		window.onresize = () => {
			updateCurrBob(currMod);
		};

		return () => {
			window.onresize = null;
		};
	}, [currMod]);

	useEffect(() => {
		updateCurrBob(currMod);
	}, [currMod, page]);

	function reorderModules(items: FlattenedItem[]) {
		const flattenedNewPage = flattenTree(page);

		let newParentId: string | null = null;
		let newParent: BuilderModule | null = null;

		const newItems = items.map((item) => {
			const foundItem = flattenedNewPage.find((i) => i.id === item.id);
			if (foundItem) {
				newParentId = item.parentId;
				return { ...foundItem, parentId: item.parentId };
			}
			return item;
		});

		const newPage = buildTree(newItems as FlattenedItem[]);

		if (newParentId) {
			newParent = getModuleFromId(newParentId, newPage) as BuilderModule;
		}

		setMadeChanges(true);
		setPage(newPage);

		if (currMod) {
			setCurrParentMod(newParent);
			setCurrModIndex(getModuleIndex(currMod, newPage, newParent));
		}
	}

	function deleteModule(
		id: string,
		e?: React.MouseEvent<HTMLDivElement, MouseEvent>
	) {
		if (e) {
			e.stopPropagation();
		}

		let newPage = [...page];

		function recursiveDelete(id: string, parentItem: any, item: any) {
			if (item.id === id) {
				if (parentItem) {
					const index = parentItem.props.children.indexOf(item);
					parentItem.props.children.splice(index, 1);
					return;
				}

				const index = newPage.indexOf(item);
				newPage.splice(index, 1);

				return;
			}

			if (item.props?.children) {
				item.props.children.forEach((child: any) =>
					recursiveDelete(id, item, child)
				);
			}
		}

		newPage.forEach((item) => recursiveDelete(id, null, item));

		deselectModule();
		setMadeChanges(true);
		setPage(newPage);
	}

	function getModuleFromId(id: string, nPage: any[] = page) {
		const flattenedPage = flattenTree(nPage);

		return flattenedPage.find((item) => item.id === id);
	}

	function duplicateModule(
		id: string,
		e?: React.MouseEvent<HTMLDivElement, MouseEvent>
	) {
		if (e) {
			e.stopPropagation();
		}

		let newPage = [...page];

		function recursiveNewId(item: any) {
			item.id = 'f' + uuidv4();

			if (item.props?.children) {
				item.props.children.forEach((child: any) => recursiveNewId(child));
			}
		}

		function recursiveDuplicate(id: string, parentItem: any, item: any) {
			if (item.id === id) {
				const newItem = clone(item);
				newItem.id = 'f' + uuidv4();

				if (newItem.props?.children) {
					newItem.props.children = newItem.props.children.map((child: any) => {
						recursiveNewId(child);
						return child;
					});
				}

				if (parentItem) {
					const index = parentItem.props.children.indexOf(item);
					parentItem.props.children.splice(index + 1, 0, newItem);
					return;
				}

				const index = newPage.indexOf(item);
				newPage.splice(index + 1, 0, newItem);

				return;
			}

			if (item.props?.children) {
				item.props.children.forEach((child: any) =>
					recursiveDuplicate(id, item, child)
				);
			}
		}

		newPage.forEach((item) => recursiveDuplicate(id, null, item));

		setMadeChanges(true);
		setPage(newPage);
	}

	function copyModule(
		id: string,
		e?: React.MouseEvent<HTMLDivElement, MouseEvent>
	) {
		if (e) {
			e.stopPropagation();
		}

		const module = getModuleFromId(id);

		if (module) {
			const newModule = clearEmpties(recursiveSwapModuleToName(module));

			navigator.clipboard.writeText(JSON.stringify(newModule));
		}
	}

	function pasteModule(
		id: string,
		e?: React.MouseEvent<HTMLDivElement, MouseEvent>
	) {
		if (e) {
			e.stopPropagation();
		}

		navigator.clipboard.readText().then((text) => {
			// confirm text is a valid JSON string and has a module name
			if (!text || !text.includes('module') || !text.includes('id')) {
				return;
			}

			let textObj: any;

			try {
				textObj = JSON.parse(text);
			} catch (err) {
				return;
			}

			let newPage = [...page];

			let newModule = recursiveSwapNameToModule(textObj);

			function recursiveNewId(item: any) {
				item.id = 'f' + uuidv4();

				if (item.props?.children) {
					item.props.children.forEach((child: any) => recursiveNewId(child));
				}

				return item;
			}

			newModule = recursiveNewId(newModule);

			let newParentItem: any = null;
			let newIndex: number | undefined;

			function recursivePaste(id: string, parentItem: any, item: any) {
				if (item.id === id) {
					if (item.module.options.children) {
						// if the module has children, add the new module to the end of the children
						item.props.children.push(newModule);
						newParentItem = item;
						newIndex = item.props.children.length - 1;
						return;
					}

					if (parentItem) {
						const index = parentItem.props.children.indexOf(item);
						parentItem.props.children.splice(index + 1, 0, newModule);
						newParentItem = parentItem;
						newIndex = index + 1;
						return;
					}

					const index = newPage.indexOf(item);
					newPage.splice(index + 1, 0, newModule);

					return;
				}

				if (item.props?.children) {
					item.props.children.forEach((child: any) =>
						recursivePaste(id, item, child)
					);
				}

				return;
			}

			newPage.forEach((item) => recursivePaste(id, null, item));

			setMadeChanges(true);
			setPage(newPage);

			selectModule(newModule, newIndex, newParentItem);
		});
	}

	// #endregion

	const [bob, setBob] = useState<{
		x: number;
		y: number;
		width: number;
		height: number;
	} | null>({
		x: 0,
		y: 0,
		width: 0,
		height: 0,
	});

	const [currBob, setCurrBob] = useState<{
		x: number;
		y: number;
		width: number;
		height: number;
	} | null>({
		x: 0,
		y: 0,
		width: 0,
		height: 0,
	});

	function renderModule(mod: BuilderModule, index: number, parentMod?: any) {
		return (
			<mod.module.Component
				inEditor={true}
				id={mod.id}
				ref={mod.module.ref}
				key={mod.id}
				wrapperProps={{
					onClick: (e: MouseEvent) => {
						e.stopPropagation();
						if (isPreviewing) return;
						hideContextMenu();
						selectModule(mod, index, parentMod);
					},
					onContextMenu: (e: MouseEvent) => {
						e.stopPropagation();
						if (isPreviewing) return;
						selectModule(mod, index, parentMod);
						showContextMenu({
							event: e,
							position: {
								y: e.clientY + 56,
								x: e.clientX + (inspectorWidth ?? 0),
							},
						});
					},
					onMouseOver: (e: MouseEvent) => {
						e.stopPropagation();

						if (isPreviewing) return;

						if (!iframeRef.current) return;

						const target = e.currentTarget as HTMLElement;

						const targetCoords = getCoords(target);
						const scrollY =
							iframeRef.current.contentWindow.document.querySelector(
								'.simplebar-content-wrapper'
							).scrollTop;

						if (
							bob &&
							bob.x === targetCoords.left &&
							bob.y === targetCoords.top
						) {
							return;
						}

						setBob({
							x: targetCoords.left,
							y: targetCoords.top + scrollY,
							width: target.offsetWidth,
							height: target.offsetHeight,
						});
					},
				}}
				{...(mod.props ?? {})}
				renderedChildren={
					mod.props?.children
						? mod.props.children.map((child: any, index: number) => {
								return renderModule(child, index, mod);
						  })
						: undefined
				}
			/>
		);
	}

	const [galleryOpen, setGalleryOpen] = useState<string[]>([]);
	const [currentFile, setCurrentFile] = useState(null);
	const [onGalleryChange, setOnGalleryChange] = useState<(file: any) => void>(
		() => null
	);

	const [closedCategories, setCloseCategories] = useState<string[]>(
		defaultClosedCategories
	);
	const [navigatorOpen, setNavigatorOpen] = useState(defaultNavigatorOpen);
	const [navigatorWidth, setNavigatorWidth] = useState(defaultNavigatorWidth);
	const [inspectorWidth, setInspectorWidth] = useState(defaultInspectorWidth);

	const [openedPropGroup, setOpenedPropGroup] = useState<number>(0);
	const [currPropCategory, setCurrPropCategory] = useState<number>(0);

	function selectPropGroup(index: number) {
		if (openedPropGroup === index) {
			setOpenedPropGroup(-1);
			return;
		}
		setOpenedPropGroup(index);
	}
	function selectPropCategory(index: number) {
		setOpenedPropGroup(0);
		setCurrPropCategory(index);
	}

	function onInspectorResize(
		e: MouseEvent | TouchEvent,
		direction: Direction,
		inspectorRef: HTMLElement,
		d: NumberSize
	) {
		localStorage.setItem(
			defaultInspectorWidthItem,
			`${inspectorRef.clientWidth}`
		);
		setInspectorWidth(inspectorRef.clientWidth ?? 0);
		updateCurrBob(currMod);
	}

	function onNavigatorResize(
		e: MouseEvent | TouchEvent,
		direction: Direction,
		navigatorRef: HTMLElement,
		d: NumberSize
	) {
		localStorage.setItem(
			defaultNavigatorWidthItem,
			`${navigatorRef.clientWidth}`
		);
		setNavigatorWidth(navigatorRef.clientWidth ?? 0);
		updateCurrBob(currMod);
	}

	function onCategoryLabelClick(cat: any) {
		setCloseCategories((cats) => {
			if (cats.includes(cat.categoryLabel)) {
				const newCats = cats.filter((c) => c !== cat.categoryLabel);
				localStorage.setItem(
					defaultClosedCategoriesItem,
					JSON.stringify(newCats)
				);
				return newCats;
			}

			const newCats = [...cats, cat.categoryLabel];
			localStorage.setItem(
				defaultClosedCategoriesItem,
				JSON.stringify(newCats)
			);
			return newCats;
		});
	}

	function toggleNavigator() {
		localStorage.setItem(defaultNavigatorOpenItem, `${!navigatorOpen}`);
		setNavigatorOpen(!navigatorOpen);
	}

	function renderInspector() {
		return (
			<Resizable
				defaultSize={{
					width: defaultInspectorWidth,
					height: '100vh',
				}}
				size={{ width: inspectorWidth, height: '100vh' }}
				style={{
					position: 'fixed',
					top: 0,
					left: 0,
					zIndex: 1001,
				}}
				onResize={onInspectorResize}
				minWidth={150}
				maxWidth={1000}
				enable={{
					top: false,
					right: true,
					bottom: false,
					left: false,
					topRight: false,
					bottomRight: false,
					bottomLeft: false,
					topLeft: false,
				}}
			>
				<div
					className='nitrogen-panel'
					id='nitrogen-inspector'
				>
					<div className='nitrogen-panel__header'>
						{currMod !== null || settingsOpen ? (
							<div
								className='nitrogen-panel__header__button'
								onClick={() =>
									settingsOpen
										? setSettingsOpen(false)
										: setInspectorOpen(!inspectorOpen)
								}
							>
								<FontAwesomeIcon
									icon={
										settingsOpen
											? !inspectorOpen
												? faGrid
												: faEdit
											: inspectorOpen
											? faGrid
											: faEdit
									}
								/>
							</div>
						) : (
							<div className='nitrogen-panel__header__empty-and-lonely-bob'></div>
						)}
						<div className='nitrogen-panel__header__title'>
							{currMod && inspectorOpen
								? `Edit ${currMod.module.name}`
								: 'Nitrogen'}
						</div>
						<div className='nitrogen-panel__header__empty-and-lonely-bob'></div>
					</div>
					<SimpleBar
						style={{ maxHeight: 'calc(100vh - 88px)' }}
						className='nitrogen-panel__content'
					>
						{settingsOpen ? (
							<div className='nitrogen-panel__settings'>
								{config.editorUrl && (
									<a
										className='nitrogen-panel__settings__btn'
										href={config.editorUrl(pageId)}
									>
										<i className='far fa-person-to-portal' />
										Back to Dashboard
									</a>
								)}
							</div>
						) : currMod !== null && inspectorOpen ? (
							<div className='nitrogen-panel__inspector'>
								<div className={`nitrogen-panel__inspector__menu`}>
									{currMod.module.props.map((cat, index) => {
										return (
											<div
												key={(cat.label ?? 'cat-') + index}
												className={`nitrogen-panel__inspector__menu__button ${
													currPropCategory === index
														? 'nitrogen-panel__inspector__menu__button--active '
														: ''
												}`}
												onClick={() => selectPropCategory(index)}
											>
												<div
													className={`nitrogen-panel__inspector__menu__button__content ${
														currPropCategory === index
															? 'nitrogen-panel__inspector__menu__button__content--active'
															: ''
													}`}
												></div>
												<div className='nitrogen-panel__inspector__menu__button__content__text'>
													{cat?.icon && (
														<i
															className={`nitrogen-panel__inspector__menu__button__content__text__icon ${cat.icon} fa-fw`}
														/>
													)}
													{cat.label}
												</div>
											</div>
										);
									})}
								</div>
								{currMod.module.props[currPropCategory]
									? currMod.module.props[currPropCategory].groups.map(
											(group, index) => {
												return (
													<div
														key={(group.label ?? 'group-') + index}
														className='nitrogen-panel__inspector__section'
													>
														<div
															className='nitrogen-panel__inspector__section__header'
															onClick={() => selectPropGroup(index)}
														>
															<i
																className={`nitrogen-panel__inspector__section__header__icon fa-solid fa-sharp fa-caret-right ${
																	openedPropGroup === index
																		? 'nitrogen-panel__inspector__section__header__icon--active'
																		: ''
																}`}
															/>
															{group.label}
														</div>
														<div
															className={`nitrogen-panel__inspector__section__content ${
																openedPropGroup === index
																	? ''
																	: 'nitrogen-panel__inspector__section__content--hidden'
															}`}
														>
															{Object.keys(group.props).map((key) => {
																const prop = group.props[key];

																let fieldTitle =
																	prop?.label ?? key.replace(/([A-Z])/g, ' $1');

																fieldTitle =
																	fieldTitle.charAt(0).toUpperCase() +
																	fieldTitle.slice(1);

																if (
																	prop.isVisible &&
																	prop.isVisible(currMod.props) === false
																) {
																	return;
																}

																return (
																	<div
																		key={key}
																		className={`nitrogen-panel__inspector__section__content__ctrl-option ${
																			prop?.divider
																				? 'nitrogen-panel__inspector__section__content__ctrl-option--divider'
																				: ''
																		}`}
																	>
																		<div className='nitrogen-panel__inspector__section__content__ctrl-option__label'>
																			{fieldTitle}{' '}
																			{prop?.responsive && (
																				<ResponsiveWidget
																					responsiveMode={responsiveMode}
																					setResponsiveMode={setResponsiveMode}
																				/>
																			)}
																		</div>
																		{renderPropInput(prop, key)}
																	</div>
																);
															})}
														</div>
													</div>
												);
											}
									  )
									: 'This module has no properties.'}
								<div className='nitrogen-panel__inspector__mod-id'>
									Module ID: <span>{currMod.id}</span>
								</div>
							</div>
						) : (
							<div className='nitrogen-panel__content__default'>
								<div className='nitrogen-panel__content__default__input-group'>
									<input
										type='text'
										name='component-search'
										placeholder='Search modules...'
										className='nitrogen-panel__content__default__search-modules'
										value={modsSearch}
										onChange={handleModSearch}
									/>
									{modsSearch.length > 0 && (
										<button
											type='button'
											name='component-search-clear'
											className='nitrogen-panel__content__default__clear-search'
											onClick={clearModSearch}
										>
											X
										</button>
									)}
								</div>
								{(modsSearch.length > 0 || fuseResults.length > 0
									? fuseResults
									: mods
								)
									.reduce((acc: any[], modM: any) => {
										const mod = fuseResults.length > 0 ? modM.item : modM;

										const category = mod.options.category; // we know this exists because we set it if not exists when we load in module

										let catObj = acc.find((m) => m.categoryLabel === category);

										if (!catObj) {
											catObj = {
												categoryLabel: category,
												modules: [modM],
											};

											acc.push(catObj);

											return acc;
										}

										catObj.modules.push(modM);

										return acc;
									}, [])
									.sort((a, b) => {
										if (a.categoryLabel < b.categoryLabel) {
											return -1;
										}
										if (a.categoryLabel > b.categoryLabel) {
											return 1;
										}

										return 0;
									})
									.map((cat) => {
										return (
											<React.Fragment key={cat.categoryLabel}>
												<div
													className='nitrogen-panel__content__default__section-header'
													onClick={() => onCategoryLabelClick(cat)}
												>
													{cat.categoryLabel}
													<div>
														<i
															className={`nitrogen-panel__content__default__section-header__icon fa-solid fa-sharp fa-caret-right ${
																closedCategories.includes(cat.categoryLabel)
																	? ''
																	: 'nitrogen-panel__content__default__section-header__icon--active'
															}`}
														/>
													</div>
												</div>
												<CardsContainer
													closedCategories={closedCategories}
													cat={cat}
													fuseResults={fuseResults}
													addModule={addModule}
												/>
											</React.Fragment>
										);
									})}
								{modsSearch.length > 0 && fuseResults.length === 0 && (
									<div className='nitrogen-panel__content__default__search-no-results'>
										No results found.
									</div>
								)}
							</div>
						)}
					</SimpleBar>
					<div className='nitrogen-panel__footer'>
						<div
							className='nitrogen-panel__footer__btn nitrogen-panel__footer__btn--settings'
							onClick={() => setSettingsOpen(!settingsOpen)}
							title='Page Settings'
						>
							<i className='fa-solid fa-cog' />
						</div>
						<ResponsiveWidget
							responsiveMode={responsiveMode}
							setResponsiveMode={setResponsiveMode}
							expandDirection='up'
						/>
						<div
							className='nitrogen-panel__footer__btn nitrogen-panel__footer__btn--preview'
							onClick={() => setIsPreviewing(!isPreviewing)}
							title='Page Preview'
						>
							<i className='fa-solid fa-eye' />
						</div>
						<div
							className='nitrogen-panel__footer__btn'
							onClick={toggleNavigator}
							title='Navigator'
						>
							<i className='fa-solid fa-layer-group' />
						</div>
						<button
							className={`nitrogen-panel__footer__save-btn ${
								canSave ? '' : 'nitrogen-panel__footer__save-btn--disabled'
							}`}
							onClick={canSave && !isSaving ? savePage : () => {}}
							disabled={!canSave}
						>
							<FontAwesomeIcon
								icon={faSpinnerThird}
								spin={true}
								className={`nitrogen-panel__footer__save-btn__spinner ${
									isSaving
										? 'nitrogen-panel__footer__save-btn__spinner--saving'
										: ''
								}`}
							/>
							SAVE
						</button>
					</div>
				</div>
			</Resizable>
		);
	}

	// TODO: 'Page' should be a prop in the "Editing Page" string

	return (
		<div
			className='nitrogen-screen'
			style={{
				paddingLeft: inspectorWidth,
				paddingRight: navigatorOpen ? navigatorWidth : 0,
			}}
			onMouseOver={(e) => {
				setBob(null);
			}}
		>
			{isPreviewing && (
				<div className={`nitrogen-screen__preview-popup`}>
					Press <kbd>ESC</kbd> to exit preview mode
				</div>
			)}
			{renderInspector()}
			<div
				className={`nitrogen-screen__page-container`}
				onClick={deselectModule}
			>
				<div
					className='nitrogen-screen__page-container__top-bar'
					style={{
						left: inspectorWidth,
						maxWidth: `calc(100% - ${
							inspectorWidth + (navigatorOpen ? navigatorWidth : 0)
						}px)`,
					}}
					onClick={(e) => e.stopPropagation()}
				>
					<ResponsiveWidget
						responsiveMode={responsiveMode}
						setResponsiveMode={setResponsiveMode}
						expanded
						backgroundClass='responsive-widget-background'
					/>
					<div className='page-title-wrapper'>
						<span>Editing Page:</span>
						<input
							value={pageTitle}
							onChange={(e) => setPageTitle(e.target.value)}
							type='text'
							name='page-title'
							className='nitrogen-screen__page-container__top-bar__page-title'
							placeholder='Page Title'
						/>
					</div>
				</div>
				<SimpleBar
					style={isPreviewing ? {} : { maxHeight: `calc(100vh - 36px)` }}
					className={`nitrogen-screen__page-container__simple-bar ${
						responsiveMode === 'desktop'
							? 'nitrogen-screen__page-container__simple-bar--desktop'
							: ''
					} ${
						responsiveMode === 'laptop'
							? 'nitrogen-screen__page-container__simple-bar--laptop'
							: ''
					} ${
						responsiveMode === 'tablet'
							? 'nitrogen-screen__page-container__simple-bar--tablet'
							: ''
					} ${
						responsiveMode === 'mobileLandscape'
							? 'nitrogen-screen__page-container__simple-bar--mobile-landscape'
							: ''
					} ${
						responsiveMode === 'mobile'
							? 'nitrogen-screen__page-container__simple-bar--mobile-portrait'
							: ''
					} ${
						isPreviewing
							? 'nitrogen-screen__page-container__simple-bar--is-previewing'
							: ''
					}`}
				>
					<div
						className={`i-frame-container ${
							responsiveMode === 'desktop' ? 'i-frame-container--desktop' : ''
						} ${
							responsiveMode === 'laptop' ? 'i-frame-container--laptop' : ''
						} ${
							responsiveMode === 'tablet' ? 'i-frame-container--tablet' : ''
						} ${
							responsiveMode === 'mobileLandscape'
								? 'i-frame-container--mobile-landscape'
								: ''
						} ${
							responsiveMode === 'mobile'
								? 'i-frame-container--mobile-portrait'
								: ''
						}`}
					>
						<IFrame
							ref={iframeRef}
							inspectorWidth={inspectorWidth}
							onClick={hideContextMenu}
						>
							<SimpleBar
								className={`bob-container ${
									isPreviewing ? 'is-previewing' : ''
								}`}
							>
								{bob && !isPreviewing && (
									<div
										className={`simplebar-content__bob-container`}
										style={{
											top: bob.y,
											left: bob.x,
											width: bob.width,
											height: bob.height,
										}}
									>
										<div
											className={`simplebar-content__bob-container__bob`}
										></div>
									</div>
								)}
								{currBob && !isPreviewing && (
									<div
										className={`simplebar-content__bob-container`}
										style={{
											top: currBob.y,
											left: currBob.x,
											width: currBob.width,
											height: currBob.height,
										}}
									>
										<div
											className={`simplebar-content__bob-container__curr-bob`}
										></div>
									</div>
								)}
								{currMod && currBob && !isPreviewing && (
									<div
										className={`simplebar-content__bob-container__buttons`}
										onClick={(e) => e.stopPropagation()}
										style={{
											top: currBob.y,
											left: currBob.x,
											height: currBob.height,
										}}
									>
										<div className='simplebar-content__bob-container__buttons__container'>
											{currModIndex > 0 && (
												<div
													className={`simplebar-content__bob-container__buttons__container__button`}
													onClick={() => moveModuleUp(currMod.id)}
												>
													<div className='simplebar-content__bob-container__buttons__container__button__inside'></div>
													<FontAwesomeIcon
														className='relative'
														icon={faCaretUp}
													/>
												</div>
											)}
											{(currParentMod
												? currParentMod &&
												  currParentMod.props.children &&
												  currModIndex < currParentMod.props.children.length - 1
												: currModIndex < page.length - 1) && (
												<div
													className={`simplebar-content__bob-container__buttons__container__button ${
														currModIndex === 0 ? '' : ''
													}`}
													onClick={() => moveModuleDown(currMod.id)}
												>
													<div className='simplebar-content__bob-container__buttons__container__button__inside'></div>
													<FontAwesomeIcon
														className='relative'
														icon={faCaretDown}
													/>
												</div>
											)}
											<div
												className={`simplebar-content__bob-container__buttons__container__button`}
												onClick={(e) => duplicateModule(currMod.id, e)}
											>
												<div className='simplebar-content__bob-container__buttons__container__button__inside'></div>
												<FontAwesomeIcon
													className='relative'
													icon={faClone}
												/>
											</div>
											<div
												className={`simplebar-content__bob-container__buttons__container__button`}
												onClick={(e) => deleteModule(currMod.id, e)}
											>
												<div className='simplebar-content__bob-container__buttons__container__button__inside'></div>
												<FontAwesomeIcon
													className='relative'
													icon={faTimes}
												/>
											</div>
										</div>
									</div>
								)}
								{page.map((mod, index) => {
									return renderModule(mod, index);
								})}
							</SimpleBar>
						</IFrame>
					</div>
				</SimpleBar>
			</div>
			{galleryOpen.length > 0 && (
				<Gallery
					currentFile={currentFile}
					setCurrentFile={onGalleryChange}
					close={() => setGalleryOpen([])}
					fileTypes={galleryOpen}
				/>
			)}
			{navigatorOpen && (
				<Navigator
					modules={page}
					defaultWidth={defaultNavigatorWidth}
					width={navigatorWidth}
					onResize={onNavigatorResize}
					currModId={currMod?.id ?? ''}
					selectModule={(id, e) => {
						if (id && id.length > 4) {
							const mod = getModuleFromId(id.substring(4));
							if (mod) {
								selectModule(mod);
							}
						}
					}}
					deleteModule={deleteModule}
					reorderModules={reorderModules}
					toggleNavigator={toggleNavigator}
				/>
			)}
			<Menu id={CONTEXT_MENU_ID}>
				{currMod ? (
					<>
						<Item
							onClick={({ event, props, triggerEvent, data }) => {
								if (!currMod) return;
								hideContextMenu();
								moveModuleUp(currMod.id);
							}}
						>
							<span className='contexify-item-span'>Move Up</span>{' '}
							<FontAwesomeIcon icon={faArrowUp} />
						</Item>
						<Item
							onClick={({ event, props, triggerEvent, data }) => {
								if (!currMod) return;
								hideContextMenu();
								moveModuleDown(currMod.id);
							}}
						>
							<span className='contexify-item-span'>Move Down</span>{' '}
							<FontAwesomeIcon icon={faArrowDown} />
						</Item>
						<Separator />
						<Item
							onClick={({ event, props, triggerEvent, data }) => {
								if (!currMod) return;
								hideContextMenu();
								copyModule(currMod.id);
							}}
						>
							<span className='contexify-item-span'>Copy</span>{' '}
							<FontAwesomeIcon icon={faCopy} />
						</Item>
						<Item
							onClick={({ event, props, triggerEvent, data }) => {
								if (!currMod) return;
								hideContextMenu();
								pasteModule(currMod.id);
							}}
						>
							<span className='contexify-item-span'>Paste</span>{' '}
							<FontAwesomeIcon icon={faPaste} />
						</Item>
						<Separator />
						<Item
							onClick={({ event, props, triggerEvent, data }) => {
								if (!currMod) return;
								hideContextMenu();
								duplicateModule(currMod.id);
							}}
						>
							<span className='contexify-item-span'>Duplicate</span>{' '}
							<FontAwesomeIcon icon={faClone} />
						</Item>
						<Separator />
						<Item
							onClick={({ event, props, triggerEvent, data }) => {
								if (!currMod) return;
								hideContextMenu();
								deleteModule(currMod.id);
							}}
						>
							<span className='contexify-item-span'>Delete</span>{' '}
							<FontAwesomeIcon icon={faTrashAlt} />
						</Item>
					</>
				) : (
					<></>
				)}

				{/* <Item
					onClick={({ event, props, triggerEvent, data }) => {
						console.log(event, props, triggerEvent, data);
					}}
				>
					Item 2
				</Item>
				<Separator />
				<Item disabled>Disabled</Item>
				<Separator />
				<Submenu label='Submenu'>
					<Item
						onClick={({ event, props, triggerEvent, data }) => {
							console.log(event, props, triggerEvent, data);
						}}
					>
						Sub Item 1
					</Item>
					<Item
						onClick={({ event, props, triggerEvent, data }) => {
							console.log(event, props, triggerEvent, data);
						}}
					>
						Sub Item 2
					</Item>
				</Submenu> */}
			</Menu>
		</div>
	);
}

export default App;
