import { Client } from "../client.ts";
import {
  EditorView,
  foldAll,
  foldCode,
  toggleFold,
  Transaction,
  unfoldAll,
  unfoldCode,
  Vim,
  vimGetCm,
} from "../deps.ts";
import { SysCallMapping } from "../../plugos/system.ts";
import type { FilterOption } from "../types.ts";
import { UploadFile } from "../../plug-api/types.ts";
import { PageRef } from "$sb/lib/page.ts";
import { openSearchPanel } from "../deps.ts";
import { diffAndPrepareChanges } from "./cm_util.ts";

export function editorSyscalls(client: Client): SysCallMapping {
  const syscalls: SysCallMapping = {
    "editor.getCurrentPage": (): string => {
      return client.currentPage;
    },
    "editor.getText": () => {
      return client.editorView.state.sliceDoc();
    },
    "editor.setText": (_ctx, newText: string) => {
      const currentText = client.editorView.state.sliceDoc();
      const allChanges = diffAndPrepareChanges(currentText, newText);
      client.editorView.dispatch({
        changes: allChanges,
      });
    },
    "editor.getCursor": (): number => {
      return client.editorView.state.selection.main.from;
    },
    "editor.getSelection": (): { from: number; to: number } => {
      return client.editorView.state.selection.main;
    },
    "editor.save": () => {
      return client.save(true);
    },
    "editor.navigate": async (
      _ctx,
      pageRef: PageRef | string,
      replaceState = false,
      newWindow = false,
    ) => {
      if (typeof pageRef === "string") {
        pageRef = { page: pageRef };
      }
      await client.navigate(pageRef, replaceState, newWindow);
    },
    "editor.reloadPage": async () => {
      await client.reloadPage();
    },
    "editor.reloadUI": () => {
      location.reload();
    },
    "editor.reloadSettingsAndCommands": async () => {
      await client.loadSettings();
      await client.system.commandHook.buildAllCommands();
    },
    "editor.openUrl": (_ctx, url: string, existingWindow = false) => {
      if (!existingWindow) {
        const win = window.open(url, "_blank");
        if (win) {
          win.focus();
        }
      } else {
        location.href = url;
      }
    },
    "editor.downloadFile": (_ctx, filename: string, dataUrl: string) => {
      const link = document.createElement("a");
      link.href = dataUrl;
      link.download = filename;
      link.click();
    },
    "editor.uploadFile": (
      _ctx,
      accept?: string,
      capture?: string,
    ): Promise<UploadFile> => {
      return new Promise<UploadFile>((resolve, reject) => {
        const input = document.createElement("input");
        input.type = "file";
        if (accept) {
          input.accept = accept;
        }
        if (capture) {
          input.capture = capture;
        }

        input.onchange = () => {
          const file = input.files?.item(0);
          if (!file) {
            reject(new Error("No file found"));
          } else {
            const reader = new FileReader();
            reader.readAsArrayBuffer(file);
            reader.onloadend = async (evt) => {
              if (evt.target?.readyState == FileReader.DONE) {
                resolve({
                  name: file.name,
                  contentType: file.type,
                  content: new Uint8Array(await file.arrayBuffer()),
                });
              }
            };
            reader.onabort = (e) => {
              reject(e);
            };
            reader.onerror = (e) => {
              reject(e);
            };
          }
        };
        input.onabort = (e) => {
          reject(e);
        };

        input.click();
      });
    },
    "editor.flashNotification": (
      _ctx,
      message: string,
      type: "error" | "info" = "info",
    ) => {
      client.flashNotification(message, type);
    },
    "editor.filterBox": (
      _ctx,
      label: string,
      options: FilterOption[],
      helpText = "",
      placeHolder = "",
    ): Promise<FilterOption | undefined> => {
      return client.filterBox(label, options, helpText, placeHolder);
    },
    "editor.showPanel": (
      _ctx,
      id: string,
      mode: number,
      html: string,
      script: string,
    ) => {
      client.ui.viewDispatch({
        type: "show-panel",
        id: id as any,
        config: { html, script, mode },
      });
      setTimeout(() => {
        // Dummy dispatch to rerender the editor and toggle the panel
        client.editorView.dispatch({});
      });
    },
    "editor.hidePanel": (_ctx, id: string) => {
      client.ui.viewDispatch({
        type: "hide-panel",
        id: id as any,
      });
      setTimeout(() => {
        // Dummy dispatch to rerender the editor and toggle the panel
        client.editorView.dispatch({});
      });
    },
    "editor.insertAtPos": (_ctx, text: string, pos: number) => {
      client.editorView.dispatch({
        changes: {
          insert: text,
          from: pos,
        },
      });
    },
    "editor.replaceRange": (_ctx, from: number, to: number, text: string) => {
      client.editorView.dispatch({
        changes: {
          insert: text,
          from: from,
          to: to,
        },
      });
    },
    "editor.moveCursor": (_ctx, pos: number, center = false) => {
      client.editorView.dispatch({
        selection: {
          anchor: pos,
        },
      });
      if (center) {
        client.editorView.dispatch({
          effects: [
            EditorView.scrollIntoView(
              pos,
              {
                y: "center",
              },
            ),
          ],
        });
      }
      client.editorView.focus();
    },
    "editor.setSelection": (_ctx, from: number, to: number) => {
      client.editorView.dispatch({
        selection: {
          anchor: from,
          head: to,
        },
      });
    },

    "editor.insertAtCursor": (_ctx, text: string) => {
      const editorView = client.editorView;
      const from = editorView.state.selection.main.from;
      editorView.dispatch({
        changes: {
          insert: text,
          from: from,
        },
        selection: {
          anchor: from + text.length,
        },
      });
    },
    "editor.dispatch": (_ctx, change: Transaction) => {
      client.editorView.dispatch(change);
    },
    "editor.prompt": (
      _ctx,
      message: string,
      defaultValue = "",
    ): Promise<string | undefined> => {
      return client.prompt(message, defaultValue);
    },
    "editor.confirm": (_ctx, message: string): Promise<boolean> => {
      return client.confirm(message);
    },
    "editor.getUiOption": (_ctx, key: string): any => {
      return (client.ui.viewState.uiOptions as any)[key];
    },
    "editor.setUiOption": (_ctx, key: string, value: any) => {
      client.ui.viewDispatch({
        type: "set-ui-option",
        key,
        value,
      });
    },
    "editor.vimEx": (_ctx, exCommand: string) => {
      const cm = vimGetCm(client.editorView)!;
      return Vim.handleEx(cm, exCommand);
    },
    "editor.openPageNavigator": (_ctx, mode: "page" | "template" = "page") => {
      client.startPageNavigate(mode);
    },
    "editor.openCommandPalette": () => {
      client.ui.viewDispatch({
        type: "show-palette",
      });
    },
    // Folding
    "editor.fold": () => {
      foldCode(client.editorView);
    },
    "editor.unfold": () => {
      unfoldCode(client.editorView);
    },
    "editor.toggleFold": () => {
      toggleFold(client.editorView);
    },
    "editor.foldAll": () => {
      foldAll(client.editorView);
    },
    "editor.unfoldAll": () => {
      unfoldAll(client.editorView);
    },
    "editor.openSearchPanel": () => {
      openSearchPanel(client.editorView);
    },
  };

  return syscalls;
}