import type { callback } from "../types.ts";
import { colorize } from "./style.ts";
import { TerminalBlock } 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 = "";

  // Function to handle input
  async function handleInput() {
    const buf = new Uint8Array(3); // arrow keys send 3 bytes
    while (true) {
      renderMenu();
      const n = await Deno.stdin.read(buf);
      if (n === null) break;

      const [a, b, c] = buf;

      if (a === 3) {
        Deno.stdin.setRaw(false);
        terminalBlock?.["layout"]?.clearAll();
        Deno.exit(130);
      }

      if (a === 13) { // Enter key
        if (inputBuffer) {
          const parsed = parseInt(inputBuffer);
          if (!isNaN(parsed)) {
            selected = parsed - 1;
          }
          inputBuffer = "";
        }
        break;
      } else if (a === 27 && b === 91) { // Arrow keys
        inputBuffer = "";
        if (c === 65) { // Up
          selected = (selected - 1 + options.length) % options.length;
        } else if (c === 66) { // Down
          selected = (selected + 1) % options.length;
        }
      } else if (a >= 48 && a <= 57) {
        inputBuffer += String.fromCharCode(a);
      } else if (a === 8) {
        inputBuffer = inputBuffer.slice(0, -1);
      }
    }

    Deno.stdin.setRaw(false);
    return options[selected];
  }
  terminalBlock.setLines(["Selected: " + options[selected]], range);
  return await handleInput();
}

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

  const rawValues = new Set(
    options.map((i) => typeof i === "string" ? i : i[0]),
  ).values().toArray();

  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);
  }

  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);
  }

  // Function to handle input
  async function handleInput() {
    const buf = new Uint8Array(3); // arrow keys send 3 bytes
    while (true) {
      renderMenu();
      const n = await Deno.stdin.read(buf);
      if (n === null) break;

      const [a, b, c] = buf;

      if (a === 3) {
        Deno.stdin.setRaw(false);
        terminalBlock?.["layout"]?.clearAll();
        Deno.exit(130);
      }

      if (a === 13) { // Enter key
        break;
      } else if (a === 27 && b === 91) { // Arrow keys
        if (c === 65) { // Up
          selected = (selected - 1 + options.length) % options.length;
        } else if (c === 66) { // Down
          selected = (selected + 1) % options.length;
        }
      } else if (a === 32) { // Space
        Deno.stdout.writeSync(new TextEncoder().encode("\x07"));
        if (selectedOptions.includes(selected)) {
          selectedOptions = selectedOptions.filter((i) => i !== selected);
        } else {
          selectedOptions.push(selected);
        }
      }
    }

    Deno.stdin.setRaw(false);
    return selectedOptions;
  }
  const selections = await handleInput();
  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();
  // 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);
  // layout.register("post", postBlock);

  // 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",
  ]);
  console.log(val);

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