import { useProps, xml } from "@odoo/owl";
import { Model } from "../../src";
import { ComposerFocusStore } from "../../src/components/composer/composer_focus_store";
import { StandaloneComposer } from "../../src/components/composer/standalone_composer/standalone_composer";
import { zoneToXc } from "../../src/helpers/zones";
import { Component } from "../../src/owl3_compatibility_layer";
import { sidePanelRegistry } from "../../src/registries/side_panel_registry";
import { SpreadsheetChildEnv } from "../../src/types/spreadsheet_env";
import { Store } from "../../src/types/store_engine";
import { createSheet, updateLocale } from "../test_helpers/commands_helpers";
import { FR_LOCALE } from "../test_helpers/constants";
import { click, getTextNodes, keyDown, simulateClick } from "../test_helpers/dom_helper";
import {
  addToRegistry,
  editStandaloneComposer,
  mountSpreadsheet,
  nextTick,
} from "../test_helpers/helpers";

let env: SpreadsheetChildEnv;
const onConfirm = jest.fn();
let fixture: HTMLElement;
let composerEl: HTMLElement;
let composerFocusStore: Store<ComposerFocusStore>;
let model: Model;

class SidePanelWithComposer extends Component<any> {
  static template = xml/*xml*/ `
      <div>
        <StandaloneComposer
          onConfirm="this.props.onConfirm"
          composerContent="this.props.composerContent"
          defaultRangeSheetId="this.props.defaultRangeSheetId"
          defaultStatic="this.props.defaultStatic"
          title="this.props.title"
          placeholder="this.props.placeholder"
        />
      </div>`;
  static components = { StandaloneComposer };

  protected props = useProps();
}

async function openSidePanelWithComposer(props?: Partial<StandaloneComposer["props"]>) {
  env.openSidePanel("SidePanelWithComposer", {
    ...props,
    onConfirm,
    composerContent: props?.composerContent || "",
    defaultRangeSheetId: props?.defaultRangeSheetId || model.getters.getActiveSheetId(),
    defaultStatic: props?.defaultStatic || false,
  });
  await nextTick();
  composerEl = fixture.querySelector<HTMLElement>(".o-sidePanel .o-composer")!;
}

const composerSelector = ".o-sidePanel .o-composer";

