import { onWillUpdateProps, proxy, signal, useProps } from "@odoo/owl";
import { deepEquals } from "../../helpers/misc";
import { UuidGenerator } from "../../helpers/uuid";
import { Component } from "../../owl3_compatibility_layer";
import { MenuItemRegistry } from "../../registries/menu_items_registry";
import { _t } from "../../translation";
import { MenuMouseEvent, Pixel, UID } from "../../types/misc";
import { Rect } from "../../types/rendering";
import { SpreadsheetChildEnv } from "../../types/spreadsheet_env";
import { Ripple } from "../animation/ripple";
import { useDragAndDropListItems } from "../helpers/drag_and_drop_dom_items_hook";
import { MenuPopover, MenuState } from "../menu_popover/menu_popover";
import { types } from "../props_validation";
import { BottomBarSheet } from "./bottom_bar_sheet/bottom_bar_sheet";
import { BottomBarStatistic } from "./bottom_bar_statistic/bottom_bar_statistic";

// -----------------------------------------------------------------------------
// SpreadSheet
// -----------------------------------------------------------------------------

const SHEET_LIST_MENU_MAX_HEIGHT = 250;

interface BottomBarSheetItem {
  id: UID;
  name: string;
}

interface BottomBarMenuState extends MenuState {
  menuId: UID | undefined;
  maxHeight?: Pixel;
}

export class BottomBar extends Component<SpreadsheetChildEnv> {
  static template = "o-spreadsheet-BottomBar";

  protected props = useProps({ onClick: types.function<(ev: MouseEvent) => void>() });

  static components = { MenuPopover, Ripple, BottomBarSheet, BottomBarStatistic };

  private bottomBarRef = signal.ref();
  private sheetListRef = signal.ref();

  private dragAndDrop = useDragAndDropListItems();
  private targetScroll: number | undefined = undefined;
  private state = proxy({
    isSheetListScrollableLeft: false,
    isSheetListScrollableRight: false,
  });

  menuState: BottomBarMenuState = proxy({
    isOpen: false,
    menuId: undefined,
    anchorRect: null,
    menuItems: [],
  });

  sheetList = this.getVisibleSheets();

  setup() {
    onWillUpdateProps(() => {
      this.updateScrollState();
      const visibleSheets = this.getVisibleSheets();
      // Cancel sheet dragging when there is a change in the sheets
      if (!deepEquals(this.sheetList, visibleSheets)) {
        this.dragAndDrop.cancel();
      }
      this.sheetList = visibleSheets;
    });
  }

