"use client";

import React, { useCallback, useEffect, useRef, useState } from "react";

import "./editor.md.css";

// css plugin
import "./plugin/gallery/gallery.css";

import { useEditor } from "./provider";
import { GalleryPlugin } from "./plugin/gallery/gallery";
import { convertMarkdownToJSON } from "./utils/markdown.to.json";
import { convertToMarkdown } from "./utils/to.markdown";

/**
 * EditorMD
 * @description
 * Editor markdown component on change will return markdown and data as json.
 *
 * If you want to styling using css module, use className props instead of className.
 * To use it, wrap to className style you want to change style.
 *
 *
 * Example:
 * .classname :global(.codex-editor__redactor) {
 *
 *    padding-bottom: 100px !important;
 *
 * }
 *
 * @type {React.FC<{
 *    id: string;
 *    data: string;
 *    onChange?: (markdown: string, data: any) => void;
 *    onFocus?: (e: any) => void;
 *    onBlur?: (e: any) => void;
 *    onReady: (editorElement: HTMLElement, editor: any) => void;
 *    className?: string;
 *    config: {
 *        readonly: boolean;
 *        holder: string;
 *        plugin: pluginConfig = { name: config | null }, // header, list, paragraph, quote, code, delimiter, table
 *        tools: {
 *            gallery: {
 *                status: boolean;
 *                trigger: (callback: (data: { url: string; caption: string }) => void) => void;
 *            },
 *            image: {
 *                status: boolean;
 *                upload: (callback: (data: { status: number; file: { url: string } }) => void) => void;
 *            }
 *        }
 *    }
 * }>}
 * @returns
 */
