import { onWillUpdateProps, proxy, signal, useProps } from "@odoo/owl";
import { ActionSpec } from "../../../actions/action";
import { DEFAULT_CAROUSEL_TITLE_STYLE } from "../../../constants";
import {
  getCarouselItemPreview,
  getCarouselItemTitle,
  getPoppedOutChartAnchor,
} from "../../../helpers/carousel_helpers";
import { SpreadsheetChart } from "../../../helpers/figures/chart";
import { deepEquals } from "../../../helpers/misc";
import { UuidGenerator } from "../../../helpers/uuid";
import { Component } from "../../../owl3_compatibility_layer";
import { chartDataSourceRegistry } from "../../../registries/chart_data_source_registry";
import { chartTypeRegistry } from "../../../registries/chart_registry";
import { chartSubtypeRegistry } from "../../../registries/chart_subtype_registry";
import { _t } from "../../../translation";
import { CHART_TYPES, ChartDefinition, TitleDesign } from "../../../types/chart/chart";
import { CarouselItem } from "../../../types/figure";
import { UID } from "../../../types/misc";
import { PropsOf } from "../../../types/props_of";
import { SpreadsheetChildEnv } from "../../../types/spreadsheet_env";
import { getBoundingRectAsPOJO } from "../../helpers/dom_helpers";
import { useDragAndDropListItems } from "../../helpers/drag_and_drop_dom_items_hook";
import { Popover } from "../../popover/popover";
import { types } from "../../props_validation";
import { SelectionInput } from "../../selection_input/selection_input";
import { TextInput } from "../../text_input/text_input";
import { TextStyler } from "../chart/building_blocks/text_styler/text_styler";
import { ChartTypePickerPopover } from "../chart/chart_type_picker_popover/chart_type_picker_popover";
import { CogWheelMenu } from "../components/cog_wheel_menu/cog_wheel_menu";
import { Section } from "../components/section/section";

interface CarouselPanelState {
  popoverProps: PropsOf<Popover> | undefined;
  currentRange?: string;
}

export class CarouselPanel extends Component<SpreadsheetChildEnv> {
  static template = "o-spreadsheet-CarouselPanel";
  static components = {
    Section,
    TextInput,
    TextStyler,
    CogWheelMenu,
    ChartTypePickerPopover,
    SelectionInput,
  };

  protected props = useProps({
    onCloseSidePanel: types.function(),
    figureId: types.UID(),
  });

  DEFAULT_CAROUSEL_TITLE_STYLE = DEFAULT_CAROUSEL_TITLE_STYLE;

  private dragAndDrop = useDragAndDropListItems();
  private previewListRef = signal.ref();
  addChartButton = signal.ref(HTMLButtonElement);

  state = proxy<CarouselPanelState>({ popoverProps: undefined, currentRange: undefined });

  setup() {
    let lastCarouselItems: CarouselItem[] = [...this.carouselItems];
    onWillUpdateProps(() => {
      if (!deepEquals(this.carouselItems, lastCarouselItems)) {
        this.dragAndDrop.cancel();
      }
      lastCarouselItems = [...this.carouselItems];
    });
  }

  get carouselItems(): CarouselItem[] {
    return this.env.model.getters.getCarousel(this.props.figureId).items;
  }

  get title(): TitleDesign | undefined {
    return this.env.model.getters.getCarousel(this.props.figureId).title;
  }

  get carousel() {
    return this.env.model.getters.getCarousel(this.props.figureId);
  }

  getPreviewDivStyle(item: CarouselItem): string {
    return this.dragAndDrop.itemsStyle[this.getItemId(item)] || "";
  }

  getItemId(item: CarouselItem): string {
    return item.type === "chart" ? item.chartId : "transparent-carousel";
  }

  addNewChartToCarousel(ev: MouseEvent) {
    if (this.state.popoverProps) {
      this.state.popoverProps = undefined;
      return;
    }
    const target = ev.currentTarget as HTMLElement;
    const { bottom, right } = target.getBoundingClientRect();
    this.state.popoverProps = {
      anchorRect: { x: right, y: bottom, width: 0, height: 0 },
      positioning: "top-right",
      verticalOffset: 0,
      maxWidth: target.parentElement?.getBoundingClientRect().width || 300,
    };
  }

