"use client";

import { cn } from "@lib/utils";
import { Button } from "@repo/ui/components/button";
import isHotkey from "is-hotkey";
import {
	Bold,
	Code,
	Heading1,
	Heading2,
	Heading3,
	Italic,
	List,
	Quote,
} from "lucide-react";
import { useCallback, useMemo, useState } from "react";
import {
	type BaseEditor,
	createEditor,
	type Descendant,
	Editor,
	Transforms,
} from "slate";
import type { ReactEditor as ReactEditorType } from "slate-react";
import {
	Editable,
	ReactEditor,
	type RenderElementProps,
	type RenderLeafProps,
	Slate,
	withReact,
} from "slate-react";

type CustomEditor = BaseEditor & ReactEditorType;

type ParagraphElement = {
	type: "paragraph";
	children: CustomText[];
};

type HeadingElement = {
	type: "heading";
	level: number;
	children: CustomText[];
};

type ListItemElement = {
	type: "list-item";
	children: CustomText[];
};

type BlockQuoteElement = {
	type: "block-quote";
	children: CustomText[];
};

type CustomElement =
	| ParagraphElement
	| HeadingElement
	| ListItemElement
	| BlockQuoteElement;

type FormattedText = {
	text: string;
	bold?: true;
	italic?: true;
	code?: true;
};

type CustomText = FormattedText;

declare module "slate" {
	interface CustomTypes {
		Editor: CustomEditor;
		Element: CustomElement;
		Text: CustomText;
	}
}

// Hotkey mappings
const HOTKEYS: Record<string, keyof CustomText> = {
	"mod+b": "bold",
	"mod+i": "italic",
	"mod+`": "code",
};

interface TextEditorProps {
	value?: string;
	onChange?: (value: string) => void;
	onBlur?: () => void;
	placeholder?: string;
	disabled?: boolean;
	className?: string;
}

const initialValue: Descendant[] = [
	{
		type: "paragraph",
		children: [{ text: "" }],
	},
];

const serialize = (nodes: Descendant[]): string => {
	return nodes.map((n) => serializeNode(n)).join("\n");
};

const serializeNode = (node: CustomElement | CustomText): string => {
	if ("text" in node) {
		let text = node.text;
		if (node.bold) text = `**${text}**`;
		if (node.italic) text = `*${text}*`;
		if (node.code) text = `\`${text}\``;
		return text;
	}

	const children = node.children
		? node.children.map(serializeNode).join("")
		: "";

	switch (node.type) {
		case "paragraph":
			return children;
		case "heading":
			return `${"#".repeat(node.level || 1)} ${children}`;
		case "list-item":
			return `- ${children}`;
		case "block-quote":
			return `> ${children}`;
		default:
			return children;
	}
};

const deserialize = (text: string): Descendant[] => {
	if (!text.trim()) {
		return initialValue;
	}

	const lines = text.split("\n");
	const nodes: Descendant[] = [];

	for (const line of lines) {
		const trimmedLine = line.trim();

		if (trimmedLine.startsWith("# ")) {
			nodes.push({
				type: "heading",
				level: 1,
				children: [{ text: trimmedLine.slice(2) }],
			});
		} else if (trimmedLine.startsWith("## ")) {
			nodes.push({
				type: "heading",
				level: 2,
				children: [{ text: trimmedLine.slice(3) }],
			});
		} else if (trimmedLine.startsWith("### ")) {
			nodes.push({
				type: "heading",
				level: 3,
				children: [{ text: trimmedLine.slice(4) }],
			});
		} else if (trimmedLine.startsWith("- ")) {
			nodes.push({
				type: "list-item",
				children: [{ text: trimmedLine.slice(2) }],
			});
		} else if (trimmedLine.startsWith("> ")) {
			nodes.push({
				type: "block-quote",
				children: [{ text: trimmedLine.slice(2) }],
			});
		} else {
			nodes.push({
				type: "paragraph",
				children: [{ text: line }],
			});
		}
	}

	return nodes.length > 0 ? nodes : initialValue;
};

const isMarkActive = (editor: CustomEditor, format: keyof CustomText) => {
	const marks = Editor.marks(editor);
	return marks ? marks[format as keyof typeof marks] === true : false;
};

const toggleMark = (editor: CustomEditor, format: keyof CustomText) => {
	const isActive = isMarkActive(editor, format);

	if (isActive) {
		Editor.removeMark(editor, format);
	} else {
		Editor.addMark(editor, format, true);
	}

	// Focus back to editor after toggling
	ReactEditor.focus(editor);
};