const EditorMD = (props) => {
  const editorRef = useRef(null);
  const onChangeRef = useRef(null);
  const changeTimeout = useRef(null);
  const editorFocus = useRef(null);
  const [editorId, setEditorId] = useState(props.id || "editor-23asd238we23d");
  const [isMounted, setIsMounted] = useState(false);
  const [isLoading, setIsLoading] = useState(true);

  const { editor: registry, registerEditor, unregisterEditor } = useEditor();

  useEffect(() => {
    // eslint-disable-next-line react-hooks/set-state-in-effect
    setIsMounted(true);
  }, []);

  useEffect(() => {
    if (props.onChange && typeof props.onChange === "function")
      onChangeRef.current = props.onChange;
  }, [props.onChange]);

  // listen change data, NOTE: only listen data on blur. not realtime change
  useEffect(() => {
    if (!isMounted || !registry?.current?.[editorId] || editorFocus.current)
      return;
    const jsonData =
      typeof props.data === "string"
        ? convertMarkdownToJSON(props.data)
        : props.data;

    registry.current[editorId].render(jsonData);
  }, [props.data, registry]);

  const filterData = (data) => {
    let temp = null;

    // gallery plugin
    if (data.blocks.length) {
      temp = {
        ...data,
        blocks: data.blocks.map((block) =>
          block.type === "gallery" ? { ...block, type: "image" } : block
        ),
      };
    }

    // any filter...

    return temp;
  };

  const onChangeData = useCallback((data) => {
    if (!onChangeRef.current) return;

    data = filterData(data);

    const markdown = convertToMarkdown(data);
    onChangeRef.current(markdown, data);
  }, []);

  useEffect(() => {
    const initEditor = async () => {
      if (!isMounted || registry.current?.[editorId]) return;

      const EditorJS = (await import("@editorjs/editorjs")).default;
      const Header = (await import("@editorjs/header")).default;
      const List = (await import("@editorjs/list")).default;
      const Paragraph = (await import("@editorjs/paragraph")).default;
      const Quote = (await import("@editorjs/quote")).default;
      const Code = (await import("@editorjs/code")).default;
      const Delimiter = (await import("@editorjs/delimiter")).default;
      const ImageTool = (await import("@editorjs/image")).default;
      const Table = (await import("@editorjs/table")).default;

      const plugins = {
        header: {
          class: Header,
          config: {
            placeholder: "Enter a header",
            levels: [1, 2, 3, 4],
            defaultLevel: 2,
          },
        },
        paragraph: {
          class: Paragraph,
          inlineToolbar: true,
        },
        table: {
          class: Table,
          inlineToolbar: true,
          config: {
            rows: 2,
            cols: 3,
            maxRows: 5,
            maxCols: 5,
          },
        },
        list: {
          class: List,
          inlineToolbar: true,
        },
        quote: {
          class: Quote,
          inlineToolbar: true,
        },
        code: Code,
        delimiter: Delimiter,
      };

      const editor = new EditorJS({
        holder: editorRef.current,
        placeholder: props?.config?.holder || "Start writing your content",
        readOnly: props?.config?.readonly || false,
        tools: {
          // Gallery, must define status true to enable
          ...(props.config?.tools?.gallery?.status
            ? {
                gallery: {
                  class: GalleryPlugin,
                  config: {
                    openModal: (callback) => {
                      // Your modal opening logic here
                      // When user selects an image, call:
                      // callback({ url: 'image-url.jpg', caption: 'Optional caption' })
                      // Example:
                      // openYourGalleryModal((selectedImage) => {
                      //   callback({
                      //     url: selectedImage.url,
                      //     caption: selectedImage.title,
                      //   });
                      // });

                      const galleryConfig = props?.config?.tools?.gallery;

                      if (typeof galleryConfig.trigger === "function")
                        galleryConfig.trigger((data) => {
                          callback({
                            url: data.url,
                            caption: data.caption,
                          });
                        });
                    },
                  },
                },
              }
            : {}),
          // Image, must define status true to enable
          ...(props.config?.tools?.image?.status
            ? {
                image: {
                  class: ImageTool,
                  config: {
                    uploader: {
                      uploadByFile(file) {
                        return new Promise(async (resolve) => {
                          if (
                            typeof props?.config?.tools?.image?.upload ===
                            "function"
                          ) {
                            props?.config?.tools?.image?.upload(
                              file,
                              (
                                data = {
                                  success: 0,
                                  file: { url: "" },
                                  error: null,
                                }
                              ) => {
                                resolve(data);
                              }
                            );

                            return;
                          }

                          const reader = new FileReader();
                          reader.onload = (e) => {
                            resolve({
                              success: 1,
                              file: {
                                url: e.target.result,
                              },
                            });
                          };
                          reader.readAsDataURL(file);
                        });
                      },
                      uploadByUrl(url) {
                        return Promise.resolve({
                          success: 1,
                          file: {
                            url: url,
                          },
                        });
                      },
                    },
                    feature: {
                      caption: "optional",
                    },
                  },
                },
              }
            : {}),
          // Other plugins, if plugin is defined filter by config
          ...(props.config?.plugin
            ? Object.fromEntries(
                Object.entries(plugins)
                  .filter(([k, _]) =>
                    Object.keys(props.config.plugin).includes(k)
                  )
                  .map(([k, v]) => [
                    k,
                    { ...v, ...(props.config.plugin?.[k] || {}) },
                  ])
              )
            : plugins),
        },
        data:
          typeof props.data === "string"
            ? convertMarkdownToJSON(props.data)
            : props.data,
        onChange: (api, event) => {
          if (changeTimeout.current) clearTimeout(changeTimeout.current);

          changeTimeout.current = setTimeout(async () => {
            if (!editor || typeof editor.save !== "function") return;

            const data = await editor.save();
            onChangeData(data);
          }, 1000);
        },
        onReady: () => {
          setIsLoading(false);

          if (props.onReady && typeof props.onReady === "function")
            props.onReady(editorRef.current, editor, editorId);

          const editorElement = document.querySelector(".codex-editor");

          // Focus
          editorElement.addEventListener("focusin", (e) => {
            if (props.onFocus && typeof props.onFocus === "function")
              props.onFocus(e);
          });

          // Blur
          editorElement.addEventListener("focusout", (e) => {
            if (props.onBlur && typeof props.onBlur === "function")
              props.onBlur(e);
          });
        },
      });

      registerEditor({ id: editorId, editor, element: editorRef.current });
    };

    initEditor();

    return () => {
      unregisterEditor(editorId);
    };
  }, [isMounted, registry, editorId]);

  return (
    <div className={`container ${props.className}`}>
      {isLoading && (
        <div className="loading">
          <div className="spinner"></div>
          Loading your content
        </div>
      )}
      <div
        ref={editorRef}
        // className={props.className}
        onFocus={() => {
          editorFocus.current = true;
        }}
        onBlur={() => {
          editorFocus.current = false;
        }}
      />
    </div>
  );
};

export default EditorMD;
