import {
    Bold,
    Italic,
    Link,
    List,
    ListOrdered,
    Palette,
    Quote,
    Table,
    TableColumnsSplit,
    TableRowsSplit,
    Underline,
    Youtube,
} from 'lucide-react';
import { useEffect, useRef } from 'react';
import { Button } from '@/components/ui/button';
import {
    Select,
    SelectContent,
    SelectItem,
    SelectTrigger,
    SelectValue,
} from '@/components/ui/select';
import { cn } from '@/lib/utils';

type RichTextEditorProps = {
    value: string;
    onChange: (value: string) => void;
    className?: string;
};

const commands = [
    { command: 'bold', icon: Bold, label: 'Bold' },
    { command: 'italic', icon: Italic, label: 'Italic' },
    { command: 'underline', icon: Underline, label: 'Underline' },
    {
        command: 'formatBlock',
        icon: Quote,
        label: 'Quote',
        value: 'blockquote',
    },
    { command: 'insertUnorderedList', icon: List, label: 'Bullet list' },
    { command: 'insertOrderedList', icon: ListOrdered, label: 'Numbered list' },
];

const headingOptions = [
    { value: 'p', label: 'Paragraph' },
    { value: 'h1', label: 'Heading 1' },
    { value: 'h2', label: 'Heading 2' },
    { value: 'h3', label: 'Heading 3' },
    { value: 'h4', label: 'Heading 4' },
    { value: 'h5', label: 'Heading 5' },
    { value: 'h6', label: 'Heading 6' },
];

