import { useEffect } from "react";
import { useUIState } from "src/useUIState";
import { useEditorStates } from "src/state/useEditorState";

import { isTextBlock } from "src/utils/isTextBlock";
import { focusBlock } from "src/utils/focusBlock";
import { elementId } from "src/utils/elementId";
import { indent, outdent } from "src/utils/list-operations";
import { generateKeyBetween } from "fractional-indexing";
import { v7 } from "uuid";
import { BlockProps } from "./Block";
import { ReplicacheMutators, useEntity, useReplicache } from "src/replicache";
import { useEntitySetContext } from "components/EntitySetProvider";
import { Replicache } from "replicache";
import { deleteBlock } from "./DeleteBlock";
import { entities } from "drizzle/schema";
import { scanIndex } from "src/replicache/utils";

export function useBlockKeyboardHandlers(
  props: BlockProps,
  areYouSure: boolean,
  setAreYouSure: (value: boolean) => void,
) {
  let { rep, undoManager } = useReplicache();
  let entity_set = useEntitySetContext();
  let isLocked = !!useEntity(props.entityID, "block/is-locked")?.data.value;

  let isSelected = useUIState((s) => {
    let selectedBlocks = s.selectedBlocks;
    return !!s.selectedBlocks.find((b) => b.value === props.entityID);
  });

  useEffect(() => {
    if (!isSelected || !rep) return;
    let listener = async (e: KeyboardEvent) => {
      // keymapping for textBlocks is handled in TextBlock/keymap
      if (e.defaultPrevented) return;
      //if no permissions, do nothing
      if (!entity_set.permissions.write) return;
      let command = {
        Tab,
        ArrowUp,
        ArrowDown,
        Backspace,
        Enter,
        Escape,
        j,
        k,
      }[e.key];

      let el = e.target as HTMLElement;
      if (
        (el.tagName === "LABEL" ||
          el.tagName === "INPUT" ||
          el.tagName === "TEXTAREA" ||
          el.tagName === "SELECT" ||
          el.contentEditable === "true") &&
        !isTextBlock[props.type]
      ) {
        if ((el as HTMLInputElement).value !== "" || e.key === "Tab") return;
      }
      if (!AllowedIfTextBlock.includes(e.key) && isTextBlock[props.type])
        return;

      undoManager.startGroup();
      command?.({
        e,
        props,
        rep,
        entity_set,
        areYouSure,
        setAreYouSure,
        isLocked,
      });
      undoManager.endGroup();
    };
    window.addEventListener("keydown", listener);
    return () => window.removeEventListener("keydown", listener);
  }, [entity_set, isSelected, props, rep, areYouSure, setAreYouSure, isLocked]);
}

type Args = {
  e: KeyboardEvent;
  isLocked: boolean;
  props: BlockProps;
  rep: Replicache<ReplicacheMutators>;
  entity_set: { set: string };
  areYouSure: boolean;
  setAreYouSure: (value: boolean) => void;
};

const AllowedIfTextBlock = ["Tab"];

function Tab({ e, props, rep }: Args) {
  // if tab or shift tab, indent or outdent
  if (useUIState.getState().selectedBlocks.length > 1) return false;
  if (e.shiftKey) {
    e.preventDefault();
    outdent(props, props.previousBlock, rep);
  } else {
    e.preventDefault();
    if (props.previousBlock) indent(props, props.previousBlock, rep);
  }
}

function j(args: Args) {
  if (args.e.ctrlKey || args.e.metaKey) ArrowDown(args);
}
function ArrowDown({ e, props }: Args) {
  e.preventDefault();
  let nextBlock = props.nextBlock;
  if (nextBlock && useUIState.getState().selectedBlocks.length <= 1)
    focusBlock(nextBlock, {
      type: "top",
      left: useEditorStates.getState().lastXPosition,
    });
  if (!nextBlock) return;
}

function k(args: Args) {
  if (args.e.ctrlKey || args.e.metaKey) ArrowUp(args);
}
function ArrowUp({ e, props }: Args) {
  e.preventDefault();
  let prevBlock = props.previousBlock;
  if (prevBlock && useUIState.getState().selectedBlocks.length <= 1) {
    focusBlock(prevBlock, {
      type: "bottom",
      left: useEditorStates.getState().lastXPosition,
    });
  }
  if (!prevBlock) return;
}

