'use client';

import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Placeholder from '@tiptap/extension-placeholder';
import Image from '@tiptap/extension-image'; // [New]
import { Markdown } from 'tiptap-markdown';
import EditorToolbar from './EditorToolbar'; // [New]
import { UnderlineMarkdown } from '@/lib/tiptap/UnderlineMarkdown';
import { Extension } from '@tiptap/core'; // Import Extension
import { Selection, TextSelection } from 'prosemirror-state';
import { useEffect, useRef } from 'react';
import { WikiLinkExtension, suggestionOptions } from '@/lib/tiptap/WikiLinkExtension';
import { collection, query, where, getDocs, orderBy, startAt, endAt, limit } from 'firebase/firestore';
import { db } from '@/lib/firebase';
import { COLLECTIONS } from '@/types/firestore';
import { WikiAutoLink } from '@/lib/tiptap/WikiAutoLink';
import Fuse from 'fuse.js';

import { useWikiMapContext } from '@/contexts/WikiMapContext';
import { getChoseong } from '@/lib/utils/hangul';

interface ReflectEditorProps {
    content: string;
    onChange: (content: string) => void;
    onSave?: (content: string) => void;
    editable?: boolean;
}

export default function ReflectEditor({ content, onChange, onSave, editable = true }: ReflectEditorProps) {
    const { wikiMap, isLoading: isLoadingWikiMap, refresh } = useWikiMapContext();

    // [Root Fix] useEditor()는 마운트 시 단 한 번만 실행되어 searchDocuments 클로저가
    // 초기의 빈 wikiMap을 stale하게 캡처하는 문제를 해결.
    // useRef를 통해 항상 최신 wikiMap을 참조하도록 보장.
    const wikiMapRef = useRef<Map<string, string>>(wikiMap);
    useEffect(() => {
        wikiMapRef.current = wikiMap;
    }, [wikiMap]);

    // [Fix 1] 마운트 시 wikiMap을 강제로 최신화 (stale 캐시 방지)
    useEffect(() => {
        refresh();
    }, []); // eslint-disable-line react-hooks/exhaustive-deps

    // Local search function using the pre-cached wikiMap, fuse.js, and manual choseong logic
    const searchDocuments = async (searchTerm: string) => {
        try {
            // wikiMapRef.current를 사용해 항상 최신 Map을 참조 (stale closure 방지)
            const wikiList = Array.from(wikiMapRef.current.entries()).map(([title, id]) => ({
                id,
                title,
                choseong: getChoseong(title)
            }));

            let filtered: any[] = [];
            const normalizedSearchTerm = searchTerm.trim().toLowerCase();

            if (normalizedSearchTerm) {
                // 1. Manual exact substring & choseong match (guarantees top results)
                const isChoseongOnly = /^[ㄱ-ㅎa-zA-Z0-9]+$/.test(normalizedSearchTerm);
                const exactAndChoseongMatches = wikiList.filter(item => {
                    const titleLower = item.title.toLowerCase();
                    if (/^[ㄱ-ㅎ]+$/.test(normalizedSearchTerm)) {
                        return item.choseong.includes(normalizedSearchTerm);
                    } else {
                        // Substring match for normal text (e.g. "누수" in "누수플랜")
                        return titleLower.includes(normalizedSearchTerm);
                    }
                });

                // 2. Fuzzy search using Fuse.js for typo tolerance
                const fuse = new Fuse(wikiList, {
                    keys: ['title', 'choseong'],
                    threshold: 0.3,
                    includeScore: true,
                    distance: 100,
                });

                const fuseResults = fuse.search(normalizedSearchTerm).map(result => result.item);

                // 3. Merge and deduplicate
                const mergedMap = new Map();
                exactAndChoseongMatches.forEach(item => mergedMap.set(item.id, item));
                fuseResults.forEach(item => {
                    if (!mergedMap.has(item.id)) mergedMap.set(item.id, item);
                });

                filtered = Array.from(mergedMap.values()).slice(0, 10);
            } else {
                // Default suggestion (empty search) - show first 10
                filtered = wikiList.slice(0, 10);
            }

            // 2. Allow creating a new link if an exact or very close match doesn't exist
            // CRITICAL FIX: To prevent duplicate creation (e.g., "ABC" when "ABC" already exists but was bumped out of top 10),
            // check the exact title against the ENTIRE wikiMap, not just the truncated 'filtered' array.
            let exactMatchFound = false;

            if (normalizedSearchTerm.length > 0) {
                // A quick iteration over keys. Since wikiMapRef.current is client-side and usually < 1000 items, O(N) is acceptable.
                for (const title of wikiMapRef.current.keys()) {
                    if (title.toLowerCase() === normalizedSearchTerm) {
                        exactMatchFound = true;
                        break;
                    }
                }
            }

            if (!exactMatchFound && searchTerm.trim().length > 0) {
                filtered.push({
                    id: `new-${searchTerm}`, // Special ID prefix to indicate new doc
                    title: `Create new: "${searchTerm}"`,
                    isNew: true,
                    originalTerm: searchTerm
                });
            }

            return filtered;
        } catch (error) {
            console.error("Error searching in WikiMap:", error);
            return [];
        }
    }
    const editor = useEditor({
        extensions: [
            StarterKit.configure({
                underline: false,
                bulletList: {
                    keepMarks: true,
                    keepAttributes: false,
                },
                orderedList: {
                    keepMarks: true,
                    keepAttributes: false,
                },
            }),
            Placeholder.configure({
                placeholder: "Type '/' for commands, or write something...",
            }),
            Image, // [New]
            UnderlineMarkdown,
            // WikiLink Extension
            WikiLinkExtension.configure({
                suggestion: suggestionOptions(searchDocuments),
            }),
            // WikiAutoLink Extension (Proactive Backlinking)
            WikiAutoLink.configure({
                wikiMap: wikiMap,
            }),
            // Outliner Enforcement Extension
            Extension.create({
                name: 'outlinerEnforcement',
                addKeyboardShortcuts() {
                    return {
                        'Backspace': () => {
                            const { state } = this.editor;
                            const { selection } = state;
                            const { empty, $anchor } = selection;

                            // If selection is empty and at the start of a list item
                            if (empty && $anchor.parentOffset === 0) {
                                // Check if we are in a list item
                                if ($anchor.parent.type.name === 'paragraph' && $anchor.node(-1).type.name === 'listItem') {
                                    const listItemCount = $anchor.node(-2).childCount;
                                    const listItemIndex = $anchor.index(-2);

                                    // If it's the only list item, or first item trying to merge back, block it
                                    if (listItemCount === 1 || listItemIndex === 0) {
                                        return true; // Block backspace
                                    }
                                }
                            }
                            return false;
                        },
                        // Prevent Enter from creating a paragraph outside the list
                        'Enter': () => {
                            const { state } = this.editor;
                            const { selection } = state;
                            const { $from } = selection;

                            // If we are at the end of a list path, and try to enter out of it
                            if ($from.depth === 1 && $from.parent.type.name === 'orderedList') {
                                // Default behavior would usually handle this, but let's be safe
                                return false;
                            }
                            return false;
                        }
                    }
                }
            }),
            // Save Shortcut Extension (Ctrl+Enter)
            Extension.create({
                name: 'saveShortcut',
                addKeyboardShortcuts() {
                    return {
                        'Mod-Enter': () => {
                            const markdown = (this.editor.storage as any).markdown.getMarkdown();
                            onSave?.(markdown);
                            return true;
                        },
                    }
                }
            }),
            // Node Movement Extension (Shift-Alt-↑/↓)
            Extension.create({
                name: 'nodeMovement',
                addKeyboardShortcuts() {
                    return {
                        'Shift-Alt-ArrowUp': () => {
                            const { state, view } = this.editor;
                            const { selection, tr } = state;
                            const { $from } = selection;

                            // Find the list item
                            let depth = $from.depth;
                            while (depth > 0 && $from.node(depth).type.name !== 'listItem') {
                                depth--;
                            }

                            if (depth === 0) return false;

                            const node = $from.node(depth);
                            const pos = $from.before(depth);
                            const index = $from.index(depth - 1);

                            if (index === 0) return false;

                            // Calculate previous node position
                            const parentPos = $from.before(depth - 1);
                            let prevNodePos = parentPos + 1;
                            const parent = $from.node(depth - 1);
                            for (let i = 0; i < index - 1; i++) {
                                prevNodePos += parent.child(i).nodeSize;
                            }

                            tr.delete(pos, pos + node.nodeSize);
                            tr.insert(prevNodePos, node);

                            // Restore selection
                            const newPos = prevNodePos + 1;
                            tr.setSelection(Selection.near(tr.doc.resolve(newPos)));

                            view.dispatch(tr);
                            return true;
                        },
                        'Shift-Alt-ArrowDown': () => {
                            const { state, view } = this.editor;
                            const { selection, tr } = state;
                            const { $from } = selection;

                            // Find the list item
                            let depth = $from.depth;
                            while (depth > 0 && $from.node(depth).type.name !== 'listItem') {
                                depth--;
                            }

                            if (depth === 0) return false;

                            const node = $from.node(depth);
                            const pos = $from.before(depth);
                            const parent = $from.node(depth - 1);
                            const index = $from.index(depth - 1);

                            if (index >= parent.childCount - 1) return false;

                            const nextNode = parent.child(index + 1);

                            // Move: Insert after next node then delete original
                            const insertPos = pos + node.nodeSize + nextNode.nodeSize;
                            tr.insert(insertPos, node);
                            tr.delete(pos, pos + node.nodeSize);

                            // Restore selection
                            const newPos = insertPos - node.nodeSize + 1;
                            tr.setSelection(Selection.near(tr.doc.resolve(newPos)));

                            view.dispatch(tr);
                            return true;
                        }
                    }
                }
            }),
            Markdown.configure({
                linkify: false, // Prevent standard links from hijacking wiki links
                html: true,
                transformPastedText: true,
                transformCopiedText: true,
            }),
        ],
        content: content, // Initial content (markdown string)
        editable: editable,
        immediatelyRender: false,
        editorProps: {
            attributes: {
                class: 'prose prose-indigo dark:prose-invert max-w-none focus:outline-none min-h-[400px]',
            },
            // Redundant protection at ProseMirror level
            handleDOMEvents: {
                click: (view, event) => {
                    const target = event.target as HTMLElement;
                    if (target.closest('a') || target.closest('.wiki-link') || target.closest('.wiki-link-node')) {
                        event.preventDefault();
                        event.stopPropagation();
                        return true;
                    }
                    return false;
                },
            },
        },
        onUpdate: ({ editor }) => {
            // Ensure content stays as a list
            if (editor.isEmpty || editor.getHTML() === '<p></p>') {
                editor.commands.setContent('1. ', { emitUpdate: false });
            }

            const markdown = (editor.storage as any).markdown.getMarkdown();
            onChange(markdown);
        },
        onSelectionUpdate: ({ editor }) => {
            // Reflect style: Focus bullet on selection
        },
        onCreate: ({ editor }) => {
            // Initialize with Ordered List if empty
            if (editor.isEmpty || editor.getHTML() === '<p></p>') {
                editor.commands.setContent('1. ', { emitUpdate: false });
            }

            // IRON-CLAD PROTECTION: Add capture phase listener to the real DOM
            const dom = editor.view.dom;
            dom.addEventListener('click', (e: MouseEvent) => {
                const target = e.target as HTMLElement;
                // Block if it's a link or wiki link
                if (target.closest('a') || target.closest('.wiki-link') || target.closest('.wiki-link-node')) {
                    e.preventDefault();
                    e.stopPropagation();
                }
            }, true); // TRUE = CAPTURING PHASE (Highest Priority)
        },
    });

    // Handle dynamic WikiMap updates
    useEffect(() => {
        if (editor && !editor.isDestroyed) {
            // 1. Update WikiAutoLink configuration
            editor.setOptions({
                extensions: [
                    ...editor.extensionManager.extensions.filter(e => e.name !== 'wikiAutoLink'),
                    WikiAutoLink.configure({ wikiMap }),
                ]
            });

            // 2. [New] Reconcile "new-*" IDs to real IDs if they now exist
            // This fixes the issue where a "red" link doesn't turn "blue" after creating the target doc
            const { state, view } = editor;
            const tr = state.tr;
            let modified = false;

            state.doc.descendants((node, pos) => {
                if (node.type.name === 'wikiLink') {
                    const id = node.attrs.id;
                    const label = node.attrs.label;

                    // Check if it's a placeholder ID (starts with "new-") or if we want to be safe, check all
                    if (id && typeof id === 'string' && id.startsWith('new-')) {
                        // Try to find the real ID by label (Title)
                        // formatting: id is usually "new-Title" or just check the label
                        const titleKey = label || id.replace(/^new-/, '');

                        if (wikiMap.has(titleKey)) {
                            const realId = wikiMap.get(titleKey);
                            if (realId) {
                                tr.setNodeMarkup(pos, undefined, {
                                    ...node.attrs,
                                    id: realId
                                });
                                modified = true;
                            }
                        }
                    }
                }
            });

            if (modified) {
                view.dispatch(tr);
            }
        }
    }, [wikiMap, editor]);

    // [WikiLink Stale ID Fix] wikiMap 갱신 시 에디터 내 stale wikiLink 노드 자동 교정
    // 삭제된 문서를 가리키는 [[title]] 링크를 wikiMap 기준으로 최신 ID로 업데이트
    useEffect(() => {
        if (!editor || editor.isDestroyed || wikiMap.size === 0) return;
        const validIds = new Set(wikiMap.values());
        const patches: { pos: number; newId: string; attrs: any }[] = [];

        editor.state.doc.descendants((node, pos) => {
            if (node.type.name !== 'wikiLink') return;
            const currentId = node.attrs.id as string | null;
            const label = (node.attrs.label || '') as string;
            if (!currentId || validIds.has(currentId)) return; // 유효한 ID면 스킵
            const freshId = wikiMap.get(label.trim());
            if (freshId) patches.push({ pos, newId: freshId, attrs: node.attrs });
        });

        if (patches.length === 0) return;
        // 뒤에서부터 처리해야 pos offset이 밀리지 않음
        const tr = editor.state.tr;
        patches.reverse().forEach(({ pos, newId, attrs }) => {
            tr.setNodeMarkup(pos, null, { ...attrs, id: newId });
        });
        editor.view.dispatch(tr);
    }, [wikiMap, editor]);

    // [Fix] Sync content prop to editor for external updates (e.g. Voice Insertion)
    useEffect(() => {
        if (editor && !editor.isDestroyed && content !== undefined) {
            const currentContent = (editor.storage as any).markdown.getMarkdown();
            if (content !== currentContent) {
                // [Fix 2] suggestion(자동완성 팝업)이 활성 중일 때는 setContent를 호출하지 않음
                // setContent()는 ProseMirror 상태를 전체 리셋하여 [[ 팝업을 강제 종료시킴
                const suggestionPluginKey = (editor.state as any).plugins?.find(
                    (p: any) => p?.key?.key?.startsWith?.('Suggestion')
                );
                const isSuggestionActive = !!suggestionPluginKey?.getState?.(editor.state)?.active;
                if (!isSuggestionActive) {
                    editor.commands.setContent(content);
                }
            }
        }
    }, [content, editor]);

    if (!editor) {
        return null;
    }

    // Intercept clicks on potential-wiki-link decorations
    const handleClick = (e: React.MouseEvent) => {
        const target = e.target as HTMLElement;
        const potentialLink = target.closest('.potential-wiki-link');

        if (potentialLink && editor) {
            const id = potentialLink.getAttribute('data-id');
            const title = potentialLink.getAttribute('data-title');

            if (id && title) {
                // Find current position in ProseMirror doc
                // Since it's a decoration, we need to be careful.
                // For now, let's use a simple approach: Search the text around the click or just use the node selection
                const pos = editor.view.posAtDOM(target, 0);

                // Replace the text with a wikiLink node
                editor.chain()
                    .focus()
                    .insertContentAt({ from: pos, to: pos + title.length }, [
                        {
                            type: 'wikiLink',
                            attrs: { id, label: title }
                        }
                    ])
                    .run();
            }
        }
    };

    return (
        <div className="reflect-editor w-full" onClick={handleClick}>
            {/* [New] Sticky Toolbar */}
            {editable && <EditorToolbar editor={editor} />}

            <EditorContent editor={editor} />
        </div>
    );
}