describe("Spreadsheet integrations tests", () => {
  beforeEach(async () => {
    addToRegistry(sidePanelRegistry, "SidePanelWithComposer", {
      title: "SidePanelWithComposer",
      Body: SidePanelWithComposer,
    });
    ({ env, fixture, model } = await mountSpreadsheet());
    composerFocusStore = env.getStore(ComposerFocusStore);
  });

  test("Can edit a standalone composer", async () => {
    await openSidePanelWithComposer({ composerContent: "Hello world" });
    expect(composerEl.textContent).toBe("Hello world");
    composerEl.focus();
    const textNode = getTextNodes(composerEl)[0];
    const selection = document.getSelection()!;
    selection.addRange(document.createRange());
    const range = selection.getRangeAt(0);
    range.setStart(textNode, composerEl.textContent?.length || 0);
    range.setEnd(textNode, composerEl.textContent?.length || 0);
    await editStandaloneComposer(composerSelector, " new text !", {
      fromScratch: false,
      confirm: false,
    });
    expect(composerEl.textContent).toBe("Hello world new text !");
    await keyDown({ key: "Enter" });
    expect(onConfirm).toHaveBeenCalledWith("Hello world new text !");
  });

  test("Can use formula assistant using standalone composer", async () => {
    await openSidePanelWithComposer();
    await editStandaloneComposer(composerSelector, "=SU", { confirm: false });
    await nextTick();
    await click(fixture.querySelector(".o-autocomplete-value")!);
    expect(composerEl.textContent).toBe("=SUM(");
  });

  test("Can select a range with the keyboard using standalone composer", async () => {
    await openSidePanelWithComposer();
    await editStandaloneComposer(composerSelector, "=", { confirm: false });

    await keyDown({ key: "ArrowRight" });
    expect(composerEl.textContent).toBe("=B1");

    await editStandaloneComposer(composerSelector, "+", { confirm: false, fromScratch: false });
    await keyDown({ key: "ArrowDown" });
    expect(composerEl.textContent).toBe("=B1+A2");
  });

  test("Can select a range with the mouse using standalone composer", async () => {
    await openSidePanelWithComposer();
    await editStandaloneComposer(composerSelector, "=SUM(", { confirm: false });
    await simulateClick(".o-grid-overlay", 300, 200);
    expect(composerEl.textContent).toBe("=SUM(D9");
  });

  test("Can select a static range with the mouse using standalone composer", async () => {
    await openSidePanelWithComposer({ defaultStatic: true });
    await editStandaloneComposer(composerSelector, "=SUM(", { confirm: false });
    await simulateClick(".o-grid-overlay", 300, 200);
    expect(composerEl.textContent).toBe("=SUM($D$9");
  });

  test("Parenthesis are closed when composer is confirmed", async () => {
    await openSidePanelWithComposer();
    await editStandaloneComposer(composerSelector, "=SUM(A1");
    await keyDown({ key: "Enter" });
    expect(onConfirm).toHaveBeenCalledWith("=SUM(A1)");
  });

  test("Brackets are closed when composer is confirmed", async () => {
    await openSidePanelWithComposer();
    await editStandaloneComposer(composerSelector, "={1,2");
    await keyDown({ key: "Enter" });
    expect(onConfirm).toHaveBeenCalledWith("={1,2}");
  });

  test("Bracket then parenthesis are closed when composer is confirmed", async () => {
    await openSidePanelWithComposer();
    await editStandaloneComposer(composerSelector, "=SUM(A1,{2,3");
    await keyDown({ key: "Enter" });
    expect(onConfirm).toHaveBeenCalledWith("=SUM(A1,{2,3})");
  });

  test("Parenthesis then bracket are closed when composer is confirmed", async () => {
    await openSidePanelWithComposer();
    await editStandaloneComposer(composerSelector, "={A1,SUM(2,3");
    await keyDown({ key: "Enter" });
    expect(onConfirm).toHaveBeenCalledWith("={A1,SUM(2,3)}");
  });

  test("Standalone composer lose focus and is confirmed when clicking on another composer", async () => {
    await openSidePanelWithComposer();
    await editStandaloneComposer(composerSelector, "Hi !", { confirm: false });
    expect(composerFocusStore.activeComposer.id).toBe("standaloneComposer");

    await simulateClick(".o-spreadsheet-topbar .o-composer");
    expect(composerFocusStore.activeComposer.id).toBe("topbarComposer");
    expect(onConfirm).toHaveBeenCalledWith("Hi !");
  });

  test("Confirming standalone composer do not move the selection", async () => {
    await openSidePanelWithComposer();
    await editStandaloneComposer(composerSelector, "=A1", { confirm: false });
    await keyDown({ key: "Enter" });
    expect(zoneToXc(model.getters.getSelectedZone())).toBe("A1");
  });

  test("content with references from another sheet", async () => {
    const sheet1Id = model.getters.getActiveSheetId();
    createSheet(model, { sheetId: "sheet2", activate: true });
    await openSidePanelWithComposer({
      composerContent: "=A1",
      defaultRangeSheetId: sheet1Id,
      defaultStatic: false,
    });
    expect(composerEl.textContent).toBe("=Sheet1!A1");
  });

  test("content with references from another sheet having space in name adds single quotes", async () => {
    await openSidePanelWithComposer({ defaultStatic: true });
    await editStandaloneComposer(composerSelector, "=", { confirm: false });
    createSheet(model, { sheetId: "sheet2", name: "second sheet", activate: true });
    await simulateClick(".o-grid-overlay", 300, 200);
    expect(composerEl.textContent).toBe("='second sheet'!$D$9");
  });

  test("display the content from the props when inactive", async () => {
    await openSidePanelWithComposer({ composerContent: "content from props" });
    await editStandaloneComposer(composerSelector, "edited", { confirm: false });
    expect(composerEl.textContent).toBe("edited");
    await keyDown({ key: "Enter" });
    // in a real world scenario, the props most likely changed
    // to the new confirmed content
    expect(composerEl.textContent).toBe("content from props");
  });

  test("Standalone composer works with non-default locale", async () => {
    updateLocale(model, FR_LOCALE);
    await openSidePanelWithComposer({ composerContent: "=SUM(1,2.5)" });
    expect(composerEl.textContent).toBe("=SUM(1;2,5)");

    await editStandaloneComposer(composerSelector, "=SUM(1;2,5) + SUM(1,5;4)", {
      confirm: false,
    });
    expect(composerEl.textContent).toBe("=SUM(1;2,5) + SUM(1,5;4)");
    await keyDown({ key: "Enter" });
    expect(onConfirm).toHaveBeenCalledWith("=SUM(1,2.5) + SUM(1.5,4)");
  });

  test("Can have title and placeholder", async () => {
    await openSidePanelWithComposer({
      title: "MyTitle",
      placeholder: "MyPlaceholder",
    });

    expect(composerEl).toHaveAttribute("placeholder", "MyPlaceholder");
    expect(".o-standalone-composer").toHaveAttribute("title", "MyTitle");
  });
});
