import { Model } from "../../src";
import { NamedRangeSelector } from "../../src/components/named_range_selector/named_range_selector";
import { HIGHLIGHT_COLOR } from "../../src/constants";
import { toZone } from "../../src/helpers/zones";
import { HighlightStore } from "../../src/stores/highlight_store";
import { NotificationStore } from "../../src/stores/notification_store";
import { ViewportsStore } from "../../src/stores/viewports_store";
import { SpreadsheetChildEnv } from "../../src/types/spreadsheet_env";
import {
  createNamedRange,
  createSheet,
  hideColumns,
  hideSheet,
  setInputValueAndTrigger,
  setSelection,
  simulateClick,
  triggerMouseEvent,
} from "../test_helpers";
import { mountComponentWithPortalTarget, nextTick } from "../test_helpers/helpers";

let model: Model;
let env: SpreadsheetChildEnv;
let fixture: HTMLElement;
let raiseError: jest.Mock;
let openSidePanel: jest.Mock;

beforeEach(() => {
  model = new Model();
});

async function mountRangeSelector() {
  raiseError = jest.fn();
  openSidePanel = jest.fn();
  ({ model, env, fixture } = await mountComponentWithPortalTarget(NamedRangeSelector, {
    model,
    env: { raiseError, openSidePanel },
  }));
}