let debounced: null | number = null;
async function Backspace({
  e,
  props,
  rep,
  areYouSure,
  setAreYouSure,
  isLocked,
}: Args) {
  // if this is a textBlock, let the textBlock/keymap handle the backspace
  if (isLocked) return;
  // if its an input, label, or teatarea with content, do nothing (do the broswer default instead)
  let el = e.target as HTMLElement;
  if (
    el.tagName === "LABEL" ||
    el.tagName === "INPUT" ||
    el.tagName === "TEXTAREA" ||
    el.contentEditable === "true"
  ) {
    if ((el as HTMLInputElement).value !== "") return;
  }

  // if the block is a card or mailbox...
  if (
    props.type === "card" ||
    props.type === "mailbox" ||
    props.type === "rsvp"
  ) {
    // ...and areYouSure state is false, set it to true
    if (!areYouSure) {
      setAreYouSure(true);
      debounced = window.setTimeout(() => {
        debounced = null;
      }, 300);
      return;
    }
    // ... and areYouSure state is true,
    // and the user is not in an input or textarea, remove it
    // if there is a page to close, close it
    if (areYouSure) {
      e.preventDefault();
      if (debounced) {
        window.clearTimeout(debounced);
        debounced = window.setTimeout(() => {
          debounced = null;
        }, 300);
        return;
      }
      return deleteBlock([props.entityID].flat(), rep);
    }
  }

  e.preventDefault();
  await rep.mutate.removeBlock({ blockEntity: props.entityID });
  useUIState.getState().closePage(props.entityID);
  let prevBlock = props.previousBlock;
  if (prevBlock) focusBlock(prevBlock, { type: "end" });
}

async function Enter({ e, props, rep, entity_set }: Args) {
  let newEntityID = v7();
  let position;
  let el = e.target as HTMLElement;
  if (
    el.tagName === "LABEL" ||
    el.tagName === "INPUT" ||
    el.tagName === "TEXTAREA" ||
    el.contentEditable === "true"
  )
    return;

  if (e.ctrlKey || e.metaKey) {
    if (props.listData) {
      rep?.mutate.toggleTodoState({
        entityID: props.entityID,
      });
    }
    return;
  }
  if (props.pageType === "canvas") {
    let el = document.getElementById(elementId.block(props.entityID).container);
    let [position] =
      (await rep?.query((tx) =>
        scanIndex(tx).vae(props.entityID, "canvas/block"),
      )) || [];
    if (!position || !el) return;

    let box = el.getBoundingClientRect();

    await rep.mutate.addCanvasBlock({
      newEntityID,
      factID: v7(),
      permission_set: entity_set.set,
      parent: props.parent,
      type: "text",
      position: {
        x: position.data.position.x,
        y: position.data.position.y + box.height + 12,
      },
    });
    focusBlock(
      { type: "text", value: newEntityID, parent: props.parent },
      { type: "start" },
    );
    return;
  }

  // if it's a list, create a new list item at the same depth
  if (props.listData) {
    let hasChild =
      props.nextBlock?.listData &&
      props.nextBlock.listData.depth > props.listData.depth;
    position = generateKeyBetween(
      hasChild ? null : props.position,
      props.nextPosition,
    );
    await rep?.mutate.addBlock({
      newEntityID,
      factID: v7(),
      permission_set: entity_set.set,
      parent: hasChild ? props.entityID : props.listData.parent,
      type: "text",
      position,
    });
    await rep?.mutate.assertFact({
      entity: newEntityID,
      attribute: "block/is-list",
      data: { type: "boolean", value: true },
    });
  }

  // if it's not a list, create a new block between current and next block
  if (!props.listData) {
    position = generateKeyBetween(props.position, props.nextPosition);
    await rep?.mutate.addBlock({
      newEntityID,
      factID: v7(),
      permission_set: entity_set.set,
      parent: props.parent,
      type: "text",
      position,
    });
  }
  setTimeout(() => {
    document.getElementById(elementId.block(newEntityID).text)?.focus();
  }, 10);
}

function Escape({ e, props, areYouSure, setAreYouSure }: Args) {
  e.preventDefault();
  if (areYouSure) {
    setAreYouSure(false);
    focusBlock(
      { type: "card", value: props.entityID, parent: props.parent },
      { type: "start" },
    );
  }

  useUIState.setState({ selectedBlocks: [] });
  useUIState.setState({
    focusedEntity: { entityType: "page", entityID: props.parent },
  });
}
