import { Node, mergeAttributes } from '@tiptap/core';
import Suggestion from '@tiptap/suggestion';
import { ReactRenderer } from '@tiptap/react';
import tippy from 'tippy.js';
import WikiLinkList from '../../components/WikiLinkList';

export const WikiLinkExtension = Node.create({
    name: 'wikiLink',

    priority: 1000,

    group: 'inline',
    inline: true,
    selectable: true,
    atom: true,

    addAttributes() {
        return {
            id: {
                default: null,
            },
            label: {
                default: null,
            },
        };
    },

    // Define how this node maps to Markdown when saving
    addStorage() {
        return {
            markdown: {
                serialize(state: any, node: any) {
                    // Convert internal node to [Label](/docs/ID)
                    const label = node.attrs.label || node.attrs.id;
                    // decode the ID first to ensure we don't double encode if it came in encoded
                    const rawId = decodeURIComponent(node.attrs.id);
                    const url = `/docs/${encodeURIComponent(rawId)}`;
                    state.write(`[${label}](${url})`);
                },
                parse: {
                    setup(markdownit: any) {
                        // 1. Handle [[ID|Label]] format (legacy or manual typing)
                        markdownit.inline.ruler.before('link', 'wiki_link', (state: any, silent: any) => {
                            const start = state.pos;
                            if (state.src.charCodeAt(start) !== 0x5B /* [ */ ||
                                state.src.charCodeAt(start + 1) !== 0x5B /* [ */) {
                                return false;
                            }

                            const match = state.src.slice(start).match(/^\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/);
                            if (!match) return false;

                            if (!silent) {
                                const token = state.push('wiki_link', 'wikiLink', 0);
                                token.attrs = [
                                    ['id', match[1]],
                                    ['label', match[2] || match[1]],
                                ];
                            }

                            state.pos += match[0].length;
                            return true;
                        });

                        // 2. Intercept [Label](/docs/ID) before standard markdown link parsing
                        // Because we do not use the @tiptap/extension-link, tiptap-markdown will drop the link entirely.
                        // We must parse it directly into our wikiLink node.
                        markdownit.inline.ruler.before('link', 'wiki_md_link', (state: any, silent: any) => {
                            const start = state.pos;
                            if (state.src.charCodeAt(start) !== 0x5B /* [ */) {
                                return false;
                            }

                            const match = state.src.slice(start).match(/^\[([^\]]+)\]\(\/docs\/([^)]+)\)/);
                            if (!match) return false;

                            if (!silent) {
                                const token = state.push('wiki_link', 'wikiLink', 0);
                                token.attrs = [
                                    ['id', decodeURIComponent(match[2])],
                                    ['label', match[1]],
                                ];
                            }

                            state.pos += match[0].length;
                            return true;
                        });

                        // 3. Add the render rule so tiptap can convert the markdown token to HTML
                        markdownit.renderer.rules.wiki_link = (tokens: any, idx: number) => {
                            const token = tokens[idx];
                            const idAttr = token.attrs?.find((a: any) => a[0] === 'id');
                            const labelAttr = token.attrs?.find((a: any) => a[0] === 'label');
                            const id = idAttr ? idAttr[1] : '';
                            const label = labelAttr ? labelAttr[1] : id;
                            return `<a href="/docs/${encodeURIComponent(id)}">${label}</a>`;
                        };
                    },
                }
            }
        }
    },

    addOptions() {
        return {
            suggestion: {
                char: '[[',
                allowSpaces: true,
                pluginKey: undefined,
                command: ({ editor, range, props }: any) => {
                    // Props should contain id and label
                    const id = props.id || props.label;
                    const label = props.label || props.id;

                    editor
                        .chain()
                        .focus()
                        .insertContentAt(range, [
                            {
                                type: 'wikiLink',
                                attrs: {
                                    id: id,
                                    label: label,
                                },
                            },
                            {
                                type: 'text',
                                text: ' ',
                            },
                        ])
                        .run()
                },
                allow: ({ state, range }: any) => {
                    const $from = state.doc.resolve(range.from);
                    const type = state.schema.nodes.wikiLink;
                    const allow = !!$from.parent.type.contentMatch.matchType(type);
                    return allow;
                },
            },
        }
    },

    parseHTML() {
        return [
            {
                tag: 'span[data-wiki-link]',
            },
            {
                tag: 'a',
                getAttrs: (node: any) => {
                    const href = node.getAttribute('href');
                    if (href && href.startsWith('/docs/')) {
                        // CRITICAL: decodeURIComponent to prevent double encoding
                        const rawId = href.replace('/docs/', '');
                        return {
                            id: decodeURIComponent(rawId),
                            label: node.textContent,
                        };
                    }
                    return false;
                },
            },
        ];
    },

    renderHTML({ HTMLAttributes }) {
        return [
            'span',
            mergeAttributes(HTMLAttributes, {
                'data-wiki-link': '',
                class: 'wiki-link'
            }),
            `[[${HTMLAttributes.label || HTMLAttributes.id}]]`,
        ];
    },

    addNodeView() {
        return ({ node, getPos, editor }) => {
            const dom = document.createElement('span');
            dom.setAttribute('data-wiki-link', '');
            dom.setAttribute('data-id', node.attrs.id);
            dom.className = 'wiki-link-node'; // Using a specific class for editor view
            dom.innerText = `[[${node.attrs.label || node.attrs.id}]]`;

            // CRITICAL: Block all mouse events to prevent standard link handling
            dom.addEventListener('mousedown', (e) => {
                // Let the editor handle selection
            });

            dom.addEventListener('click', (e) => {
                e.preventDefault();
                e.stopPropagation();

                // Select node on click for easier editing
                if (typeof getPos === 'function') {
                    const pos = getPos();
                    if (typeof pos === 'number') {
                        editor.commands.setNodeSelection(pos);
                    }
                }
            });

            return {
                dom,
            };
        };
    },

    addProseMirrorPlugins() {
        return [
            Suggestion({
                editor: this.editor,
                ...this.options.suggestion,
            }),
        ];
    },
});

