import { proxy, signal, useProps } from "@odoo/owl";
import { ActionSpec, createActions } from "../../../actions/action";
import { DEFAULT_CAROUSEL_TITLE_STYLE } from "../../../constants";
import { getCarouselItemTitle, getCarouselLayout } from "../../../helpers/carousel_helpers";
import { chartStyleToCellStyle, deepEquals } from "../../../helpers/misc";
import { cellPositions } from "../../../helpers/zones";
import { Component, useLayoutEffect } from "../../../owl3_compatibility_layer";
import { chartComponentRegistry } from "../../../registries/chart_component_registry";
import { useStore } from "../../../store_engine/store_hooks";
import { _t } from "../../../translation";
import { CellValueType } from "../../../types/cells";
import { Carousel, CarouselItem } from "../../../types/figure";
import { CSSProperties, MenuMouseEvent } from "../../../types/misc";
import { Range } from "../../../types/range";
import { Rect } from "../../../types/rendering";
import { SpreadsheetChildEnv } from "../../../types/spreadsheet_env";
import { Store } from "../../../types/store_engine";
import { FullScreenFigureStore } from "../../full_screen_figure/full_screen_figure_store";
import { cellTextStyleToCss, cssPropertiesToCss } from "../../helpers/css";
import { getBoundingRectAsPOJO, getElBoundingRect } from "../../helpers/dom_helpers";
import { MenuPopover, MenuState } from "../../menu_popover/menu_popover";
import { types } from "../../props_validation";
import { StandaloneViewport } from "../../standalone_viewport/standalone_viewport";
import { ChartAnimationStore } from "../chart/chartJs/chartjs_animation_store";
import { ChartMenu } from "../chart/chart_menu/chart_menu";

export class CarouselFigure extends Component<SpreadsheetChildEnv> {
  static template = "o-spreadsheet-CarouselFigure";
  static components = { ChartMenu, MenuPopover, StandaloneViewport };

  protected props = useProps({
    figureUI: types.FigureUI(),
    editFigureStyle: types.function<(properties: CSSProperties) => void>().optional(),
    isFullScreen: types.boolean().optional(),
    openContextMenu: types.function<(anchorRect: Rect, onClose?: () => void) => void>().optional(),
  });

  private carouselTabsRef = signal.ref();
  private carouselTabsDropdownRef = signal.ref();

  private menuState = proxy<MenuState>({ isOpen: false, anchorRect: null, menuItems: [] });
  private hiddenItems: CarouselItem[] = [];

  protected animationStore: Store<ChartAnimationStore> | undefined;
  private fullScreenFigureStore!: Store<FullScreenFigureStore>;

  setup(): void {
    this.animationStore = useStore(ChartAnimationStore);
    this.fullScreenFigureStore = useStore(FullScreenFigureStore);

    useLayoutEffect(() => {
      this.updateTabsVisibility();
    });
  }

  get carousel(): Carousel {
    return this.env.model.getters.getCarousel(this.props.figureUI.id);
  }

  get selectedCarouselItem(): CarouselItem | undefined {
    return this.env.model.getters.getSelectedCarouselItem(this.props.figureUI.id);
  }

  get chartComponent(): new (...args: any) => Component {
    const selectedItem = this.selectedCarouselItem;
    if (selectedItem?.type !== "chart") {
      throw new Error("Selected item is not a chart");
    }
    const type = this.env.model.getters.getChartType(selectedItem.chartId);
    const component = chartComponentRegistry.get(type);
    if (!component) {
      throw new Error(`Component is not defined for type ${type}`);
    }
    return component;
  }

  onCarouselDoubleClick() {
    this.env.model.dispatch("SELECT_FIGURE", { figureId: this.props.figureUI.id });
    this.env.openSidePanel("CarouselPanel", { figureId: this.props.figureUI.id });
  }

  onCarouselChartDoubleClick() {
    if (this.selectedCarouselItem?.type !== "chart") {
      return;
    }
    const chartId = this.selectedCarouselItem.chartId;
    this.env.model.dispatch("SELECT_FIGURE", { figureId: this.props.figureUI.id });
    this.env.openSidePanel("ChartPanel", { chartId });
  }

  isItemSelected(item: CarouselItem): boolean {
    const selectedItem = this.selectedCarouselItem;
    return deepEquals(selectedItem, item);
  }

  getItemTitle(item: CarouselItem): string {
    return getCarouselItemTitle(this.env.model.getters, item);
  }

  onCarouselTabClick(item: CarouselItem) {
    this.env.model.dispatch("UPDATE_CAROUSEL_ACTIVE_ITEM", {
      figureId: this.props.figureUI.id,
      sheetId: this.env.model.getters.getActiveSheetId(),
      item,
    });
    if (item.type === "chart") {
      const animationChartId = item.chartId + (this.props.isFullScreen ? "-fullscreen" : "");
      this.animationStore?.enableAnimationForChart(animationChartId);
    }
  }

  get carouselStyle(): string {
    const cssProperties: CSSProperties = {};
    const backgroundColor = this.env.model.getters.getSpreadsheetTheme().chartBackgroundColor;
    if (this.selectedCarouselItem?.type === "chart") {
      const chart = this.env.model.getters.getChartRuntime(this.selectedCarouselItem.chartId);
      if ("background" in chart && chart.background) {
        cssProperties["background-color"] = chart.background;
      } else if ("chartJsConfig" in chart) {
        cssProperties["background-color"] =
          chart.chartJsConfig.options?.plugins?.background?.color || backgroundColor;
      }
    } else {
      cssProperties["background-color"] = backgroundColor;
    }
    return cssPropertiesToCss(cssProperties);
  }