describe("Named ranges topbar selector", () => {
  test("Can create a named range from range selector", async () => {
    await mountRangeSelector();
    expect(".o-named-range-selector input").toHaveValue("A1");

    setSelection(model, ["A1:B2"]);
    await nextTick();
    expect(".o-named-range-selector input").toHaveValue("A1:B2");

    await setInputValueAndTrigger(".o-named-range-selector input", "MyRange");
    expect(model.getters.getNamedRanges()).toHaveLength(1);
    expect(model.getters.getNamedRanges()[0]).toMatchObject({
      name: "MyRange",
      range: { zone: toZone("A1:B2") },
    });
  });

  test("Can edit a named range from range selector", async () => {
    createNamedRange(model, "MyRange", "A1:B2");
    await mountRangeSelector();
    setSelection(model, ["A1:B2"]);
    await setInputValueAndTrigger(".o-named-range-selector input", "MyRange");

    await setInputValueAndTrigger(".o-named-range-selector input", "RenamedRange");
    expect(model.getters.getNamedRanges()[0]).toMatchObject({
      name: "RenamedRange",
      range: { zone: toZone("A1:B2") },
    });
  });

  test("Entering spaces with replace them by underscores", async () => {
    await mountRangeSelector();
    await setInputValueAndTrigger(".o-named-range-selector input", "Named with spaces");
    expect(model.getters.getNamedRanges()[0]).toMatchObject({ name: "Named_with_spaces" });
  });

  test("Cannot create a named range with an invalid name", async () => {
    await mountRangeSelector();
    await setInputValueAndTrigger(".o-named-range-selector input", "Wrong name !");
    expect(raiseError).toHaveBeenCalledWith(
      "The named range name is invalid. Valid names can contain letters, digits, underscores, and periods. The name cannot be only a number, TRUE, or FALSE."
    );
    expect(model.getters.getNamedRanges()).toHaveLength(0);
  });

  test("Entering a reference in the named range selector will select this reference", async () => {
    createSheet(model, { name: "Sheet2", sheetId: "sh2" });
    await mountRangeSelector();
    await setInputValueAndTrigger(".o-named-range-selector input", "Sheet2!C3:D4");
    expect(model.getters.getActiveSheetId()).toEqual("sh2");
    expect(model.getters.getSelectedZone()).toEqual(toZone("C3:D4"));
  });

  test("Entering an existing named range in the named range selector will select this range", async () => {
    createNamedRange(model, "MyRange", "A1:B2");
    createNamedRange(model, "MyRange2", "C1:C5");
    await mountRangeSelector();

    await setInputValueAndTrigger(".o-named-range-selector input", "MyRange");
    expect(model.getters.getSelectedZone()).toEqual(toZone("A1:B2"));

    await setInputValueAndTrigger(".o-named-range-selector input", "MYRANGE2");
    expect(model.getters.getSelectedZone()).toEqual(toZone("C1:C5"));
  });

  test("Can open the dropdown to select a named range", async () => {
    createSheet(model, { name: "Sheet2", sheetId: "sh2" });
    createNamedRange(model, "MyRange", "A1:B2");
    createNamedRange(model, "AnotherRange", "C3:D4", "sh2");
    await mountRangeSelector();

    await simulateClick(".o-named-range-selector [data-icon='arrow_drop_down']");
    const menuItems = [...document.querySelectorAll<HTMLElement>(".o-menu-item")];
    const getMenuItemText = (item: HTMLElement) => {
      const name = item.querySelector(".o-menu-item-name")?.textContent?.trim() || "";
      const description = item.querySelector(".o-menu-item-description")?.textContent?.trim() || "";
      return name + " " + description;
    };

    expect(menuItems.map(getMenuItemText)).toEqual([
      "MyRange Sheet1!A1:B2",
      "AnotherRange Sheet2!C3:D4",
      "Manage named ranges ",
    ]);
    await simulateClick(menuItems[1]);
    expect(model.getters.getActiveSheetId()).toEqual("sh2");
    expect(model.getters.getSelectedZone()).toEqual(toZone("C3:D4"));
  });

  test("The sheet is scrolled so the whole named range is visible when selecting a named range", async () => {
    createNamedRange(model, "MyRange", "Y60:Z70");
    await mountRangeSelector();
    const viewport = env.getStore(ViewportsStore).activeMainViewport;
    const viewportWidth = viewport.right - viewport.left;
    const viewportHeight = viewport.bottom - viewport.top;

    await simulateClick(".o-named-range-selector [data-icon='arrow_drop_down']");
    await simulateClick(".o-menu-item");

    expect(model.getters.getSelectedZone()).toEqual(toZone("Y60:Z70"));
    expect(env.getStore(ViewportsStore).activeMainViewport).toMatchObject({
      bottom: 69, // Row 70
      top: 69 - viewportHeight,
      right: 25, // Column Z
      left: 25 - viewportWidth,
    });
  });

  test("Named range is highlighted when hovering it in the dropdown", async () => {
    createNamedRange(model, "MyRange", "A1:B3");
    await mountRangeSelector();

    await simulateClick(".o-named-range-selector [data-icon='arrow_drop_down']");
    triggerMouseEvent(".o-menu-item", "mouseenter");
    await nextTick();

    const highlightStore = env.getStore(HighlightStore);
    expect(highlightStore.highlights).toMatchObject([
      { range: { zone: toZone("A1:B3") }, color: HIGHLIGHT_COLOR, noFill: true },
    ]);

    triggerMouseEvent(".o-menu-item", "mouseleave");
    await nextTick();
    expect(highlightStore.highlights).toHaveLength(0);
  });

  test("Can open the named range side panel from the dropdown", async () => {
    await mountRangeSelector();
    await simulateClick(".o-named-range-selector [data-icon='arrow_drop_down']");

    expect(".o-menu-item").toHaveCount(1);
    await simulateClick(".o-menu-item");
    expect(openSidePanel).toHaveBeenCalledWith("NamedRangesPanel", {});
  });

  test("Focusing the input opens the dropdown", async () => {
    createNamedRange(model, "MyRange", "A1:B3");
    await mountRangeSelector();

    await simulateClick(".o-named-range-selector input");
    expect(".o-menu-item").toHaveCount(2); // 1 named range + "Manage named ranges"
  });

  test("Writing in the input filter the menu items shown", async () => {
    createNamedRange(model, "MyRange", "A1:B3");
    createNamedRange(model, "AnotherRange", "C1:D4");
    await mountRangeSelector();

    await simulateClick(".o-named-range-selector input");
    expect(".o-menu-item").toHaveCount(3); // 2 named ranges + "Manage named ranges"

    await setInputValueAndTrigger(".o-named-range-selector input", "Another", "onlyInput");
    expect(".o-menu-item").toHaveCount(2); // "AnotherRange" + "Manage named ranges"

    await simulateClick(".o-menu-item");
    expect(model.getters.getSelectedZone()).toEqual(toZone("C1:D4"));
    expect(".o-named-range-selector input").toHaveValue("AnotherRange");
    expect(model.getters.getNamedRanges()).toHaveLength(2);
  });

  test("Blurring the input will reset it", async () => {
    createNamedRange(model, "MyRange", "A1");
    await mountRangeSelector();
    const input = fixture.querySelector<HTMLInputElement>(".o-named-range-selector input")!;
    expect(input).toHaveValue("MyRange");
    await nextTick();

    input.focus();
    await setInputValueAndTrigger(input, "RandomName", "onlyInput");
    expect(input).toHaveValue("RandomName");

    input.blur();
    await nextTick();
    expect(input).toHaveValue("MyRange");
    expect(model.getters.getNamedRanges()).toMatchObject([
      { name: "MyRange", range: { zone: toZone("A1") } },
    ]);
  });

  test("Hovering a menu item does not close the dropdown", async () => {
    createNamedRange(model, "MyRange", "A1:B3");
    await mountRangeSelector();
    const input = fixture.querySelector<HTMLInputElement>(".o-named-range-selector input")!;
    input.focus();
    await nextTick();

    expect(".o-menu-item").toHaveCount(2); // 1 named range + "Manage named ranges"
    triggerMouseEvent(".o-menu-item", "mouseenter");
    await nextTick();
    expect(".o-menu-item").toHaveCount(2);
  });

  test("select a named range from the dropdown select the zone even if it's hidden", async () => {
    createSheet(model, { name: "Sheet1", sheetId: "Sheet1" });
    createNamedRange(model, "MyRange", "B1:B2");
    await mountRangeSelector();

    hideColumns(model, ["B"]);

    await simulateClick(".o-named-range-selector [data-icon='arrow_drop_down']");
    const menuItems = [...document.querySelectorAll<HTMLElement>(".o-menu-item")];

    await simulateClick(menuItems[0]);
    expect(model.getters.getActiveSheetId()).toEqual("Sheet1");
    expect(model.getters.getSelectedZone()).toEqual(toZone("B1:B2"));
  });

  test("select a named range from the dropdown notify the user if the sheet is hidden", async () => {
    createSheet(model, { name: "Sheet1", sheetId: "Sheet1" });
    createSheet(model, { name: "Sheet2", sheetId: "Sheet2" });
    createNamedRange(model, "MyRange", "B1:B2");
    await mountRangeSelector();
    const notificationStore = env.getStore(NotificationStore);
    const spyNotify = jest.spyOn(notificationStore, "notifyUser");
    hideSheet(model, "Sheet1");

    await simulateClick(".o-named-range-selector [data-icon='arrow_drop_down']");
    const menuItems = [...document.querySelectorAll<HTMLElement>(".o-menu-item")];

    await simulateClick(menuItems[0]);
    expect(model.getters.getActiveSheetId()).toEqual("Sheet2");
    expect(model.getters.getSelectedZone()).toEqual(toZone("A1"));
    expect(spyNotify).toHaveBeenCalledWith({
      type: "info",
      sticky: false,
      text: "The sheet on which the range is defined is hidden.",
    });
  });
});