export const suggestionOptions = (searchDocuments: (query: string) => Promise<any[]>) => ({
    // CRITICAL: We must explicitly re-declare these options in the return object.
    // When ReflectEditor.tsx calls WikiLinkExtension.configure({ suggestion: suggestionOptions(...) }),
    // TipTap shallow-overwrites the entire suggestion object, destroying the defaults defined in addOptions().
    char: '[[',
    allowSpaces: true,
    command: ({ editor, range, props }: any) => {
        // Props should contain id and label
        const id = props.id || props.label;
        const label = props.label || props.id;

        // If it's a "Create new" item, we use the original term as label
        const finalLabel = props.isNew ? props.originalTerm : label;

        editor
            .chain()
            .focus()
            .insertContentAt(range, [
                {
                    type: 'wikiLink',
                    attrs: {
                        id: id,
                        label: finalLabel,
                    },
                },
                {
                    type: 'text',
                    text: ' ',
                },
            ])
            .run()
    },
    allow: ({ state, range }: any) => {
        const $from = state.doc.resolve(range.from);
        const type = state.schema.nodes.wikiLink;
        const allow = !!$from.parent.type.contentMatch.matchType(type);
        return allow;
    },
    items: async ({ query }: { query: string }) => {
        return await searchDocuments(query);
    },

    render: () => {
        let component: any;
        let popup: any;

        return {
            onStart: (props: any) => {
                component = new ReactRenderer(WikiLinkList, {
                    props,
                    editor: props.editor,
                });

                if (!props.clientRect) {
                    return;
                }

                popup = tippy('body', {
                    getReferenceClientRect: props.clientRect,
                    appendTo: () => document.body,
                    content: component.element,
                    showOnCreate: true,
                    interactive: true,
                    trigger: 'manual',
                    placement: 'bottom-start',
                });
            },

            onUpdate(props: any) {
                component.updateProps(props);

                if (!props.clientRect) {
                    return;
                }

                popup[0].setProps({
                    getReferenceClientRect: props.clientRect,
                });
            },

            onKeyDown(props: any) {
                if (props.event.key === 'Escape') {
                    popup?.[0]?.hide();
                    return true;
                }

                return component?.ref?.onKeyDown?.(props) ?? false;
            },

            onExit() {
                if (popup && popup[0]) {
                    popup[0].destroy();
                }
                if (component) {
                    component.destroy();
                }
            },
        }
    },
});
