import { providePlugins, useProps, xml } from "@odoo/owl";
import { Model, Pixel, Rect } from "../../src";
import { Popover } from "../../src/components/popover/popover";
import { PopoverContainerPlugin } from "../../src/components/popover/popover_container_owl_plugin";
import { types } from "../../src/components/props_validation";
import { getDefaultSheetViewSize } from "../../src/constants";
import { Component, useSubEnv } from "../../src/owl3_compatibility_layer";
import { PropsOf } from "../../src/types/props_of";
import { getStylePropertyInPx, mountComponent } from "../test_helpers/helpers";
import { extendMockGetBoundingClientRect } from "../test_helpers/mock_helpers";

const POPOVER_HEIGHT = 200;
const POPOVER_WIDTH = 200;

let fixture: HTMLElement;
let model: Model;

interface MountPopoverArgs extends Partial<PropsOf<Popover>> {
  childWidth?: Pixel;
  childHeight?: Pixel;
  containerRect?: Rect;
}

async function mountTestPopover(args: MountPopoverArgs) {
  class Parent extends Component<any> {
    static template = xml/* xml */ `
        <div class="o-spreadsheet">
          <Popover t-props="this.popoverProps">
            <div style="height:${args.childHeight}px;width:${args.childWidth}px;"/>
          </Popover>
        </div>
  `;
    static components = { Popover };
    protected props = useProps({
      model: types.object<Model>(),
    });

    setup() {
      const env: any = {
        model: this.props.model,
      };
      providePlugins([PopoverContainerPlugin], {
        getPopoverContainerRect: () =>
          args.containerRect || { x: 0, y: 0, height: 1000, width: 1000 },
      });
      useSubEnv(env);
    }

    get popoverProps() {
      return {
        anchorRect: args.anchorRect || { x: 0, y: 0, width: 10, height: 10 },
        positioning: args.positioning || "right",
        maxWidth: args.maxHeight,
        maxHeight: args.maxHeight,
      };
    }
  }

  ({ fixture } = await mountComponent(Parent, { props: { model } }));
}

beforeEach(async () => {
  model = new Model();
  extendMockGetBoundingClientRect({
    "o-popover-content": (el: HTMLElement) => {
      const popover = el.closest<HTMLElement>(".o-popover")!;
      const maxHeight = getStylePropertyInPx(popover, "max-height");
      const maxWidth = getStylePropertyInPx(popover, "max-height");
      const childHeight = getStylePropertyInPx(el.firstChild! as HTMLElement, "height");
      const childWidth = getStylePropertyInPx(el.firstChild! as HTMLElement, "width");
      return {
        height: childHeight || maxHeight,
        width: childWidth || maxWidth,
      };
    },
    "o-popover": (el: HTMLElement) => {
      const maxHeight = getStylePropertyInPx(el, "max-height");
      const maxWidth = getStylePropertyInPx(el, "max-height");
      const childHeight = getStylePropertyInPx(el.firstChild!.firstChild as HTMLElement, "height");
      const childWidth = getStylePropertyInPx(el.firstChild!.firstChild as HTMLElement, "width");
      return {
        height: childHeight || maxHeight,
        width: childWidth || maxWidth,
      };
    },
    "o-spreadsheet": () => ({ top: 0, left: 0, height: 1000, width: 1000 }),
  });
});

describe("Popover sizing", () => {
  test("Prop maxHeight and maxWidth make popover use CSS maxwidth/height", async () => {
    await mountTestPopover({
      anchorRect: { x: 0, y: 0, width: 0, height: 0 },
      positioning: "top-right",
      maxWidth: POPOVER_WIDTH,
      maxHeight: POPOVER_HEIGHT,
      childHeight: 100,
      childWidth: 100,
    });
    const popover = fixture.querySelector(".o-popover")! as HTMLElement;
    expect(popover).toBeTruthy();
    expect(popover.style["max-width"]).toEqual(`${POPOVER_WIDTH}px`);
    expect(popover.style["max-height"]).toEqual(`${POPOVER_HEIGHT}px`);
  });

  test("Popover use the spreadsheet size to compute its max size", async () => {
    await mountTestPopover({
      anchorRect: { x: 0, y: 0, width: 0, height: 0 },
      positioning: "top-right",
      childHeight: 100,
      childWidth: 100,
    });
    const popover = fixture.querySelector(".o-popover")! as HTMLElement;
    expect(popover).toBeTruthy();
    expect(popover.style["max-width"]).toEqual("1000px");
    expect(popover.style["max-height"]).toEqual("1000px");
  });
});

