import { EditorContent, useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import {
    Bold,
    Code2,
    Heading2,
    Italic,
    Link2,
    List,
    ListOrdered,
    Quote,
    Redo2,
    RemoveFormatting,
    Strikethrough,
    Underline as UnderlineIcon,
    Undo2,
} from 'lucide-react';
import { useState } from 'react';
import { cn } from '@/lib/utils';

/**
 * The rich text editor used for a ticket's description.
 *
 * The value it submits is HTML, and the server sanitizes it again before it is
 * stored — the toolbar here decides what a person can write, not what is
 * allowed to reach the database.
 *
 * It submits through a hidden input carrying the same `name`, so the ticket
 * forms keep posting exactly as they did when this was a textarea.
 */
export function RichTextEditor({
    name = 'body',
    value = '',
    placeholder,
    id,
    className,
}: {
    name?: string;
    /** Existing description: sanitized HTML, as the server sends it. */
    value?: string;
    placeholder?: string;
    id?: string;
    className?: string;
}) {
    const [html, setHtml] = useState(value);

    const editor = useEditor({
        extensions: [
            StarterKit.configure({
                heading: { levels: [2, 3] },
                // The description is a message, not a document: a link is
                // allowed but nothing that needs its own block for a rule.
                horizontalRule: false,
                link: {
                    openOnClick: false,
                    autolink: true,
                    HTMLAttributes: { rel: 'noopener noreferrer nofollow' },
                },
            }),
        ],
        content: value,
        // Inertia renders on the client here, but saying so keeps the editor
        // from trying to render during a server pass.
        immediatelyRender: false,
        editorProps: {
            attributes: {
                id: id ?? name,
                class: cn(
                    'min-h-32 w-full px-3 py-2 text-sm leading-relaxed outline-none',
                    // The description is rendered with these rules on the ticket
                    // page too, so what is typed here looks like what is stored.
                    '[&_p]:mb-2 [&_p:last-child]:mb-0',
                    '[&_ul]:mb-2 [&_ul]:list-disc [&_ul]:pl-5',
                    '[&_ol]:mb-2 [&_ol]:list-decimal [&_ol]:pl-5',
                    '[&_a]:text-solar [&_a]:underline',
                    '[&_h2]:mt-3 [&_h2]:mb-2 [&_h2]:text-base [&_h2]:font-semibold',
                    '[&_h3]:mt-3 [&_h3]:mb-2 [&_h3]:font-semibold',
                    '[&_blockquote]:border-l-2 [&_blockquote]:border-hairline [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground',
                    '[&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-xs',
                    '[&_pre]:mb-2 [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:text-xs',
                ),
            },
        },
        onUpdate: ({ editor: instance }) => setHtml(instance.getHTML()),
    });

    if (!editor) {
        // First paint only: the editor exists by the time anything can be typed.
        return (
            <div className="min-h-32 rounded-md border border-input px-3 py-2 text-sm text-muted-foreground">
                Loading the editor…
            </div>
        );
    }

    const setLink = () => {
        const previous = editor.getAttributes('link').href as
            | string
            | undefined;
        const url = window.prompt(
            'Where should the link go?',
            previous ?? 'https://',
        );

        if (url === null) {
            return;
        }

        if (url.trim() === '') {
            editor.chain().focus().extendMarkRange('link').unsetLink().run();

            return;
        }

        editor
            .chain()
            .focus()
            .extendMarkRange('link')
            .setLink({ href: url.trim() })
            .run();
    };

    return (
        <div
            className={cn(
                'rounded-md border border-input shadow-xs focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 dark:bg-input/30',
                className,
            )}
        >
            <div className="flex flex-wrap items-center gap-0.5 border-b border-hairline px-1.5 py-1">
                <ToolbarButton
                    label="Bold"
                    active={editor.isActive('bold')}
                    onClick={() => editor.chain().focus().toggleBold().run()}
                >
                    <Bold />
                </ToolbarButton>
                <ToolbarButton
                    label="Italic"
                    active={editor.isActive('italic')}
                    onClick={() => editor.chain().focus().toggleItalic().run()}
                >
                    <Italic />
                </ToolbarButton>
                <ToolbarButton
                    label="Underline"
                    active={editor.isActive('underline')}
                    onClick={() =>
                        editor.chain().focus().toggleUnderline().run()
                    }
                >
                    <UnderlineIcon />
                </ToolbarButton>
                <ToolbarButton
                    label="Strikethrough"
                    active={editor.isActive('strike')}
                    onClick={() => editor.chain().focus().toggleStrike().run()}
                >
                    <Strikethrough />
                </ToolbarButton>

                <ToolbarDivider />

                <ToolbarButton
                    label="Heading"
                    active={editor.isActive('heading', { level: 2 })}
                    onClick={() =>
                        editor.chain().focus().toggleHeading({ level: 2 }).run()
                    }
                >
                    <Heading2 />
                </ToolbarButton>
                <ToolbarButton
                    label="Bullet list"
                    active={editor.isActive('bulletList')}
                    onClick={() =>
                        editor.chain().focus().toggleBulletList().run()
                    }
                >
                    <List />
                </ToolbarButton>
                <ToolbarButton
                    label="Numbered list"
                    active={editor.isActive('orderedList')}
                    onClick={() =>
                        editor.chain().focus().toggleOrderedList().run()
                    }
                >
                    <ListOrdered />
                </ToolbarButton>
                <ToolbarButton
                    label="Quote"
                    active={editor.isActive('blockquote')}
                    onClick={() =>
                        editor.chain().focus().toggleBlockquote().run()
                    }
                >
                    <Quote />
                </ToolbarButton>
                <ToolbarButton
                    label="Code"
                    active={editor.isActive('codeBlock')}
                    onClick={() =>
                        editor.chain().focus().toggleCodeBlock().run()
                    }
                >
                    <Code2 />
                </ToolbarButton>
                <ToolbarButton
                    label="Link"
                    active={editor.isActive('link')}
                    onClick={setLink}
                >
                    <Link2 />
                </ToolbarButton>

                <ToolbarDivider />

                <ToolbarButton
                    label="Clear formatting"
                    onClick={() =>
                        editor
                            .chain()
                            .focus()
                            .unsetAllMarks()
                            .clearNodes()
                            .run()
                    }
                >
                    <RemoveFormatting />
                </ToolbarButton>
                <ToolbarButton
                    label="Undo"
                    disabled={!editor.can().undo()}
                    onClick={() => editor.chain().focus().undo().run()}
                >
                    <Undo2 />
                </ToolbarButton>
                <ToolbarButton
                    label="Redo"
                    disabled={!editor.can().redo()}
                    onClick={() => editor.chain().focus().redo().run()}
                >
                    <Redo2 />
                </ToolbarButton>
            </div>

            <div className="relative">
                {editor.isEmpty && placeholder ? (
                    // Drawn behind the editor rather than typed into it, so the
                    // prompt can never end up posted as the description.
                    <span className="pointer-events-none absolute top-2 left-3 text-sm text-muted-foreground/70">
                        {placeholder}
                    </span>
                ) : null}

                <EditorContent editor={editor} />
            </div>

            {/* What the form actually posts. */}
            <input type="hidden" name={name} value={html} />
        </div>
    );
}

function ToolbarButton({
    label,
    active = false,
    disabled = false,
    onClick,
    children,
}: {
    label: string;
    active?: boolean;
    disabled?: boolean;
    onClick: () => void;
    children: React.ReactNode;
}) {
    return (
        <button
            type="button"
            title={label}
            aria-label={label}
            aria-pressed={active}
            disabled={disabled}
            onMouseDown={(event) => event.preventDefault()}
            onClick={onClick}
            className={cn(
                'flex size-7 items-center justify-center rounded-md text-muted-foreground transition-colors',
                'hover:bg-muted hover:text-foreground',
                'disabled:pointer-events-none disabled:opacity-40',
                '[&_svg]:size-3.5',
                active && 'bg-muted text-foreground',
            )}
        >
            {children}
        </button>
    );
}

function ToolbarDivider() {
    return <span className="mx-1 h-4 w-px bg-hairline" aria-hidden />;
}
