import React from "react";
import * as actions from "./actions";
import { Command } from "../../shared/Command";
import TabFlag from "../../shared/TabFlag";
import { CompletionStateContext, CompletionDispatchContext } from "./context";
import CompletionClient from "../clients/CompletionClient";
import CommandLineParser, {
  CommandLine,
  InputPhase,
} from "../commandline/CommandLineParser";
import { UnknownCommandError } from "../commandline/CommandParser";
import Completions from "../Completions";
import CompletionType from "../../shared/CompletionType";

const commandDocs = {
  [Command.Set]: "Set a value of the property",
  [Command.Open]: "Open a URL or search by keywords in current tab",
  [Command.TabOpen]: "Open a URL or search by keywords in new tab",
  [Command.WindowOpen]: "Open a URL or search by keywords in new window",
  [Command.Buffer]: "Select tabs by matched keywords",
  [Command.BufferDelete]: "Close a certain tab matched by keywords",
  [Command.BuffersDelete]: "Close all tabs matched by keywords",
  [Command.Quit]: "Close the current tab",
  [Command.QuitAll]: "Close all tabs",
  [Command.AddBookmark]: "Add current page to bookmarks",
  [Command.Help]: "Open Vim Vixen help in new tab",
};

const propertyDocs: { [key: string]: string } = {
  hintchars: "hint characters on follow mode",
  smoothscroll: "smooth scroll",
  complete: "which are completed at the open page",
  colorscheme: "color scheme of the console",
};

const completionClient = new CompletionClient();

const useDelayedCallback = <T extends unknown, U extends unknown>(
  callback: (arg1: T, arg2: U) => void,
  timeout: number
) => {
  const [timer, setTimer] = React.useState<
    ReturnType<typeof setTimeout> | undefined
  >();
  const [enabled, setEnabled] = React.useState(false);

  const enableDelay = React.useCallback(() => {
    setEnabled(true);
  }, [setEnabled]);

  const delayedCallback = React.useCallback(
    (arg1: T, arg2: U) => {
      if (enabled) {
        if (typeof timer !== "undefined") {
          clearTimeout(timer);
        }
        const id = setTimeout(() => {
          callback(arg1, arg2);
          clearTimeout(timer!);
          setTimer(undefined);
        }, timeout);
        setTimer(id);
      } else {
        callback(arg1, arg2);
      }
    },
    [enabled, timer]
  );

  return { enableDelay, delayedCallback };
};

const getCommandCompletions = async (query: string): Promise<Completions> => {
  const items = Object.entries(commandDocs)
    .filter(([name]) => name.startsWith(query))
    .map(([name, doc]) => ({
      caption: name,
      content: name,
      url: doc,
    }));
  return [
    {
      name: "Console Command",
      items,
    },
  ];
};

const getOpenCompletions = async (
  command: string,
  query: string,
  completionTypes: CompletionType[]
): Promise<Completions> => {
  const completions: Completions = [];
  for (const type of completionTypes) {
    switch (type) {
      case CompletionType.SearchEngines: {
        const items = await completionClient.requestSearchEngines(query);
        if (items.length === 0) {
          break;
        }
        completions.push({
          name: "Search Engines",
          items: items.map((key) => ({
            caption: key.title,
            content: command + " " + key.title,
          })),
        });
        break;
      }
      case CompletionType.History: {
        const items = await completionClient.requestHistory(query);
        if (items.length === 0) {
          break;
        }
        completions.push({
          name: "History",
          items: items.map((item) => ({
            caption: item.title,
            content: command + " " + item.url,
            url: item.url,
          })),
        });
        break;
      }
      case CompletionType.Bookmarks: {
        const items = await completionClient.requestBookmarks(query);
        if (items.length === 0) {
          break;
        }
        completions.push({
          name: "Bookmarks",
          items: items.map((item) => ({
            caption: item.title,
            content: command + " " + item.url,
            url: item.url,
          })),
        });
        break;
      }
    }
  }
  return completions;
};

export const getTabCompletions = async (
  command: string,
  query: string,
  excludePinned: boolean
): Promise<Completions> => {
  const items = await completionClient.requestTabs(query, excludePinned);
  if (items.length === 0) {
    return [];
  }

  return [
    {
      name: "Buffers",
      items: items.map((item) => ({
        content: command + " " + item.url,
        caption: `${item.index}: ${
          item.flag != TabFlag.None ? item.flag : " "
        } ${item.title}`,
        url: item.url,
        icon: item.faviconUrl,
      })),
    },
  ];
};

