import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from "react";
import ReactDOM from "react-dom";
import {v4 as uuidv4} from "uuid";

/**
 * Curtain UUID type.
 */
export type CurtainUuidType = string;

/**
 * The function that opens a curtain.
 */
export type OpenCurtainFunction = (content: React.ReactNode) => CurtainUuidType;

/**
 * The function that closes a curtain.
 */
export type CloseCurtainFunction = (uuid: CurtainUuidType) => void;

/**
 * Interface of curtains state.
 */
export interface CurtainsContextState
{
	/**
	 * Open a new curtain.
	 * @param content The curtain content.
	 * @return UUID of the curtain.
	 */
	open: OpenCurtainFunction;

	/**
	 * Close the given curtain.
	 * @param uuid UUID of the curtain to close.
	 */
	close: CloseCurtainFunction;
}

const CurtainsContext = React.createContext<CurtainsContextState>({
	// Empty functions.
	open() { return ""; },
	close() {},
});

/**
 * Hook to interact with curtains (open or close).
 */
export function useCurtains(): CurtainsContextState
{
	return useContext(CurtainsContext);
}

/**
 * Page curtains system.
 */
export function Curtains({children}: React.PropsWithChildren<{}>)
{
	// Curtains state.
	const [curtains, setCurtains] = useState<Record<CurtainUuidType, React.ReactNode>>({});

	// Initialize open curtain function.
	const open = useRef<OpenCurtainFunction>();
	open.current = useCallback((content: React.ReactNode) => {
		// Generate a new curtain UUID for the new curtain to open.
		const curtainUuid = uuidv4();

		// Add the curtain to open to the list of curtains, with the generated UUID.
		setCurtains({
			...curtains,
			[curtainUuid]: content,
		});

		// Return the curtain UUID.
		return curtainUuid;
	}, [curtains, setCurtains]);

	// Initialize close curtain function.
	const close = useRef<CloseCurtainFunction>();
	close.current = useCallback((uuid: CurtainUuidType) => {
		// Copy the curtains list.
		const newCurtains = {...curtains};
		// Remove the given curtain from the list.
		delete newCurtains[uuid];
		// Set the new curtains list.
		setCurtains(newCurtains);
	}, [curtains, setCurtains]);

	// Initialize context state from action functions.
	const contextState = useMemo(() => ({
		open: (content: React.ReactNode) => open.current(content),
		close: (uuid: CurtainUuidType) => close.current(uuid),
	}), [open, close]);

	// Show dimmed main content.
	useEffect(() => {
		if (Object.entries(curtains).length > 0 && !document.body.classList.contains("dimmed"))
			// We should dim content and it's currently not.
			document.body.classList.add("dimmed");
		else
			// We shouldn't dim content.
			document.body.classList.remove("dimmed");
	}, [curtains]);

	return (
		<CurtainsContext.Provider value={contextState}>
			{children}
			<CurtainsPortal curtains={curtains} />
		</CurtainsContext.Provider>
	);
}

/**
 * Curtains portal manager.
 */
function CurtainsPortal({curtains}: {
	curtains: Record<CurtainUuidType, React.ReactNode>;
})
{
	return ReactDOM.createPortal(Object.entries(curtains).map(([uuid, curtainContent]) => (
		<CurtainInstance key={uuid}>
			{curtainContent}
		</CurtainInstance>
	)), document.body);
}

/**
 * Component of an opened curtain instance.
 */
function CurtainInstance({children}: React.PropsWithChildren<{}>)
{
	return (
		<div className={"curtain"}>
			{children}
		</div>
	);
}