const isBlockActive = (
	editor: CustomEditor,
	format: string,
	level?: number,
) => {
	const { selection } = editor;
	if (!selection) return false;

	const [match] = Array.from(
		Editor.nodes(editor, {
			at: Editor.unhangRange(editor, selection),
			match: (n) =>
				!Editor.isEditor(n) &&
				(n as CustomElement).type === format &&
				(level === undefined || (n as HeadingElement).level === level),
		}),
	);

	return !!match;
};

const toggleBlock = (editor: CustomEditor, format: string, level?: number) => {
	const isActive = isBlockActive(editor, format, level);
	const newProperties: any = {
		type: isActive ? "paragraph" : format,
	};

	if (format === "heading" && level && !isActive) {
		newProperties.level = level;
	}

	Transforms.setNodes(editor, newProperties);

	// Focus back to editor after toggling
	ReactEditor.focus(editor);
};

export function TextEditor({
	value = "",
	onChange,
	onBlur,
	placeholder = "Start writing...",
	disabled = false,
	className,
}: TextEditorProps) {
	const editor = useMemo(() => withReact(createEditor()) as CustomEditor, []);
	const [editorValue, setEditorValue] = useState<Descendant[]>(() =>
		deserialize(value),
	);
	const [selection, setSelection] = useState(editor.selection);

	const renderElement = useCallback((props: RenderElementProps) => {
		switch (props.element.type) {
			case "heading": {
				const element = props.element as HeadingElement;
				const HeadingTag = `h${element.level || 1}` as
					| "h1"
					| "h2"
					| "h3"
					| "h4"
					| "h5"
					| "h6";
				return (
					<HeadingTag
						{...props.attributes}
						className={cn(
							"font-bold",
							element.level === 1 && "text-2xl mb-4",
							element.level === 2 && "text-xl mb-3",
							element.level === 3 && "text-lg mb-2",
						)}
					>
						{props.children}
					</HeadingTag>
				);
			}
			case "list-item":
				return (
					<li {...props.attributes} className="ml-4 list-disc">
						{props.children}
					</li>
				);
			case "block-quote":
				return (
					<blockquote
						{...props.attributes}
						className="border-l-4 border-white/20 pl-4 italic text-white/80"
					>
						{props.children}
					</blockquote>
				);
			default:
				return (
					<p {...props.attributes} className="mb-2">
						{props.children}
					</p>
				);
		}
	}, []);

	const renderLeaf = useCallback((props: RenderLeafProps) => {
		let { attributes, children, leaf } = props;

		if (leaf.bold) {
			children = <strong>{children}</strong>;
		}

		if (leaf.italic) {
			children = <em>{children}</em>;
		}

		if (leaf.code) {
			children = (
				<code className="bg-white/10 px-1 rounded text-sm">{children}</code>
			);
		}

		return <span {...attributes}>{children}</span>;
	}, []);

	const handleKeyDown = useCallback(
		(event: React.KeyboardEvent) => {
			// Handle hotkeys for formatting
			for (const hotkey in HOTKEYS) {
				if (isHotkey(hotkey, event)) {
					event.preventDefault();
					const mark = HOTKEYS[hotkey];
					if (mark) {
						toggleMark(editor, mark);
					}
					return;
				}
			}

			// Handle block formatting hotkeys
			if (isHotkey("mod+shift+1", event)) {
				event.preventDefault();
				toggleBlock(editor, "heading", 1);
				return;
			}
			if (isHotkey("mod+shift+2", event)) {
				event.preventDefault();
				toggleBlock(editor, "heading", 2);
				return;
			}
			if (isHotkey("mod+shift+3", event)) {
				event.preventDefault();
				toggleBlock(editor, "heading", 3);
				return;
			}
			if (isHotkey("mod+shift+8", event)) {
				event.preventDefault();
				toggleBlock(editor, "list-item");
				return;
			}
			if (isHotkey("mod+shift+.", event)) {
				event.preventDefault();
				toggleBlock(editor, "block-quote");
				return;
			}
		},
		[editor],
	);

	const handleSlateChange = useCallback(
		(newValue: Descendant[]) => {
			setEditorValue(newValue);
			const serializedValue = serialize(newValue);
			onChange?.(serializedValue);
		},
		[onChange],
	);

	// Memoized active states that update when selection changes
	const activeStates = useMemo(
		() => ({
			bold: isMarkActive(editor, "bold"),
			italic: isMarkActive(editor, "italic"),
			code: isMarkActive(editor, "code"),
			heading1: isBlockActive(editor, "heading", 1),
			heading2: isBlockActive(editor, "heading", 2),
			heading3: isBlockActive(editor, "heading", 3),
			listItem: isBlockActive(editor, "list-item"),
			blockQuote: isBlockActive(editor, "block-quote"),
		}),
		[editor, selection],
	);

	const ToolbarButton = ({
		icon: Icon,
		isActive,
		onMouseDown,
		title,
	}: {
		icon: React.ComponentType<{ className?: string }>;
		isActive: boolean;
		onMouseDown: (event: React.MouseEvent) => void;
		title: string;
	}) => (
		<Button
			variant="ghost"
			size="sm"
			className={cn(
				"h-8 w-8 !p-0 text-white/70 transition-all duration-200 rounded-sm",
				"hover:bg-white/15 hover:text-white hover:scale-105",
				"active:scale-95",
				isActive && "bg-white/20 text-white",
			)}
			onMouseDown={onMouseDown}
			title={title}
			type="button"
		>
			<Icon
				className={cn(
					"h-4 w-4 transition-transform duration-200",
					isActive && "scale-110",
				)}
			/>
		</Button>
	);

	return (
		<div className={cn("flex flex-col", className)}>
			<div className="flex-1 min-h-48 max-h-64 overflow-y-auto">
				<Slate
					editor={editor}
					initialValue={editorValue}
					onValueChange={handleSlateChange}
					onSelectionChange={() => setSelection(editor.selection)}
				>
					<Editable
						renderElement={renderElement}
						renderLeaf={renderLeaf}
						placeholder={placeholder}
						renderPlaceholder={({ children, attributes }) => {
							return (
								<div {...attributes} className="mt-2">
									{children}
								</div>
							);
						}}
						onKeyDown={handleKeyDown}
						onBlur={onBlur}
						readOnly={disabled}
						className={cn(
							"outline-none w-full h-full text-white placeholder:text-white/50",
							disabled && "opacity-50 cursor-not-allowed",
						)}
						style={{
							minHeight: "11rem",
							maxHeight: "15rem",
							padding: "12px",
						}}
					/>
				</Slate>
			</div>

			{/* Toolbar */}
			<div className="p-1 flex items-center gap-2 bg-white/5 backdrop-blur-sm rounded-b-md">
				<div className="flex items-center gap-1">
					{/* Text formatting */}
					<ToolbarButton
						icon={Bold}
						isActive={activeStates.bold}
						onMouseDown={(event) => {
							event.preventDefault();
							toggleMark(editor, "bold");
						}}
						title="Bold (Ctrl/Cmd+B)"
					/>
					<ToolbarButton
						icon={Italic}
						isActive={activeStates.italic}
						onMouseDown={(event) => {
							event.preventDefault();
							toggleMark(editor, "italic");
						}}
						title="Italic (Ctrl/Cmd+I)"
					/>
					<ToolbarButton
						icon={Code}
						isActive={activeStates.code}
						onMouseDown={(event) => {
							event.preventDefault();
							toggleMark(editor, "code");
						}}
						title="Code (Ctrl/Cmd+`)"
					/>
				</div>

				<div className="w-px h-6 bg-white/30 mx-2" />

				<div className="flex items-center gap-1">
					{/* Block formatting */}
					<ToolbarButton
						icon={Heading1}
						isActive={activeStates.heading1}
						onMouseDown={(event) => {
							event.preventDefault();
							toggleBlock(editor, "heading", 1);
						}}
						title="Heading 1 (Ctrl/Cmd+Shift+1)"
					/>
					<ToolbarButton
						icon={Heading2}
						isActive={activeStates.heading2}
						onMouseDown={(event) => {
							event.preventDefault();
							toggleBlock(editor, "heading", 2);
						}}
						title="Heading 2 (Ctrl/Cmd+Shift+2)"
					/>
					<ToolbarButton
						icon={Heading3}
						isActive={activeStates.heading3}
						onMouseDown={(event) => {
							event.preventDefault();
							toggleBlock(editor, "heading", 3);
						}}
						title="Heading 3"
					/>
					<ToolbarButton
						icon={List}
						isActive={activeStates.listItem}
						onMouseDown={(event) => {
							event.preventDefault();
							toggleBlock(editor, "list-item");
						}}
						title="Bullet List"
					/>
					<ToolbarButton
						icon={Quote}
						isActive={activeStates.blockQuote}
						onMouseDown={(event) => {
							event.preventDefault();
							toggleBlock(editor, "block-quote");
						}}
						title="Quote"
					/>
				</div>
			</div>
		</div>
	);
}