  get supportedChartTypes() {
    return new Set(CHART_TYPES);
  }

  closePopover() {
    this.state.popoverProps = undefined;
  }

  onSelectChartType(type: string) {
    const newChartInfo = chartSubtypeRegistry.get(type);
    const ChartTypeBuilder = chartTypeRegistry.get(newChartInfo.chartType);
    const DataSourceBuilder = chartDataSourceRegistry.get("range");
    const definition = SpreadsheetChart.deleteInvalidKeys({
      ...ChartTypeBuilder.getDefinitionFromContextCreation({}, DataSourceBuilder),
      ...newChartInfo.subtypeDefinition,
    } as ChartDefinition);

    this.env.model.dispatch("ADD_NEW_CHART_TO_CAROUSEL", {
      figureId: this.props.figureId,
      sheetId: this.carouselSheetId,
      newChartId: UuidGenerator.smallUuid(),
      chartDefinition: definition,
    });
    this.env.model.dispatch("SELECT_FIGURE", { figureId: this.props.figureId });
    this.env.openSidePanel("ChartPanel");
  }

  get hasDataView(): boolean {
    return this.carouselItems.some((item) => item.type === "carouselDataView");
  }

  isCarouselItemActive(item: CarouselItem): boolean {
    const activeItem = this.env.model.getters.getSelectedCarouselItem(this.props.figureId);
    return deepEquals(activeItem, item);
  }

  addDataViewToCarousel() {
    const carousel = this.env.model.getters.getCarousel(this.props.figureId);
    const selection = this.env.model.getters.getSelectedZone();
    const sheetId = this.env.model.getters.getActiveSheetId();
    this.updateItems([
      ...carousel.items,
      {
        type: "carouselDataView",
        range: this.env.model.getters.getRangeFromZone(sheetId, selection),
      },
    ]);
  }

  activateCarouselItem(item: CarouselItem) {
    this.env.model.dispatch("UPDATE_CAROUSEL_ACTIVE_ITEM", {
      figureId: this.props.figureId,
      sheetId: this.carouselSheetId,
      item,
    });
  }

  editCarouselItem(item: CarouselItem) {
    if (item.type === "chart") {
      this.activateCarouselItem(item);
      this.env.model.dispatch("SELECT_FIGURE", { figureId: this.props.figureId });
      this.env.openSidePanel("ChartPanel", { chartId: item.chartId });
    }
  }

  renameCarouselItem(item: CarouselItem, newName: string) {
    const trimmedName = newName.trim();
    if (!trimmedName || trimmedName === this.getItemTitle(item).toString()) {
      return;
    }
    const items = [...this.carouselItems];
    const itemIndex = this.carouselItems.findIndex((itm) => deepEquals(itm, item));
    if (itemIndex !== -1) {
      items[itemIndex] = { ...item, title: trimmedName };
      this.updateItems(items);
    }
  }

  deleteCarouselItem(item: CarouselItem) {
    const carousel = this.env.model.getters.getCarousel(this.props.figureId);
    const items = carousel.items.filter((itm) => !deepEquals(itm, item));
    this.updateItems(items);
  }

  popOutCarouselItem(item: CarouselItem) {
    if (item.type !== "chart") {
      return;
    }
    const anchor = getPoppedOutChartAnchor(this.env, this.carouselSheetId, this.props.figureId);
    this.env.model.dispatch("POPOUT_CHART_FROM_CAROUSEL", {
      sheetId: this.carouselSheetId,
      carouselId: this.props.figureId,
      chartId: item.chartId,
      ...anchor,
    });
  }

  duplicateCarouselChart(item: CarouselItem) {
    if (item.type !== "chart") {
      return;
    }
    this.env.model.dispatch("DUPLICATE_CAROUSEL_CHART", {
      sheetId: this.carouselSheetId,
      carouselId: this.props.figureId,
      chartId: item.chartId,
      duplicatedChartId: UuidGenerator.smallUuid(),
    });
  }

