import type { callback } from "../types.ts";
import { type CLICharEvent, InputManager } from "./InputManager.ts";
import { cliLog } from "./prompts.ts";
import { colorize } from "./style.ts";
import { TerminalBlock, TerminalLayout } from "./TerminalLayout.ts";

interface ISelectMenuConfig {
  terminalBlock?: TerminalBlock;
  initialSelection?: number;
  initialSelections?: number[];
}

export function selectMenu(items: string[]) {
  const menu = items.map((i, index) => `${index + 1}. ${i}`).join("\n");
  console.log(menu);
  const index = parseInt(prompt("Please select an option:") || "1") - 1;
  return items[index];
}

export async function selectMenuInteractive(
  q: string,
  options: string[],
  config?: ISelectMenuConfig,
): Promise<string | null> {
  Deno.stdin.setRaw(true);
  let selected = 0;

  const terminalBlock = config?.terminalBlock || new TerminalBlock();
  if (!config?.terminalBlock) {
    terminalBlock.setRenderHeight(Deno.consoleSize().rows);
  }

  let range: [number, number] = [terminalBlock.lineCount, 1];
  function renderMenu() {
    const { rows } = Deno.consoleSize();
    const terminalHeight = terminalBlock.getRenderHeight() || rows;
    const maxHeight = Math.min(terminalHeight - 1, options.length);
    let startPoint = Math.max(0, selected - Math.floor(maxHeight / 2));
    const endPoint = Math.min(options.length, startPoint + maxHeight);
    if (endPoint - startPoint < maxHeight) {
      startPoint = Math.max(0, options.length - maxHeight);
    }

    const lines: string[] = [];
    lines.push(colorize(q, "green"));
    for (let i = startPoint; i < endPoint; i++) {
      const option = options[i];
      if (i === selected) {
        lines.push(`${numberAndPadding(i, ">")}${colorize(option, "porple")}`);
      } else {
        lines.push(`${numberAndPadding(i)}${option}`);
      }
    }

    range = terminalBlock.setLines(lines, range);
  }

  function numberAndPadding(i: number, prefix?: string) {
    const padded = `${i + 1}. `.padStart(
      options.length.toString().length + 4,
    );
    return prefix ? padded.replace(" ", prefix.substring(0, 1)) : padded;
  }

  let inputBuffer = "";

  const im = InputManager.getInstance();
  im.activate();

  const onUp = (e: Event) => {
    e.stopImmediatePropagation();
    selected = (selected - 1 + options.length) % options.length;
    renderMenu();
  };

  const onDown = (e: Event) => {
    e.stopImmediatePropagation();
    selected = (selected + 1) % options.length;
    renderMenu();
  };

  const onKey = (e: CLICharEvent) => {
    e.stopImmediatePropagation();
    const ke = e.detail;
    const char = String.fromCharCode(ke.key);
    inputBuffer += char;
  };

  const onBackspace = (e: Event) => {
    e.stopImmediatePropagation();
    inputBuffer = inputBuffer.slice(0, -1);
  };

  let resolve: null | ((value: string | null) => void) = null;

  const onEscape = () => {
    im.removeEventListener("arrow-up", onUp);
    im.removeEventListener("arrow-down", onDown);
    im.removeEventListener("char", onKey);
    im.removeEventListener("backspace", onBackspace);
    im.removeEventListener("enter", onEnter);
    im.removeEventListener("escape", onEscape);
    resolve?.(null);
  };

  const onEnter = (e: Event) => {
    e.stopImmediatePropagation();
    if (inputBuffer) {
      const parsed = parseInt(inputBuffer);
      if (!isNaN(parsed)) {
        selected = parsed - 1;
      }
      inputBuffer = "";
    }
    im.removeEventListener("arrow-up", onUp);
    im.removeEventListener("arrow-down", onDown);
    im.removeEventListener("char", onKey);
    im.removeEventListener("backspace", onBackspace);
    im.removeEventListener("enter", onEnter);
    im.removeEventListener("escape", onEscape);
    resolve?.(options[selected]);
  };

  renderMenu();
  const final = await new Promise<string | null>((res) => {
    resolve = res;
    im.addEventListener("char", onKey);
    im.addEventListener("backspace", onBackspace);
    im.addEventListener("enter", onEnter);
    im.addEventListener("arrow-up", onUp);
    im.addEventListener("arrow-down", onDown);
    im.addEventListener("escape", onEscape);
  });

  terminalBlock.setLines(["Selected: " + final], range);

  return final;
}