export default function RichTextEditor({
    value,
    onChange,
    className,
}: RichTextEditorProps) {
    const editorRef = useRef<HTMLDivElement>(null);
    const selectionRef = useRef<Range | null>(null);

    useEffect(() => {
        if (editorRef.current && editorRef.current.innerHTML !== value) {
            editorRef.current.innerHTML = value;
        }
    }, [value]);

    const saveSelection = () => {
        const selection = window.getSelection();

        if (!selection || selection.rangeCount === 0) {
            return;
        }

        const range = selection.getRangeAt(0);

        if (editorRef.current?.contains(range.commonAncestorContainer)) {
            selectionRef.current = range.cloneRange();
        }
    };

    const restoreSelection = () => {
        const selection = window.getSelection();

        if (!selection || !selectionRef.current) {
            return;
        }

        selection.removeAllRanges();
        selection.addRange(selectionRef.current);
    };

    const runCommand = (command: string, commandValue?: string) => {
        editorRef.current?.focus();
        restoreSelection();
        document.execCommand(command, false, commandValue);
        onChange(editorRef.current?.innerHTML ?? '');
        saveSelection();
    };

    const insertHtml = (html: string) => {
        editorRef.current?.focus();
        restoreSelection();
        document.execCommand('insertHTML', false, html);
        onChange(editorRef.current?.innerHTML ?? '');
        saveSelection();
    };

    const insertLink = () => {
        const url = window.prompt('Enter link URL');

        if (!url) {
            return;
        }

        const selection = window.getSelection()?.toString();

        if (selection) {
            runCommand('createLink', url);
            return;
        }

        insertHtml(
            `<a href="${escapeHtml(url)}" target="_blank" rel="noopener noreferrer">${escapeHtml(url)}</a>`,
        );
    };

    const insertTable = () => {
        insertHtml(
            '<table><tbody><tr><th>Header</th><th>Header</th></tr><tr><td>Cell</td><td>Cell</td></tr></tbody></table><p><br></p>',
        );
    };

    const getSelectedTableCell = () => {
        const node = selectionRef.current?.startContainer;
        const element = node instanceof Element ? node : node?.parentElement;
        const cell = element?.closest('td, th');

        if (
            cell instanceof HTMLTableCellElement &&
            editorRef.current?.contains(cell)
        ) {
            return cell;
        }

        return null;
    };

    const syncEditorValue = () => {
        onChange(editorRef.current?.innerHTML ?? '');
    };

    const createTableCell = (tagName: 'td' | 'th') => {
        const cell = document.createElement(tagName);
        cell.innerHTML = '<br>';

        return cell;
    };

    const placeCursorInside = (element: HTMLElement) => {
        const range = document.createRange();
        const selection = window.getSelection();

        range.selectNodeContents(element);
        range.collapse(true);
        selection?.removeAllRanges();
        selection?.addRange(range);
        selectionRef.current = range.cloneRange();
    };

    const addTableRow = () => {
        editorRef.current?.focus();
        restoreSelection();

        const cell = getSelectedTableCell();

        if (!cell) {
            window.alert('Place the cursor inside a table cell first.');
            return;
        }

        const currentRow = cell.parentElement;

        if (!(currentRow instanceof HTMLTableRowElement)) {
            return;
        }

        const nextRow = document.createElement('tr');
        const columnCount = currentRow.cells.length || 1;

        for (let index = 0; index < columnCount; index += 1) {
            nextRow.appendChild(createTableCell('td'));
        }

        currentRow.after(nextRow);
        placeCursorInside(nextRow.cells[0]);
        syncEditorValue();
    };

    const addTableColumn = () => {
        editorRef.current?.focus();
        restoreSelection();

        const cell = getSelectedTableCell();
        const table = cell?.closest('table');

        if (!cell || !table) {
            window.alert('Place the cursor inside a table cell first.');
            return;
        }

        const columnIndex = cell.cellIndex + 1;
        let firstInsertedCell: HTMLTableCellElement | null = null;

        Array.from(table.rows).forEach((row) => {
            const firstCell = row.cells[0];
            const tagName =
                firstCell?.tagName.toLowerCase() === 'th' ? 'th' : 'td';
            const newCell = createTableCell(tagName);

            row.insertBefore(newCell, row.cells[columnIndex] ?? null);
            firstInsertedCell ??= newCell;
        });

        if (firstInsertedCell) {
            placeCursorInside(firstInsertedCell);
        }

        syncEditorValue();
    };

    const insertYoutube = () => {
        const url = window.prompt('Enter YouTube URL');
        const videoId = url ? getYoutubeVideoId(url) : null;

        if (!videoId) {
            return;
        }

        insertHtml(
            `<div class="youtube-embed"><iframe src="https://www.youtube-nocookie.com/embed/${videoId}" title="YouTube video" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe></div><p><br></p>`,
        );
    };

    return (
        <div className={cn('overflow-hidden rounded-md border', className)}>
            <div className="flex flex-wrap gap-1 border-b bg-muted/50 p-2">
                <Select
                    defaultValue="p"
                    onValueChange={(value) => runCommand('formatBlock', value)}
                >
                    <SelectTrigger className="h-9 w-36 bg-background">
                        <SelectValue />
                    </SelectTrigger>
                    <SelectContent>
                        {headingOptions.map((option) => (
                            <SelectItem key={option.value} value={option.value}>
                                {option.label}
                            </SelectItem>
                        ))}
                    </SelectContent>
                </Select>

                {commands.map((item) => (
                    <Button
                        key={`${item.command}-${item.label}`}
                        type="button"
                        variant="ghost"
                        size="icon"
                        onClick={() => runCommand(item.command, item.value)}
                        title={item.label}
                        onMouseDown={(event) => event.preventDefault()}
                    >
                        <item.icon />
                        <span className="sr-only">{item.label}</span>
                    </Button>
                ))}

                <label
                    className="inline-flex size-9 cursor-pointer items-center justify-center rounded-md text-sm transition-colors hover:bg-accent hover:text-accent-foreground"
                    title="Text color"
                    onMouseDown={saveSelection}
                >
                    <Palette className="size-4" />
                    <input
                        type="color"
                        className="sr-only"
                        onChange={(event) =>
                            runCommand('foreColor', event.target.value)
                        }
                    />
                    <span className="sr-only">Text color</span>
                </label>

                <Button
                    type="button"
                    variant="ghost"
                    size="icon"
                    onClick={insertLink}
                    title="Link"
                    onMouseDown={(event) => event.preventDefault()}
                >
                    <Link />
                    <span className="sr-only">Link</span>
                </Button>

                <Button
                    type="button"
                    variant="ghost"
                    size="icon"
                    onClick={insertTable}
                    title="Table"
                    onMouseDown={(event) => event.preventDefault()}
                >
                    <Table />
                    <span className="sr-only">Table</span>
                </Button>

                <Button
                    type="button"
                    variant="ghost"
                    size="icon"
                    onClick={addTableRow}
                    title="Add table row"
                    onMouseDown={(event) => event.preventDefault()}
                >
                    <TableRowsSplit />
                    <span className="sr-only">Add table row</span>
                </Button>

                <Button
                    type="button"
                    variant="ghost"
                    size="icon"
                    onClick={addTableColumn}
                    title="Add table column"
                    onMouseDown={(event) => event.preventDefault()}
                >
                    <TableColumnsSplit />
                    <span className="sr-only">Add table column</span>
                </Button>

                <Button
                    type="button"
                    variant="ghost"
                    size="icon"
                    onClick={insertYoutube}
                    title="YouTube"
                    onMouseDown={(event) => event.preventDefault()}
                >
                    <Youtube />
                    <span className="sr-only">YouTube</span>
                </Button>
            </div>
            <div
                ref={editorRef}
                contentEditable
                className="rich-text-content min-h-80 bg-background p-4 text-sm leading-6 outline-none focus-visible:ring-2 focus-visible:ring-ring [&_.youtube-embed]:aspect-video [&_.youtube-embed]:w-full [&_.youtube-embed_iframe]:h-full [&_.youtube-embed_iframe]:w-full [&_a]:text-primary [&_a]:underline [&_blockquote]:border-l-4 [&_blockquote]:pl-4 [&_blockquote]:text-muted-foreground [&_h1]:text-3xl [&_h1]:font-semibold [&_h2]:text-2xl [&_h2]:font-semibold [&_h3]:text-xl [&_h3]:font-semibold [&_h4]:text-lg [&_h4]:font-semibold [&_h5]:text-base [&_h5]:font-semibold [&_h6]:text-sm [&_h6]:font-semibold [&_ol]:list-decimal [&_ol]:pl-6 [&_table]:w-full [&_table]:border-collapse [&_td]:min-w-24 [&_td]:border [&_td]:p-2 [&_th]:min-w-24 [&_th]:border [&_th]:bg-muted [&_th]:p-2 [&_th]:text-left [&_ul]:list-disc [&_ul]:pl-6"
                onInput={(event) => {
                    onChange(event.currentTarget.innerHTML);
                    saveSelection();
                }}
                onKeyUp={saveSelection}
                onMouseUp={saveSelection}
            />
        </div>
    );
}

function getYoutubeVideoId(url: string): string | null {
    try {
        const parsedUrl = new URL(url);

        if (parsedUrl.hostname.includes('youtu.be')) {
            return parsedUrl.pathname.split('/').filter(Boolean)[0] ?? null;
        }

        if (parsedUrl.hostname.includes('youtube.com')) {
            return (
                parsedUrl.searchParams.get('v') ??
                parsedUrl.pathname.match(/\/embed\/([^/]+)/)?.[1] ??
                parsedUrl.pathname.match(/\/shorts\/([^/]+)/)?.[1] ??
                null
            );
        }
    } catch {
        return null;
    }

    return null;
}

function escapeHtml(value: string): string {
    return value
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
}