  onDragHandleMouseDown(item: CarouselItem, event: MouseEvent) {
    if (event.button !== 0) {
      return;
    }
    const previewListEl = this.previewListRef();
    if (!previewListEl) {
      return;
    }
    const previewRects = Array.from(previewListEl.children).map((previewEl) =>
      getBoundingRectAsPOJO(previewEl)
    );
    const items = this.carouselItems.map((item, index) => ({
      id: this.getItemId(item),
      size: previewRects[index].height,
      position: previewRects[index].y,
    }));
    this.dragAndDrop.start("vertical", {
      draggedItemId: this.getItemId(item),
      initialMousePosition: event.clientY,
      items: items,
      scrollableContainerEl: previewListEl,
      onDragEnd: (itemId: string, finalIndex: number) => this.onDragEnd(item, finalIndex),
    });
  }

  private onDragEnd(item: CarouselItem, finalIndex: number) {
    const originalIndex = this.carouselItems.findIndex((itm) => deepEquals(itm, item));
    if (originalIndex === -1 || originalIndex === finalIndex) {
      return;
    }
    const carousel = this.env.model.getters.getCarousel(this.props.figureId);
    const items = [...carousel.items];
    items.splice(originalIndex, 1);
    items.splice(finalIndex, 0, item);
    this.updateItems(items);
  }

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

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

  updateItems(items: CarouselItem[]) {
    this.env.model.dispatch("UPDATE_CAROUSEL", {
      figureId: this.props.figureId,
      sheetId: this.carouselSheetId,
      definition: this.env.model.getters.carouselToCarouselData({ ...this.carousel, items }),
    });
  }

  updateTitleText(title: string) {
    const carousel = this.env.model.getters.getCarousel(this.props.figureId);
    this.env.model.dispatch("UPDATE_CAROUSEL", {
      figureId: this.props.figureId,
      sheetId: this.carouselSheetId,
      definition: this.env.model.getters.carouselToCarouselData({
        ...carousel,
        title: {
          ...carousel.title,
          text: title,
        },
      }),
    });
  }

  updateTitleStyle(style: TitleDesign) {
    const carousel = this.env.model.getters.getCarousel(this.props.figureId);
    this.env.model.dispatch("UPDATE_CAROUSEL", {
      figureId: this.props.figureId,
      sheetId: this.carouselSheetId,
      definition: this.env.model.getters.carouselToCarouselData({
        ...carousel,
        title: {
          ...carousel.title,
          ...style,
        },
      }),
    });
  }

  get carouselAddChartInfoMessage(): string {
    return _t(
      "Add a chart to the carousel. You can also add a chart by dragging and dropping it over the carousel figure."
    );
  }

  getCogWheelMenuItems(item: CarouselItem): ActionSpec[] {
    const actions: ActionSpec[] = [];
    if (item.type === "chart") {
      actions.push({
        name: _t("Edit chart"),
        execute: () => this.editCarouselItem(item),
        icon: "o-spreadsheet-Icon.EDIT",
      });
      actions.push({
        name: _t("Pop out chart"),
        execute: () => this.popOutCarouselItem(item),
        icon: "o-spreadsheet-Icon.EXTERNAL",
      });
      actions.push({
        name: _t("Duplicate chart"),
        execute: () => this.duplicateCarouselChart(item),
        icon: "o-spreadsheet-Icon.COPY",
      });
    }
    actions.push({
      name: _t("Delete item"),
      execute: () => this.deleteCarouselItem(item),
      icon: "o-spreadsheet-Icon.TRASH",
    });
    return actions;
  }

  get carouselSheetId(): UID {
    const sheetId = this.env.model.getters.getFigureSheetId(this.props.figureId);
    if (!sheetId) {
      throw new Error("Could not find the sheetId of the carousel figure");
    }
    return sheetId;
  }

  getItemRangeString(item: CarouselItem): string {
    if (item.type !== "carouselDataView" || !item.range) {
      return "";
    }
    return this.env.model.getters.getRangeString(item.range);
  }

  onSelectionInputChanged(ranges: string[]) {
    this.state.currentRange = ranges[0];
  }

  onSelectionInputConfirmed() {
    const index = this.carouselItems.findIndex((item) => item.type === "carouselDataView");
    if (index === -1 || !this.state.currentRange) {
      return;
    }
    const items = [...this.carouselItems];
    items[index] = {
      ...(items[index] as Extract<CarouselItem, { type: "carouselDataView" }>),
      range: this.env.model.getters.getRangeFromSheetXC(
        this.env.model.getters.getActiveSheetId(),
        this.state.currentRange
      ),
    };
    this.updateItems(items);
  }
}