export async function multiSelectMenuInteractive(
  q: string,
  options: (string | [string, callback])[],
  config?: ISelectMenuConfig & { allOption?: boolean },
): Promise<string[] | null> {
  Deno.stdin.setRaw(true);
  let selected = 0;
  let selectedOptions: number[] = config?.initialSelections || [];

  if (config?.allOption) {
    options.unshift("Select All");
  }
  const rawValues = options.map((i) => typeof i === "string" ? i : i[0]);

  if (rawValues.length !== options.length) {
    throw new Error("Duplicate options in multi-select menu");
  }

  const terminalBlock = config?.terminalBlock || new TerminalBlock();
  if (!config?.terminalBlock) {
    terminalBlock.setRenderHeight(Deno.consoleSize().rows);
  }

  const checkSelectAll = () => {
    if (selectedOptions.includes(0)) {
      selectedOptions = [];
    } else {
      selectedOptions = Array.from(options).map((_, i) => i);
    }
  };

  const validateSelectAll = () => {
    const allPresent = selectedOptions.length == options.length;
    if (!allPresent) selectedOptions = selectedOptions.filter((e) => e != 0);
  };

  let range: [number, number] = [terminalBlock.lineCount, 1];
  function renderMenu() {
    const { rows } = Deno.consoleSize();
    const terminalHeight = terminalBlock.getRenderHeight() || rows;
    const maxHeight = Math.min(terminalHeight - 1, options.length);
    let startPoint = Math.max(0, selected - Math.floor(maxHeight / 2));
    const endPoint = Math.min(options.length, startPoint + maxHeight);
    if (endPoint - startPoint < maxHeight) {
      startPoint = Math.max(0, options.length - maxHeight);
    }

    const lines: string[] = [];
    lines.push(colorize(q, "green"));
    for (let i = startPoint; i < endPoint; i++) {
      const option = rawValues[i];
      const checkbox = selectedOptions.includes(i)
        ? colorize("◼", "green")
        : "◻";
      if (i === selected) {
        lines.push(`> ${checkbox} ${colorize(option, "porple")}`);
      } else {
        lines.push(`  ${checkbox} ${option}`);
      }
    }

    range = terminalBlock.setLines(lines, range);
  }

  const im = InputManager.getInstance();
  im.activate();

  let resolve = null as null | ((value: number[] | null) => void);

  const onUp = (e: Event) => {
    e.stopImmediatePropagation();
    selected = (selected - 1 + options.length) % options.length;
    renderMenu();
  };

  const onDown = (e: Event) => {
    e.stopImmediatePropagation();
    selected = (selected + 1) % options.length;
    renderMenu();
  };

  const onSpace = (e: CLICharEvent) => {
    if (e.detail.char !== " ") return;
    e.stopImmediatePropagation();
    if (config?.allOption && selected === 0) {
      checkSelectAll();
    } else if (selectedOptions.includes(selected)) {
      selectedOptions = selectedOptions.filter((i) => i !== selected);
    } else {
      selectedOptions.push(selected);
    }
    validateSelectAll();
    renderMenu();
  };

  const onEscape = () => {
    im.removeEventListener("arrow-up", onUp);
    im.removeEventListener("arrow-down", onDown);
    im.removeEventListener("char", onSpace);
    im.removeEventListener("enter", onEnter);
    im.removeEventListener("escape", onEscape);
    resolve?.(null);
  };

  const onEnter = (e: Event) => {
    e.stopImmediatePropagation();
    im.removeEventListener("arrow-up", onUp);
    im.removeEventListener("arrow-down", onDown);
    im.removeEventListener("char", onSpace);
    im.removeEventListener("enter", onEnter);
    im.removeEventListener("escape", onEscape);
    resolve?.(selectedOptions);
  };

  renderMenu();

  const selections = await new Promise<number[] | null>((res) => {
    resolve = res;
    im.addEventListener("arrow-up", onUp);
    im.addEventListener("arrow-down", onDown);
    im.addEventListener("char", onSpace);
    im.addEventListener("enter", onEnter);
    im.addEventListener("escape", onEscape);
  });
  if (!selections) return null;
  for (const optionI of selections) {
    const option = options[optionI];
    if (Array.isArray(option)) {
      await option[1](option[0]);
    }
  }
  const final = selectedOptions.map((i) => rawValues[i]);
  terminalBlock.setLines(["Selected: " + final.join(", ")], range);
  return final;
}

if (import.meta.main) {
  const layout = new TerminalLayout();
  const block = new TerminalBlock();
  const titleBlock = new TerminalBlock();
  const postBlock = new TerminalBlock();
  InputManager.addEventListener("exit", () => layout.clearAll());
  titleBlock.setLines(["An incredible fruit menu!"]);
  postBlock.setLines(["I'm here too!"]);
  titleBlock.setFixedHeight(1);
  postBlock.setFixedHeight(1);
  layout.register("title", titleBlock);
  layout.register("block", block);

  // const val = await selectMenuInteractive("choose a fruit", [
  //   "apple",
  //   "banana",
  //   "cherry",
  //   "date",
  //   "elderberry",
  //   "fig",
  //   "grape",
  //   "honeydew",
  //   "ilama",
  //   "jackfruit",
  //   "kiwi",
  //   "lemon",
  //   "mango",
  //   "nectarine",
  //   "orange",
  //   "papaya",
  //   "peach",
  //   "pineapple",
  //   "pomegranate",
  //   "quince",
  //   "raspberry",
  //   "strawberry",
  //   "tangerine",
  //   "watermelon",
  // ], { terminalBlock: block });
  // layout.clearAll();
  // console.log(val);

  const val = await multiSelectMenuInteractive("choose a fruit", [
    "apple",
    "banana",
    "cherry",
    "date",
    "elderberry",
    "fig",
    "grape",
    "honeydew",
    "ilama",
    "jackfruit",
    "kiwi",
    "lemon",
    "mango",
    "nectarine",
    "orange",
    "papaya",
    "quince",
    "raspberry",
    "strawberry",
    "tangerine",
    "udara",
    "vogelbeere",
    "watermelon",
    "ximenia",
    "yuzu",
    "zucchini",
  ], { terminalBlock: block, allOption: true });
  cliLog(val || "No value", block);

  // Deno.stdout.writeSync(new TextEncoder().encode("\x07"));
}