export const getPropertyCompletions = async (
  command: string,
  query: string
): Promise<Completions> => {
  const properties = await completionClient.getProperties();
  const items = properties
    .map((item) => {
      const desc = propertyDocs[item.name] || "";
      if (item.type === "boolean") {
        return [
          {
            caption: item.name,
            content: command + " " + item.name,
            url: "Enable " + desc,
          },
          {
            caption: "no" + item.name,
            content: command + " no" + item.name,
            url: "Disable " + desc,
          },
        ];
      } else {
        return [
          {
            caption: item.name,
            content: command + " " + item.name,
            url: "Set " + desc,
          },
        ];
      }
    })
    .reduce((acc, val) => acc.concat(val), [])
    .filter((item) => item.caption.startsWith(query));
  return [{ name: "Properties", items }];
};

export const useCompletions = () => {
  const state = React.useContext(CompletionStateContext);
  const dispatch = React.useContext(CompletionDispatchContext);
  const commandLineParser = React.useMemo(() => new CommandLineParser(), []);

  const updateCompletions = React.useCallback((source: string) => {
    dispatch(actions.setCompletionSource(source));
  }, []);

  const initCompletion = React.useCallback((source: string) => {
    completionClient.getCompletionTypes().then((completionTypes) => {
      dispatch(actions.initCompletion(completionTypes));
      dispatch(actions.setCompletionSource(source));
    });
  }, []);

  const { delayedCallback: queryCompletions, enableDelay } = useDelayedCallback(
    React.useCallback(
      (text: string, completionTypes?: CompletionType[]) => {
        const phase = commandLineParser.inputPhase(text);
        if (phase === InputPhase.OnCommand) {
          getCommandCompletions(text).then((completions) =>
            dispatch(actions.setCompletions(completions))
          );
        } else {
          let cmd: CommandLine | null = null;
          try {
            cmd = commandLineParser.parse(text);
          } catch (e) {
            if (e instanceof UnknownCommandError) {
              return;
            }
          }
          switch (cmd?.command) {
            case Command.Open:
            case Command.TabOpen:
            case Command.WindowOpen:
              if (!completionTypes) {
                initCompletion(text);
                return;
              }

              getOpenCompletions(cmd.command, cmd.args, completionTypes).then(
                (completions) => dispatch(actions.setCompletions(completions))
              );
              break;
            case Command.Buffer:
              getTabCompletions(cmd.command, cmd.args, false).then(
                (completions) => dispatch(actions.setCompletions(completions))
              );
              break;
            case Command.BufferDelete:
            case Command.BuffersDelete:
              getTabCompletions(cmd.command, cmd.args, true).then(
                (completions) => dispatch(actions.setCompletions(completions))
              );
              break;
            case Command.BufferDeleteForce:
            case Command.BuffersDeleteForce:
              getTabCompletions(cmd.command, cmd.args, false).then(
                (completions) => dispatch(actions.setCompletions(completions))
              );
              break;
            case Command.Set:
              getPropertyCompletions(cmd.command, cmd.args).then(
                (completions) => dispatch(actions.setCompletions(completions))
              );
              break;
          }
          enableDelay();
        }
      },
      [dispatch]
    ),
    100
  );

  React.useEffect(() => {
    queryCompletions(state.completionSource, state.completionTypes);
  }, [state.completionSource, state.completionTypes]);

  return {
    completions: state.completions,
    updateCompletions,
    initCompletion,
  };
};

export const useSelectCompletion = () => {
  const state = React.useContext(CompletionStateContext);
  const dispatch = React.useContext(CompletionDispatchContext);
  const next = React.useCallback(
    () => dispatch(actions.selectNext()),
    [dispatch]
  );
  const prev = React.useCallback(
    () => dispatch(actions.selectPrev()),
    [dispatch]
  );
  const currentValue = React.useMemo(() => {
    if (state.select < 0) {
      return state.completionSource;
    }
    const items = state.completions.map((g) => g.items).flat();
    return items[state.select]?.content || "";
  }, [state.completionSource, state.select]);

  return {
    select: state.select,
    currentValue,
    selectNext: next,
    selectPrev: prev,
  };
};