import { useEffect, useRef } from 'react';
import { Crepe } from '@milkdown/crepe';
import { editorViewCtx } from '@milkdown/kit/core';
import { Selection } from '@milkdown/kit/prose/state';
import '@milkdown/crepe/theme/common/style.css';
import '@milkdown/crepe/theme/frame-dark.css';
import './markdown-editor.css';
import { cn } from '@/lib/utils';

interface MarkdownEditorProps {
  /** Initial markdown. The editor owns its content after mount; edits flow out
   *  through `onChange`, so this is only read when the editor is (re)created. */
  value: string;
  onChange?: (markdown: string) => void;
  placeholder?: string;
  /** Render the same engine read-only, for displaying a message. */
  readOnly?: boolean;
  autoFocus?: boolean;
  className?: string;
}

/**
 * Single markdown engine used for both composing and displaying messages
 * (Milkdown Crepe). Editing is inline/WYSIWYG (Obsidian/Notion feel) with full
 * GFM — headings, lists, task lists, tables, code blocks, quotes, links — and
 * read-only mode renders the exact same way, so write and read never drift.
 */
export function MarkdownEditor({
  value,
  onChange,
  placeholder,
  readOnly = false,
  autoFocus = true,
  className,
}: MarkdownEditorProps) {
  const rootRef = useRef<HTMLDivElement>(null);
  // Keep the latest onChange without forcing the editor to be recreated.
  const onChangeRef = useRef(onChange);
  onChangeRef.current = onChange;

  useEffect(() => {
    const root = rootRef.current;
    if (!root) return;

    let cancelled = false;
    let created: Crepe | null = null;

    const crepe = new Crepe({
      root,
      defaultValue: value,
      features: {
        [Crepe.Feature.CodeMirror]: true,
        [Crepe.Feature.ListItem]: true,
        [Crepe.Feature.Table]: true,
        [Crepe.Feature.LinkTooltip]: !readOnly,
        [Crepe.Feature.Cursor]: !readOnly,
        [Crepe.Feature.BlockEdit]: !readOnly,
        [Crepe.Feature.Toolbar]: !readOnly,
        [Crepe.Feature.Placeholder]: !readOnly,
        [Crepe.Feature.ImageBlock]: true,
        [Crepe.Feature.Latex]: false,
        [Crepe.Feature.TopBar]: false,
        [Crepe.Feature.AI]: false,
      },
      featureConfigs: {
        [Crepe.Feature.Placeholder]: { text: placeholder ?? '' },
        // Images come from URLs only (e.g. pasted markdown) — there is no
        // stable public upload URL, so the file uploader is hidden in CSS and
        // onUpload rejects in case a file ever reaches it anyway (the default
        // would serialize an ephemeral blob: URL into the message).
        [Crepe.Feature.ImageBlock]: {
          blockUploadPlaceholderText: 'Paste an image link…',
          inlineUploadPlaceholderText: 'paste an image link',
          maxHeight: 420,
          onUpload: () => Promise.reject(new Error('Image uploads disabled')),
        },
      },
    });

    crepe.setReadonly(readOnly);

    if (!readOnly) {
      crepe.on((listener) => {
        listener.markdownUpdated((_ctx, markdown) => {
          onChangeRef.current?.(markdown);
        });
      });
    }

    crepe.create().then(() => {
      if (cancelled) {
        crepe.destroy();
        return;
      }
      created = crepe;
      if (autoFocus && !readOnly) {
        // Place the caret at the end of the document — the editor often mounts
        // mid-typing (immersive → card flip), where start-of-doc would strand
        // the user. Selection-only transactions don't echo markdownUpdated.
        crepe.editor.action((ctx) => {
          const view = ctx.get(editorViewCtx);
          view.dispatch(
            view.state.tr
              .setSelection(Selection.atEnd(view.state.doc))
              .scrollIntoView(),
          );
          view.focus();
        });
      }
    });

    return () => {
      cancelled = true;
      created?.destroy();
    };
    // `value` is the initial content only; recreate when the mode flips.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [readOnly]);

  return (
    <div
      ref={rootRef}
      className={cn(
        'llink-crepe',
        !readOnly && 'llink-crepe--fill scrollbar-card',
        className,
      )}
    />
  );
}