  get contentStyle(): string {
    return cssPropertiesToCss(this.rectToCss(this.carouselLayout.contentRect));
  }

  get headerStyle(): string {
    return cssPropertiesToCss({
      ...this.rectToCss(this.carouselLayout.headerRect),
      "line-height": String(this.carouselLayout.headerLineHeight),
    });
  }

  get separatorStyle(): string {
    const separatorRect = this.carouselLayout.separatorRect;
    return separatorRect ? cssPropertiesToCss(this.rectToCss(separatorRect)) : "";
  }

  get title(): string {
    return this.env.model.getters.dynamicTranslate(this.carousel.title?.text ?? "");
  }

  get titleStyle(): string {
    const style = { ...DEFAULT_CAROUSEL_TITLE_STYLE, ...this.carousel.title };
    return cssPropertiesToCss(cellTextStyleToCss(chartStyleToCellStyle(style)));
  }

  private rectToCss(rect: Rect): CSSProperties {
    return {
      top: `${rect.y}px`,
      left: `${rect.x}px`,
      width: `${rect.width}px`,
      height: `${rect.height}px`,
    };
  }

  private updateTabsVisibility(): void {
    const tabsContainerEl = this.carouselTabsRef();
    const dropDownEl = this.carouselTabsDropdownRef();
    if (!tabsContainerEl || !dropDownEl) {
      return;
    }

    this.hiddenItems = [];

    const containerRect = getBoundingRectAsPOJO(tabsContainerEl);
    const tabs = Array.from(tabsContainerEl.children) as HTMLElement[];

    for (const tab of tabs) {
      tab.style.display = "block";
    }

    const tabWidths = tabs.map((tab) => getBoundingRectAsPOJO(tab).width);

    let currentWidth = 0;
    for (let i = 0; i < tabs.length; i++) {
      const shouldBeHidden = currentWidth + tabWidths[i] > containerRect.width;
      currentWidth += tabWidths[i];
      if (shouldBeHidden) {
        tabs[i].style.display = "none";
        this.hiddenItems.push(this.carousel.items[i]);
      }
    }

    dropDownEl.style.display = this.hiddenItems.length ? "block" : "none";
  }

  get menuId() {
    return "carousel-tabs-menu-";
  }

  toggleMenu(ev: MenuMouseEvent) {
    if (ev.closedMenuId === this.menuId) {
      this.menuState.isOpen = false;
      return;
    }
    const rect = getElBoundingRect(this.carouselTabsDropdownRef());
    const menuItems: ActionSpec[] = this.hiddenItems.map((item) => ({
      name: this.getItemTitle(item),
      execute: () => this.onCarouselTabClick(item),
      isActive: () => this.isItemSelected(item),
      isReadonlyAllowed: true,
    }));
    this.menuState.isOpen = true;
    this.menuState.anchorRect = rect;
    this.menuState.menuItems = createActions(menuItems);
  }

  toggleFullScreen() {
    this.fullScreenFigureStore.toggleFullScreenFigure(this.props.figureUI.id);
  }

  get fullScreenButtonTitle(): string {
    return this.props.isFullScreen ? _t("Exit Full Screen") : _t("Full Screen");
  }

  openContextMenu(event: MenuMouseEvent) {
    if (event.closedMenuId === "figure-context-menu") {
      return;
    }
    const target = event.currentTarget as HTMLElement;
    if (target) {
      this.props.openContextMenu?.(getBoundingRectAsPOJO(target));
    }
  }

  removeEmptyHeadersFromRange(range: Range): Range {
    if (range.invalidXc || range.invalidSheetName) {
      const id = this.props.figureUI.id;
      throw new Error(
        `Invalid range provided to carousel ${id} data view ${JSON.stringify(this.carousel)}`
      );
    }
    const zone = range.zone;
    let lastUsedRow = zone.top;
    const sheetId = range.sheetId;

    for (const position of cellPositions(sheetId, zone)) {
      if (this.env.model.getters.getEvaluatedCell(position).type === CellValueType.empty) {
        continue;
      }
      lastUsedRow = Math.max(lastUsedRow, position.row);
    }

    const newZone = { ...range.zone, bottom: lastUsedRow };
    return this.env.model.getters.getRangeFromZone(sheetId, newZone);
  }

  onResizeColumns(columnWeights: number[]) {
    const selectedItem = this.selectedCarouselItem;
    if (selectedItem?.type !== "carouselDataView") {
      return;
    }

    const carousel = this.env.model.getters.getCarousel(this.props.figureUI.id);
    const index = carousel.items.findIndex((item) => deepEquals(item, selectedItem));
    if (index === -1) {
      return;
    }

    const newItems = [...carousel.items];
    newItems[index] = { ...selectedItem, columnWeights };
    this.env.model.dispatch("UPDATE_CAROUSEL", {
      figureId: this.props.figureUI.id,
      sheetId: this.env.model.getters.getActiveSheetId(),
      definition: this.env.model.getters.carouselToCarouselData({ ...carousel, items: newItems }),
    });
  }

  get canResizeDataViewColumns() {
    return (
      !this.env.model.getters.isReadonly() &&
      !this.env.model.getters.isSheetLocked(this.env.model.getters.getActiveSheetId())
    );
  }

  get carouselLayout() {
    const rect: Rect = { ...this.props.figureUI, x: 0, y: 0 }; // {0, 0} to have positions relative to the figure
    return getCarouselLayout(rect, this.carousel, this.selectedCarouselItem);
  }
}