describe("Popover positioning", () => {
  describe("Popover positioned TopRight", () => {
    test("Popover right of point", async () => {
      await mountTestPopover({
        anchorRect: { x: 0, y: 0, width: 0, height: 0 },
        positioning: "top-right",
        childHeight: 100,
        childWidth: 100,
      });
      const popover = fixture.querySelector(".o-popover")! as HTMLElement;
      expect(popover).toBeTruthy();
      expect(popover.style.left).toEqual("0px");
      expect(popover.style.top).toEqual("0px");
    });

    test("Popover right of box", async () => {
      const box = { x: 0, y: 0, width: 100, height: 100 };
      await mountTestPopover({
        anchorRect: box,
        positioning: "top-right",
        childWidth: 50,
        childHeight: 50,
      });
      const popover = fixture.querySelector(".o-popover")! as HTMLElement;
      expect(popover).toBeTruthy();
      expect(popover.style.left).toEqual(`${box.width}px`);
      expect(popover.style.top).toEqual(`${box.y}px`);
    });

    test("Popover overflowing right is rendered left of box", async () => {
      const viewPortDims = getDefaultSheetViewSize();
      const box = { x: viewPortDims - 50, y: 0, width: 50, height: 50 };
      await mountTestPopover({
        anchorRect: box,
        positioning: "top-right",
        childWidth: 100,
        childHeight: 100,
      });
      const popover = fixture.querySelector(".o-popover")! as HTMLElement;
      expect(popover).toBeTruthy();
      expect(popover.style.left).toEqual(`${box.x - 100}px`);
      expect(popover.style.top).toEqual(`${box.y}px`);
    });

    test("Popover overflowing down is rendered with its bottom aligned to the bottom of the box", async () => {
      const viewPortDims = getDefaultSheetViewSize();
      const box = { x: 0, y: viewPortDims - 50, width: 50, height: 50 };
      await mountTestPopover({
        anchorRect: box,
        positioning: "top-right",
        childHeight: 100,
        childWidth: 100,
      });
      const popover = fixture.querySelector(".o-popover")! as HTMLElement;
      expect(popover).toBeTruthy();
      expect(popover.style.left).toEqual(`${box.width}px`);
      expect(popover.style.top).toEqual(`${box.y + box.height - 100}px`);
    });

    test("Popover overflowing down and right is rendered to the left of the box with its bottom aligned to the bottom of the box", async () => {
      const viewPortDims = getDefaultSheetViewSize();
      const box = {
        x: viewPortDims - 50,
        y: viewPortDims - 50,
        width: 50,
        height: 50,
      };
      await mountTestPopover({
        anchorRect: box,
        positioning: "top-right",
        childHeight: 100,
        childWidth: 100,
      });
      const popover = fixture.querySelector(".o-popover")! as HTMLElement;
      expect(popover).toBeTruthy();
      expect(popover.style.left).toEqual(`${box.x - 100}px`);
      expect(popover.style.top).toEqual(`${box.y + box.height - 100}px`);
    });
  });

  describe("Popover positioned bottom-left", () => {
    test("Popover bottom of point", async () => {
      await mountTestPopover({
        anchorRect: { x: 0, y: 0, width: 0, height: 0 },
        positioning: "bottom-left",
        childHeight: 100,
        childWidth: 100,
      });
      const popover = fixture.querySelector(".o-popover")! as HTMLElement;
      expect(popover).toBeTruthy();
      expect(popover.style.left).toEqual("0px");
      expect(popover.style.top).toEqual("0px");
    });

    test("Popover bottom of box", async () => {
      const box = { x: 0, y: 0, width: 100, height: 100 };
      await mountTestPopover({
        anchorRect: box,
        positioning: "bottom-left",
        childHeight: 100,
        childWidth: 100,
      });
      const popover = fixture.querySelector(".o-popover")! as HTMLElement;
      expect(popover).toBeTruthy();
      expect(popover.style.left).toEqual(`${box.x}px`);
      expect(popover.style.top).toEqual(`${box.height}px`);
    });

    test("Popover overflowing right is rendered with its right border matching the box right border", async () => {
      const viewPortDims = getDefaultSheetViewSize();
      const box = { x: viewPortDims - 50, y: 0, width: 50, height: 50 };
      await mountTestPopover({
        anchorRect: box,
        positioning: "bottom-left",
        childWidth: 100,
        childHeight: 100,
      });
      const popover = fixture.querySelector(".o-popover")! as HTMLElement;
      expect(popover).toBeTruthy();
      expect(popover.style.left).toEqual(`${box.x + box.width - 100}px`);
      expect(popover.style.top).toEqual(`${box.height}px`);
    });

    test("Popover overflowing down is rendered above the box", async () => {
      const viewPortDims = getDefaultSheetViewSize();
      const box = { x: 0, y: viewPortDims - 50, width: 50, height: 50 };
      await mountTestPopover({
        anchorRect: box,
        positioning: "bottom-left",
        childHeight: 100,
        childWidth: 100,
      });
      const popover = fixture.querySelector(".o-popover")! as HTMLElement;
      expect(popover).toBeTruthy();
      expect(popover.style.left).toEqual(`${box.x}px`);
      expect(popover.style.top).toEqual(`${box.y - 100}px`);
    });

    test("Popover overflowing down and right is rendered with its right border matching the box right border and above the box", async () => {
      const viewPortDims = getDefaultSheetViewSize();
      const box = {
        x: viewPortDims - 50,
        y: viewPortDims - 50,
        width: 50,
        height: 50,
      };
      await mountTestPopover({
        anchorRect: box,
        containerRect: { x: 0, y: 0, width: viewPortDims, height: viewPortDims },
        positioning: "bottom-left",
        childHeight: 100,
        childWidth: 100,
      });
      const popover = fixture.querySelector(".o-popover")! as HTMLElement;
      expect(popover).toBeTruthy();
      expect(popover.style.left).toEqual(`${box.x + box.width - 100}px`);
      expect(popover.style.top).toEqual(`${box.y - 100}px`);
    });
  });

  test("If the anchorRect is not fully in the containerRect, the intersection between the two in taken as anchor", async () => {
    const viewPortDims = getDefaultSheetViewSize();
    const anchorRect = {
      x: viewPortDims - 75,
      y: 0,
      width: 100,
      height: 100,
    };
    await mountTestPopover({
      anchorRect,
      positioning: "bottom-left",
      childHeight: 100,
      childWidth: 100,
    });
    const popover = fixture.querySelector(".o-popover")! as HTMLElement;
    expect(popover).toBeTruthy();
    expect(popover.style.left).toEqual(`${anchorRect.x + 75 - 100}px`);
    expect(popover.style.top).toEqual(`${anchorRect.y + anchorRect.height}px`);
  });

  test("If the anchorRect is not at all in the containerRect, the popover is hidden", async () => {
    const viewPortDims = getDefaultSheetViewSize();
    const anchorRect = {
      x: viewPortDims + 75,
      y: 0,
      width: 100,
      height: 100,
    };
    await mountTestPopover({
      anchorRect,
      positioning: "bottom-left",
      childHeight: 100,
      childWidth: 100,
    });
    const popover = fixture.querySelector(".o-popover")! as HTMLElement;
    expect(popover).toBeTruthy();
    expect(popover.style.display).toEqual("none");
  });

  test("Popover position is updated when its content changes size", async () => {
    let popoverContentBox = { x: 0, y: 0, width: 100, height: 100 };
    extendMockGetBoundingClientRect({
      "o-popover": (el: HTMLElement) => ({
        height: Math.min(getStylePropertyInPx(el, "max-height") || 0, popoverContentBox.height),
        width: 100,
      }),
      "o-popover-content": () => popoverContentBox,
      "o-spreadsheet": () => ({ top: 0, left: 0, height: 1000, width: 1000 }),
    });

    await mountTestPopover({
      anchorRect: { x: 500, y: 800, width: 0, height: 0 },
      positioning: "bottom-left",
    });
    const popover = fixture.querySelector(".o-popover")! as HTMLElement;
    expect(popover.style.top).toEqual(`800px`);

    popoverContentBox = { x: 0, y: 0, width: 100, height: 400 };
    window.resizers.resize();
    expect(popover.style.top).toEqual(`400px`);
  });

  test("Popover do not move back and forth if there is multiple content size changes", async () => {
    let popoverContentBox = { x: 0, y: 0, width: 100, height: 100 };
    extendMockGetBoundingClientRect({
      "o-popover": (el: HTMLElement) => ({
        height: Math.min(getStylePropertyInPx(el, "max-height") || 0, popoverContentBox.height),
        width: 100,
      }),
      "o-popover-content": () => popoverContentBox,
      "o-spreadsheet": () => ({ top: 0, left: 0, height: 1000, width: 1000 }),
    });

    await mountTestPopover({
      anchorRect: { x: 500, y: 800, width: 0, height: 0 },
      positioning: "bottom-left",
    });
    const popover = fixture.querySelector(".o-popover")! as HTMLElement;
    expect(popover.style.top).toEqual(`800px`);

    popoverContentBox = { x: 0, y: 0, width: 100, height: 400 };
    window.resizers.resize();
    expect(popover.style.top).toEqual(`400px`);

    popoverContentBox = { x: 0, y: 0, width: 100, height: 100 };
    window.resizers.resize();
    // top moves because the content changes, but the bottom stays the same, even if there is now space below the anchor rect
    expect(popover.style.top).toEqual(`700px`);
  });
});
