
import { DEBUGGING, debug } from "src/utils/debugHelper";
import ExcalidrawView from "../ExcalidrawView";
import { App, Notice, TFile } from "obsidian";
import { AppState, ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
import { DEVICE, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS, viewportCoordsToSceneCoords } from "src/constants/constants";
import { internalDragModifierType, isWinCTRLorMacCMD, localFileDragModifierType, modifierKeyTooltipMessages, webbrowserDragModifierType } from "src/utils/modifierkeyHelper";
import { errorlog, hyperlinkIsImage, hyperlinkIsYouTubeLink } from "src/utils/utils";
import { InsertPDFModal } from "src/shared/Dialogs/InsertPDFModal";
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
import { getEA } from "src/core";
import { insertEmbeddableToView, insertImageToView } from "src/utils/excalidrawViewUtils";
import { t } from "src/lang/helpers";
import ExcalidrawPlugin from "src/core/main";
import { getInternalLinkOrFileURLLink, getURLImageExtension, importFileToVault } from "src/utils/fileUtils";
import { ScriptEngine } from "src/shared/Scripts";
import { UniversalInsertFileModal } from "src/shared/Dialogs/UniversalInsertFileModal";
import { Position } from "src/types/excalidrawViewTypes";

/*
static getDropAction(event: DragEvent): string {
  // Get modifier action
}

static parseDropData(event: DragEvent): DropData {
  // Parse drop data into clean format
}

static handleInternalDrop(data: DropData, context: DropContext): boolean {
  // Handle Obsidian internal file drops
}

static handleExternalFileDrop(data: DropData, context: DropContext): boolean {
  // Handle external file drops
}

static handleTextDrop(data: DropData, context: DropContext): boolean {
  // Handle text/url drops
}*/

export class DropManager {
  private view: ExcalidrawView;
  private app: App;
  private draginfoDiv: HTMLDivElement;
  
  constructor(view: ExcalidrawView) {
    this.view = view;
    this.app = this.view.app;
  }

  public destroy() {
    if(this.draginfoDiv) {
      this.ownerDocument.body.removeChild(this.draginfoDiv);
      delete this.draginfoDiv;
    }
  }

  get ownerDocument(): Document {
    return this.view.ownerDocument;
  }

  get currentPosition(): Position {
    return this.view.currentPosition;
  }

  set currentPosition(pos: Position) {
    this.view.currentPosition = pos;
  }

  get excalidrawAPI():ExcalidrawImperativeAPI {
    return this.view.excalidrawAPI;
  }

  get plugin(): ExcalidrawPlugin {
    return this.view.plugin;
  }

  get file(): TFile {
    return this.view.file;
  }

  public onDrop (event: React.DragEvent<HTMLDivElement>): boolean {
    (process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onDrop, "ExcalidrawView.onDrop", event);
    if(this.draginfoDiv) {
      this.ownerDocument.body.removeChild(this.draginfoDiv);
      delete this.draginfoDiv;
    }
    const api = this.excalidrawAPI;
    if (!api) {
      return false;
    }
    const st: AppState = api.getAppState();
    this.currentPosition = viewportCoordsToSceneCoords(
      { clientX: event.clientX, clientY: event.clientY },
      st,
    );
    const draggable = (this.app as any).dragManager.draggable;
    const internalDragAction = internalDragModifierType(event);
    const externalDragAction = webbrowserDragModifierType(event);
    const localFileDragAction = localFileDragModifierType(event);

    //Call Excalidraw Automate onDropHook
    const onDropHook = (
      type: "file" | "text" | "unknown",
      files: TFile[],
      text: string,
    ): boolean => {
      this.app.workspace.setActiveLeaf(this.view.leaf); //ensure the view is active, so ModalContainerObserver is active
      if (this.view.getHookServer().onDropHook) {
        try {
          return this.view.getHookServer().onDropHook({
            ea: this.view.getHookServer(), //the ExcalidrawAutomate object
            event, //React.DragEvent<HTMLDivElement>
            draggable, //Obsidian draggable object
            type, //"file"|"text"
            payload: {
              files, //TFile[] array of dropped files
              text, //string
            },
            excalidrawFile: this.file, //the file receiving the drop event
            view: this.view, //the excalidraw view receiving the drop
            pointerPosition: this.currentPosition, //the pointer position on canvas at the time of drop
          });
        } catch (e) {
          new Notice("on drop hook error. See console log for details");
          errorlog({ where: "ExcalidrawView.onDrop", error: e });
          return false;
        }
      } else {
        return false;
      }
    };

    //---------------------------------------------------------------------------------
    // Obsidian internal drag event
    //---------------------------------------------------------------------------------
    switch (draggable?.type) {
      case "link":
      case "file":
        if (!onDropHook("file", [draggable.file], null)) {
          const file:TFile = draggable.file;
          //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/422
          if (file.path.match(REG_LINKINDEX_INVALIDCHARS)) {
            new Notice(t("FILENAME_INVALID_CHARS"), 4000);
            return false;
          }
          if (
            ["image", "image-fullsize"].contains(internalDragAction) && 
            (IMAGE_TYPES.contains(file.extension) ||
              file.extension === "md"  ||
              file.extension.toLowerCase() === "pdf" )
          ) {
            if(file.extension.toLowerCase() === "pdf") {
              const insertPDFModal = new InsertPDFModal(this.plugin, this.view);
              insertPDFModal.open(file);
            } else {
              (async () => {
                const ea: ExcalidrawAutomate = getEA(this.view);
                ea.selectElementsInView([
                  await insertImageToView(
                    ea,
                    this.currentPosition,
                    file,
                    !(internalDragAction==="image-fullsize")
                  )
                ]);
                ea.destroy();
              })();
            }
            return false;
          }
          
          if (internalDragAction === "embeddable") {
            (async () => {
              const ea: ExcalidrawAutomate = getEA(this.view);
              ea.selectElementsInView([
                await insertEmbeddableToView(
                  ea,
                  this.currentPosition,
                  file,
                )
              ]);
              ea.destroy();
            })();
            return false;
          }

          //internalDragAction === "link"
          this.view.addText(
            `[[${this.app.metadataCache.fileToLinktext(
              draggable.file,
              this.file.path,
              true,
            )}]]`,
          );
        }
        return false;
      case "files":
        if (!onDropHook("file", draggable.files, null)) {
          (async () => {
            if (["image", "image-fullsize"].contains(internalDragAction)) {
              const ea:ExcalidrawAutomate = getEA(this.view);
              ea.canvas.theme = api.getAppState().theme;
              let counter:number = 0;
              const ids:string[] = [];
              for (const f of draggable.files) {
                if ((IMAGE_TYPES.contains(f.extension) || f.extension === "md")) {
                  ids.push(await ea.addImage(
                    this.currentPosition.x + counter*50,
                    this.currentPosition.y + counter*50,
                    f,
                    !(internalDragAction==="image-fullsize"),
                  ));
                  counter++;
                  await ea.addElementsToView(false, false, true);
                  ea.selectElementsInView(ids);
                }
                if (f.extension.toLowerCase() === "pdf") {
                  const insertPDFModal = new InsertPDFModal(this.plugin, this.view);
                  insertPDFModal.open(f);
                }
              }
              ea.destroy();
              return;
            }

            if (internalDragAction === "embeddable") {
              const ea:ExcalidrawAutomate = getEA(this.view);
              let column:number = 0;
              let row:number = 0;
              const ids:string[] = [];
              for (const f of draggable.files) {
                ids.push(await insertEmbeddableToView(
                  ea,
                  {
                    x:this.currentPosition.x + column*500,
                    y:this.currentPosition.y + row*550
                  },
                  f,
                ));
                column = (column + 1) % 3;
                if(column === 0) {
                  row++;
                }
              }
              ea.destroy();
              return false;
            }

            //internalDragAction === "link"
            for (const f of draggable.files) {
              await this.view.addText(
                `[[${this.app.metadataCache.fileToLinktext(
                  f,
                  this.file.path,
                  true,
                )}]]`, undefined,false
              );
              this.currentPosition.y += st.currentItemFontSize * 2;
            }
            this.view.save(false);
          })();
        }
        return false;
    }

    //---------------------------------------------------------------------------------
    // externalDragAction
    //---------------------------------------------------------------------------------
    if (event.dataTransfer.types.includes("Files")) {
      if (event.dataTransfer.types.includes("text/plain")) {
        const text: string = event.dataTransfer.getData("text");
        if (text && onDropHook("text", null, text)) {
          return false;
        }
        if(text && (externalDragAction === "image-url") && hyperlinkIsImage(text)) {
          this.view.addImageWithURL(text);
          return false;
        }
        if(text && (externalDragAction === "link")) {
          if (
            this.plugin.settings.iframelyAllowed &&
            text.match(/^https?:\/\/\S*$/)
          ) {
            this.view.addTextWithIframely(text);
            return false;
          } else {
            this.view.addText(text);
            return false;
          }
        }
        if(text && (externalDragAction === "embeddable")) {
          const ea = getEA(this.view) as ExcalidrawAutomate;
          insertEmbeddableToView(
            ea,
            this.currentPosition,
            undefined,
            text,
          ).then(()=>ea.destroy());
          return false;
        }
      }

      if(event.dataTransfer.types.includes("text/html")) {
        const html = event.dataTransfer.getData("text/html");
        const src = html.match(/src=["']([^"']*)["']/)
        if(src && (externalDragAction === "image-url") && hyperlinkIsImage(src[1])) {
          this.view.addImageWithURL(src[1]);
          return false;
        }
        if(src && (externalDragAction === "link")) {
          if (
            this.plugin.settings.iframelyAllowed &&
            src[1].match(/^https?:\/\/\S*$/)
          ) {
            this.view.addTextWithIframely(src[1]);
            return false;
          } else {
            this.view.addText(src[1]);
            return false;
          }
        }
        if(src && (externalDragAction === "embeddable")) {
          const ea = getEA(this.view) as ExcalidrawAutomate;
          insertEmbeddableToView(
            ea,
            this.currentPosition,
            undefined,
            src[1],
          ).then(ea.destroy);
          return false;
        }
      }
      
      if (event.dataTransfer.types.length >= 1 && ["image-url","image-import","embeddable"].contains(localFileDragAction)) {
        const files = Array.from(event.dataTransfer.files || []);
        
        for(let i = 0; i < files.length; i++) {
          // Try multiple ways to get file path
          const file = files[i];
          let path = file?.path

          if(!path && file && DEVICE.isDesktop) {
            //https://www.electronjs.org/docs/latest/breaking-changes#removed-filepath
            const { webUtils } = require('electron');
            if(webUtils && webUtils.getPathForFile) {
              path = webUtils.getPathForFile(file);
            }
          }
          if(!path) {            
            new Notice(t("ERROR_CANT_READ_FILEPATH"),6000);
            return true; //excalidraw to continue processing
          }
          const link = getInternalLinkOrFileURLLink(path, this.plugin, event.dataTransfer.files[i].name, this.file);
          const {x,y} = this.currentPosition;
          const pos = {x:x+i*300, y:y+i*300};
          if(link.isInternal) {
            if(localFileDragAction === "embeddable") {
              const ea = getEA(this.view) as ExcalidrawAutomate;
              insertEmbeddableToView(ea, pos, link.file).then(()=>ea.destroy());
            } else {
              if(link.file.extension === "pdf") {
                const insertPDFModal = new InsertPDFModal(this.plugin, this.view);
                insertPDFModal.open(link.file);
              }
              const ea = getEA(this.view) as ExcalidrawAutomate;
              insertImageToView(ea, pos, link.file).then(()=>ea.destroy()) ;
            }
          } else {
            const extension = getURLImageExtension(link.url);
            if(localFileDragAction === "image-import") {
              if (IMAGE_TYPES.contains(extension)) {
                (async () => {
                  const droppedFilename = event.dataTransfer.files[i].name;
                  const fileToImport = await event.dataTransfer.files[i].arrayBuffer();
                  const maybeFile = this.app.metadataCache.getFirstLinkpathDest(droppedFilename, this.file.path);
                  if(maybeFile && maybeFile instanceof TFile) {
                    const action = await ScriptEngine.suggester(
                      this.app,[
                        "Use the file already in the Vault instead of importing",
                        "Overwrite existing file in the Vault",
                        "Import the file with a new name",
                      ],[
                        "Use",
                        "Overwrite",
                        "Import",
                      ],
                      "A file with the same name/path already exists in the Vault\n" +
                      maybeFile.path,
                    );
                    switch(action) {
                      case "Import":
                        break;
                        case "Overwrite":
                          await this.app.vault.modifyBinary(maybeFile, fileToImport);
                          // there is deliberately no break here
                      case "Use":
                      default:
                        const ea = getEA(this.view) as ExcalidrawAutomate;
                        await insertImageToView(ea, pos, maybeFile);
                        ea.destroy();
                        return false;
                    }
                  }
                  const file = await importFileToVault(
                    this.app,
                    droppedFilename,
                    fileToImport,
                    this.view.file,
                    this.view
                  );
                  const ea = getEA(this.view) as ExcalidrawAutomate;
                  await insertImageToView(ea, pos, file);
                  ea.destroy();
                })();
              } else if(extension === "excalidraw") {
                return true; //excalidraw to continue processing
              } else {
                (async () => {
                  const file = await importFileToVault(
                    this.app,
                    event.dataTransfer.files[i].name,
                    await event.dataTransfer.files[i].arrayBuffer(),
                    this.view.file,
                    this.view,
                  );
                  const modal = new UniversalInsertFileModal(this.plugin, this.view);
                  modal.open(file, pos);
                })();
              }
            }
            else if(localFileDragAction === "embeddable" || !IMAGE_TYPES.contains(extension)) {
              const ea = getEA(this.view) as ExcalidrawAutomate;
              insertEmbeddableToView(ea, pos, null, link.url).then(()=>ea.destroy());
              if(localFileDragAction !== "embeddable") {
                new Notice("Not imported to Vault. Embedded with local URI");
              }
            } else {
              const ea = getEA(this.view) as ExcalidrawAutomate;
              insertImageToView(ea, pos, link.url).then(()=>ea.destroy());
            }
          }
        };
        return false;
      }

      if(event.dataTransfer.types.length >= 1 && localFileDragAction === "link") {
        const ea = getEA(this.view) as ExcalidrawAutomate;
        for(let i=0;i<event.dataTransfer.files.length;i++) {
          const file = event.dataTransfer.files[i];
          let path = file?.path;
          const name = file?.name;
          if(!path && file && DEVICE.isDesktop) {
            //https://www.electronjs.org/docs/latest/breaking-changes#removed-filepath
            const { webUtils } = require('electron');
            if(webUtils && webUtils.getPathForFile) {
              path = webUtils.getPathForFile(file);
            }
          }
          if(!path || !name) {
            new Notice(t("ERROR_CANT_READ_FILEPATH"),6000);
            ea.destroy();
            return true; //excalidraw to continue processing
          }
          const link = getInternalLinkOrFileURLLink(path, this.plugin, name, this.file);
          const id = ea.addText(
            this.currentPosition.x+i*40,
            this.currentPosition.y+i*20,
            link.isInternal ? link.link :`📂 ${name}`);
          if(!link.isInternal) {
            ea.getElement(id).link = link.link;
          }
        }
        ea.addElementsToView().then(()=>ea.destroy());
        return false;
      }

      return true;
    }

    if (event.dataTransfer.types.includes("text/plain") || event.dataTransfer.types.includes("text/uri-list") || event.dataTransfer.types.includes("text/html")) {

      const html = event.dataTransfer.getData("text/html");
      const src = html.match(/src=["']([^"']*)["']/);
      const htmlText = src ? src[1] : "";
      const textText = event.dataTransfer.getData("text");
      const uriText = event.dataTransfer.getData("text/uri-list");

      let text: string = src ? htmlText : textText;
      if (!text || text === "") {
        text = uriText
      }
      if (!text || text === "") {
        return true;
      }
      if (!onDropHook("text", null, text)) {
        if(text && (externalDragAction==="embeddable") && /^(blob:)?(http|https):\/\/[^\s/$.?#].[^\s]*$/.test(text)) {
          return true;
        }
        if(text && (externalDragAction==="image-url") && hyperlinkIsYouTubeLink(text)) {
          this.view.addYouTubeThumbnail(text);
          return false;
        }
        if(uriText && (externalDragAction==="image-url") && hyperlinkIsYouTubeLink(uriText)) {
          this.view.addYouTubeThumbnail(uriText);
          return false;
        }
        if(text && (externalDragAction==="image-url") && hyperlinkIsImage(text)) {
          this.view.addImageWithURL(text);
          return false;
        }
        if(uriText && (externalDragAction==="image-url") && hyperlinkIsImage(uriText)) {
          this.view.addImageWithURL(uriText);
          return false;
        }
        if(text && (externalDragAction==="image-import") && hyperlinkIsImage(text)) {
          this.view.addImageSaveToVault(text);
          return false;
        }
        if(uriText && (externalDragAction==="image-import") && hyperlinkIsImage(uriText)) {
          this.view.addImageSaveToVault(uriText);
          return false;
        }
        if (
          this.plugin.settings.iframelyAllowed &&
          text.match(/^https?:\/\/\S*$/)
        ) {
          this.view.addTextWithIframely(text);
          return false;
        }
        //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/599
        if(text.startsWith("obsidian://open?vault=")) {
          const html = event.dataTransfer.getData("text/html");
          if(html) {
            const path = html.match(/href="app:\/\/obsidian\.md\/(.*?)"/);
            if(path.length === 2) {
              const link = decodeURIComponent(path[1]).split("#");
              const f = this.app.vault.getAbstractFileByPath(link[0]);
              if(f && f instanceof TFile) {
                const path = this.app.metadataCache.fileToLinktext(f,this.file.path);
                this.view.addText(`[[${
                  path +
                  (link.length>1 ? "#" + link[1] + "|" + path : "")
                }]]`);
                return;
              }
              this.view.addText(`[[${decodeURIComponent(path[1])}]]`);
              return false;  
            }
          }
          const path = text.split("file=");
          if(path.length === 2) {
            this.view.addText(`[[${decodeURIComponent(path[1])}]]`);
            return false;
          }
        }
        this.view.addText(text.replace(/(!\[\[.*#[^\]]*\]\])/g, "$1{40}"));
      }
      return false;
    }
    if (onDropHook("unknown", null, null)) {
      return false;
    }
    return true;
  }

  public onDragOver(e: any) {
    const action = this.dropAction(e.dataTransfer);
    if (action) {
      if(!this.draginfoDiv) {
        this.draginfoDiv = createDiv({cls:"excalidraw-draginfo"});
        this.ownerDocument.body.appendChild(this.draginfoDiv);
      }
      let msg: string = "";
      if((this.app as any).dragManager.draggable) {
        //drag from Obsidian file manager
        msg = modifierKeyTooltipMessages().InternalDragAction[internalDragModifierType(e)];
      } else if(e.dataTransfer.types.length === 1 && e.dataTransfer.types.includes("Files")) {
        //drag from OS file manager
        msg = modifierKeyTooltipMessages().LocalFileDragAction[localFileDragModifierType(e)];
        if(DEVICE.isMacOS && isWinCTRLorMacCMD(e)) {
          msg = "CMD is reserved by MacOS for file system drag actions.\nCan't use it in Obsidian.\nUse a combination of SHIFT, CTRL, OPT instead."
        }
      } else {
        //drag from Internet
        msg = modifierKeyTooltipMessages().WebBrowserDragAction[webbrowserDragModifierType(e)];
      }
      if(!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
        msg += DEVICE.isMacOS || DEVICE.isIOS
        ? "\nTry SHIFT, OPT, CTRL combinations for other drop actions" 
        : "\nTry SHIFT, CTRL, ALT, Meta combinations for other drop actions";
      }
      if(this.draginfoDiv.innerText !== msg) this.draginfoDiv.innerText = msg;
      const top = `${e.clientY-parseFloat(getComputedStyle(this.draginfoDiv).fontSize)*8}px`;
      const left = `${e.clientX-this.draginfoDiv.clientWidth/2}px`;
      if(this.draginfoDiv.style.top !== top) this.draginfoDiv.style.top = top;
      if(this.draginfoDiv.style.left !== left) this.draginfoDiv.style.left = left;
      e.dataTransfer.dropEffect = action;
      e.preventDefault();
      return false;
    }
  }

  public onDragLeave() {
    if(this.draginfoDiv) {
      this.ownerDocument.body.removeChild(this.draginfoDiv);
      delete this.draginfoDiv;
    }
  }

  private dropAction(transfer: DataTransfer) {
    (process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.dropAction, "ExcalidrawView.dropAction");
    // Return a 'copy' or 'link' action according to the content types, or undefined if no recognized type
    const files = (this.app as any).dragManager.draggable?.files;
    if (files) {
      if (files[0] == this.file) {
        files.shift();
        (
          this.app as any
        ).dragManager.draggable.title = `${files.length} files`;
      }
    }
    if (
      ["file", "files"].includes(
        (this.app as any).dragManager.draggable?.type,
      )
    ) {
      return "link";
    }
    if (
      transfer.types?.includes("text/html") ||
      transfer.types?.includes("text/plain") ||
      transfer.types?.includes("Files")
    ) {
      return "copy";
    }
  };
}