  clickAddSheet(ev: MouseEvent) {
    const activeSheetId = this.env.model.getters.getActiveSheetId();
    const position =
      this.env.model.getters.getSheetIds().findIndex((sheetId) => sheetId === activeSheetId) + 1;
    const sheetId = UuidGenerator.smallUuid();
    const name = this.env.model.getters.getNextSheetName(_t("Sheet"));
    this.env.model.dispatch("CREATE_SHEET", { sheetId, position, name });
    this.env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom: activeSheetId, sheetIdTo: sheetId });
  }

  getVisibleSheets(): BottomBarSheetItem[] {
    return this.env.model.getters.getVisibleSheetIds().map((sheetId) => {
      const sheet = this.env.model.getters.getSheet(sheetId);
      return { id: sheet.id, name: sheet.name };
    });
  }

  clickListSheets(ev: MouseEvent) {
    const registry = new MenuItemRegistry();
    const from = this.env.model.getters.getActiveSheetId();
    let i = 0;
    for (const sheetId of this.env.model.getters.getSheetIds()) {
      const sheet = this.env.model.getters.getSheet(sheetId);
      registry.add(sheetId, {
        name: sheet.name,
        sequence: i,
        isReadonlyAllowed: true,
        textColor: sheet.isVisible ? undefined : "#808080",
        execute: (env) => {
          if (!this.env.model.getters.isSheetVisible(sheetId)) {
            this.env.model.dispatch("SHOW_SHEET", { sheetId });
          }
          env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom: from, sheetIdTo: sheetId });
        },
        isEnabled: (env) => (env.model.getters.isReadonly() ? sheet.isVisible : true),
        icon: sheet.color ? "o-spreadsheet-Icon.SMALL_DOT_RIGHT_ALIGN" : undefined,
        iconColor: sheet.color,
        isEnabledOnLockedSheet: true,
      });
      i++;
    }
    const target = ev.currentTarget as HTMLElement;
    const { left } = target.getBoundingClientRect();
    const top = this.bottomBarRef()!.getBoundingClientRect().top;
    this.openContextMenu(left, top, "listSheets", registry);
  }

  openContextMenu(x: Pixel, y: Pixel, menuId: UID, registry: MenuItemRegistry) {
    this.menuState.isOpen = true;
    this.menuState.menuId = menuId;
    this.menuState.menuItems = registry.getMenuItems();
    this.menuState.anchorRect = { x, y, width: 0, height: 0 };
    this.menuState.maxHeight = menuId === "listSheets" ? SHEET_LIST_MENU_MAX_HEIGHT : undefined;
  }

  onSheetContextMenu(sheetId: UID, registry: MenuItemRegistry, ev: MenuMouseEvent) {
    const target = ev.currentTarget as HTMLElement;
    const { top, left } = target.getBoundingClientRect();
    if (ev.closedMenuId === sheetId) {
      this.closeMenu();
      return;
    }
    this.openContextMenu(left, top, sheetId, registry);
  }

  closeMenu() {
    this.menuState.isOpen = false;
    this.menuState.menuId = undefined;
    this.menuState.menuItems = [];
    this.menuState.anchorRect = null;
  }

  closeContextMenuWithId(menuId: UID) {
    if (this.menuState.menuId === menuId) {
      this.closeMenu();
    }
  }

  onWheel(ev: WheelEvent) {
    this.targetScroll = undefined;
    const target = ev.currentTarget as HTMLElement;
    target.scrollLeft += ev.deltaY * 0.5;
  }

  onScroll() {
    this.updateScrollState();
    if (this.targetScroll === this.sheetListCurrentScroll) {
      this.targetScroll = undefined;
    }
  }

  onArrowLeft(ev: MouseEvent) {
    if (!this.state.isSheetListScrollableLeft) {
      return;
    }
    if (!this.targetScroll) {
      this.targetScroll = this.sheetListCurrentScroll;
    }
    const newScroll = this.targetScroll - this.sheetListWidth;
    this.scrollSheetListTo(Math.max(0, newScroll));
  }

  onArrowRight(ev: MouseEvent) {
    if (!this.state.isSheetListScrollableRight) {
      return;
    }
    if (!this.targetScroll) {
      this.targetScroll = this.sheetListCurrentScroll;
    }
    const newScroll = this.targetScroll + this.sheetListWidth;
    this.scrollSheetListTo(Math.min(this.sheetListMaxScroll, newScroll));
  }

  private updateScrollState() {
    this.state.isSheetListScrollableLeft = this.sheetListCurrentScroll > 0;
    this.state.isSheetListScrollableRight = this.sheetListCurrentScroll < this.sheetListMaxScroll;
  }

  private scrollSheetListTo(scroll: number) {
    const sheetListEl = this.sheetListRef();
    if (!sheetListEl) {
      return;
    }
    this.targetScroll = scroll;
    sheetListEl.scrollTo({ top: 0, left: scroll, behavior: "smooth" });
  }

  onSheetMouseDown(sheetId: UID, event: MouseEvent) {
    if (event.button !== 0 || this.env.model.getters.isReadonly()) {
      return;
    }
    this.closeMenu();

    if (this.env.isMobile()) {
      return;
    }

    const visibleSheets = this.getVisibleSheets();
    const sheetRects = this.getSheetItemRects();

    const sheets = visibleSheets.map((sheet, index) => ({
      id: sheet.id,
      size: sheetRects[index].width,
      position: sheetRects[index].x,
    }));
    this.dragAndDrop.start("horizontal", {
      draggedItemId: sheetId,
      initialMousePosition: event.clientX,
      items: sheets,
      scrollableContainerEl: this.sheetListRef()!,
      onDragEnd: (sheetId: UID, finalIndex: number) => this.onDragEnd(sheetId, finalIndex),
    });
  }

  private onDragEnd(sheetId: UID, finalIndex: number) {
    const originalIndex = this.getVisibleSheets().findIndex((sheet) => sheet.id === sheetId);
    const delta = finalIndex - originalIndex;
    if (sheetId && delta !== 0) {
      this.env.model.dispatch("MOVE_SHEET", {
        sheetId: sheetId,
        delta: delta,
      });
    }
  }

  getSheetStyle(sheetId: UID): string {
    return this.dragAndDrop.itemsStyle[sheetId] || "";
  }

  private getSheetItemRects(): Rect[] {
    return Array.from(this.bottomBarRef()!.querySelectorAll<HTMLElement>(`.o-sheet`))
      .map((sheetEl) => sheetEl.getBoundingClientRect())
      .map((rect) => ({
        x: rect.x,
        width: rect.width - 1, // -1 to compensate negative margin
        y: rect.y,
        height: rect.height,
      }));
  }

  get sheetListCurrentScroll() {
    const sheetListEl = this.sheetListRef();
    if (!sheetListEl) {
      return 0;
    }
    return sheetListEl.scrollLeft;
  }

  get sheetListWidth() {
    const sheetListEl = this.sheetListRef();
    if (!sheetListEl) {
      return 0;
    }
    return sheetListEl.clientWidth;
  }

  get sheetListMaxScroll() {
    const sheetListEl = this.sheetListRef();
    if (!sheetListEl) {
      return 0;
    }
    return sheetListEl.scrollWidth - sheetListEl.clientWidth;
  }
}
