import React from 'react';

import { store } from '@grafana/data';
import { t } from '@grafana/i18n';
import { logWarning } from '@grafana/runtime';
import { getFeatureFlagClient } from '@grafana/runtime/internal';
import {
  NewSceneObjectAddedEvent,
  sceneGraph,
  type SceneObject,
  SceneObjectBase,
  type SceneObjectState,
  VariableDependencyConfig,
  type SceneGridItemLike,
  SceneGridLayout,
} from '@grafana/scenes';
import { type RowsLayoutRowKind } from '@grafana/schema/apis/dashboard.grafana.app/v2';
import { appEvents } from 'app/core/app_events';
import { LS_ROW_COPY_KEY } from 'app/core/constants';
import kbn from 'app/core/utils/kbn';
import { ShowConfirmModalEvent } from 'app/types/events';

import { ConditionalRenderingGroup } from '../../conditional-rendering/group/ConditionalRenderingGroup';
import { dashboardEditActions } from '../../edit-pane/shared';
import { serializeRow } from '../../serialization/layoutSerializers/RowsLayoutSerializer';
import { getElements } from '../../serialization/layoutSerializers/utils';
import { removeRepeatLocalVariableFromSet } from '../../utils/clone';
import { type PanelIdGenerator } from '../../utils/dashboardSceneGraph';
import { trackDropItemCrossLayout } from '../../utils/tracking';
import { getDashboardSceneFor, interpolateSectionTitle } from '../../utils/utils';
import { AutoGridItem } from '../layout-auto-grid/AutoGridItem';
import { AutoGridLayout } from '../layout-auto-grid/AutoGridLayout';
import { AutoGridLayoutManager } from '../layout-auto-grid/AutoGridLayoutManager';
import { DashboardGridItem } from '../layout-default/DashboardGridItem';
import { clearClipboard } from '../layouts-shared/paste';
import { scrollCanvasElementIntoView } from '../layouts-shared/scrollCanvasElementIntoView';
import { type BulkActionElement } from '../types/BulkActionElement';
import { type DashboardDropTarget } from '../types/DashboardDropTarget';
import { isDashboardLayoutGrid } from '../types/DashboardLayoutGrid';
import { type DashboardLayoutManager } from '../types/DashboardLayoutManager';
import { type EditableDashboardElement, type EditableDashboardElementInfo } from '../types/EditableDashboardElement';
import { type LayoutParent } from '../types/LayoutParent';

import { useEditOptions } from './RowItemEditor';
import { RowItemRenderer } from './RowItemRenderer';
import { RowItems } from './RowItems';
import { RowsLayoutManager } from './RowsLayoutManager';

export interface RowItemState extends SceneObjectState {
  layout: DashboardLayoutManager;
  title?: string;
  collapse?: boolean;
  hideHeader?: boolean;
  fillScreen?: boolean;
  isDropTarget?: boolean;
  conditionalRendering?: ConditionalRenderingGroup;
  repeatByVariable?: string;
  repeatedRows?: RowItem[];
  /** Marks object as a repeated object and a key pointer to source object */
  repeatSourceKey?: string;
}

export class RowItem
  extends SceneObjectBase<RowItemState>
  implements LayoutParent, BulkActionElement, EditableDashboardElement, DashboardDropTarget
{
  public static Component = RowItemRenderer;

  protected _variableDependency = new VariableDependencyConfig(this, {
    statePaths: ['title'],
  });

  public readonly isEditableDashboardElement = true;
  public readonly isDashboardDropTarget = true;
  public containerRef: React.MutableRefObject<HTMLDivElement | null> = React.createRef<HTMLDivElement>();

  public constructor(state?: Partial<RowItemState>) {
    super({
      ...state,
      title: state?.title ?? t('dashboard.rows-layout.row.new', 'New row'),
      layout: state?.layout ?? AutoGridLayoutManager.createEmpty(),
      conditionalRendering: state?.conditionalRendering ?? ConditionalRenderingGroup.createEmpty(),
    });

    this.addActivationHandler(() => this._activationHandler());
  }

  private _activationHandler() {
    const deactivate = this.state.conditionalRendering?.activate();

    return () => {
      if (deactivate) {
        deactivate();
      }
    };
  }

  public getEditableElementInfo(): EditableDashboardElementInfo {
    const isHidden = !this.state.conditionalRendering?.state.result;
    return {
      typeName: t('dashboard.edit-pane.elements.row', 'Row'),
      instanceName: interpolateSectionTitle(this, this.state.title),
      icon: 'list-ul',
      isHidden,
    };
  }

  public getOutlineChildren(isEditing?: boolean): SceneObject[] {
    const layoutChildren = this.state.layout.getOutlineChildren();
    if (
      isEditing &&
      getFeatureFlagClient().getBooleanValue('dashboardSectionVariables', false) &&
      this.state.$variables
    ) {
      return [this.state.$variables, ...layoutChildren];
    }
    return layoutChildren;
  }

  public getLayout(): DashboardLayoutManager {
    return this.state.layout;
  }

  public getSlug(): string {
    return kbn.slugifyForUrl(interpolateSectionTitle(this, this.state.title ?? 'Row'));
  }

  public switchLayout(layout: DashboardLayoutManager) {
    const currentLayout = this.state.layout;

    dashboardEditActions.edit({
      description: t('dashboard.edit-actions.switch-layout-row', 'Switch layout'),
      source: this,
      perform: () => {
        this.setState({ layout });
        this.publishEvent(new NewSceneObjectAddedEvent(this), true);
      },
      undo: () => {
        this.setState({ layout: currentLayout });
        this.publishEvent(new NewSceneObjectAddedEvent(this), true);
      },
    });
  }

  public useEditPaneOptions = useEditOptions.bind(this);

  public onDelete() {
    this.getParentLayout().removeRow(this);
  }

  public onConfirmDelete() {
    if (this.getLayout().getVizPanels().length === 0) {
      this.onDelete();
      return;
    }

    appEvents.publish(
      new ShowConfirmModalEvent({
        title: t('dashboard.rows-layout.delete-row-title', 'Delete row?'),
        text: t(
          'dashboard.rows-layout.delete-row-text',
          'Deleting this row will also remove all panels. Are you sure you want to continue?'
        ),
        yesText: t('dashboard.rows-layout.delete-row-yes', 'Delete'),
        onConfirm: () => {
          this.onDelete();
        },
      })
    );
  }

  public createMultiSelectedElement(items: SceneObject[]): RowItems {
    return new RowItems(items.filter((item) => item instanceof RowItem));
  }

  public onDuplicate() {
    this.getParentLayout().duplicateRow(this);
  }

  // panelIdGenerator is a shared sequential counter created by the parent layout
  // we forward id to ensure sibling tabs never produce duplicate panel IDs
  public duplicate(panelIdGenerator?: PanelIdGenerator): RowItem {
    return this.clone({ key: undefined, layout: this.getLayout().duplicate(panelIdGenerator) });
  }

  public serialize(): RowsLayoutRowKind {
    return serializeRow(this);
  }

  public onCopy() {
    const elements = getElements(this.getLayout(), getDashboardSceneFor(this));

    clearClipboard();
    store.set(LS_ROW_COPY_KEY, JSON.stringify({ elements, row: this.serialize() }));
  }

  public setIsDropTarget(isDropTarget: boolean) {
    if (!!this.state.isDropTarget !== isDropTarget) {
      this.setState({ isDropTarget });
    }
  }

  public draggedGridItemOutside?(gridItem: SceneGridItemLike): void {
    // Remove from source layout
    if (gridItem instanceof DashboardGridItem || gridItem instanceof AutoGridItem) {
      const layout = gridItem.parent;
      if (gridItem instanceof DashboardGridItem && layout instanceof SceneGridLayout) {
        // Toggle isDraggable off to force react-grid-layout to exit drag mode
        // This clears react-grid-layout's internal drag state
        // This is a workaround until we upgrade to react-grid-layout 2.x.x
        const wasDraggable = layout.state.isDraggable;
        if (wasDraggable) {
          layout.setState({ isDraggable: false });
        }

        const newChildren = layout.state.children.filter((child) => child !== gridItem);
        layout.setState({ children: newChildren });

        // Restore isDraggable after a microtask to ensure react-grid-layout processes the change
        if (wasDraggable) {
          queueMicrotask(() => {
            layout.setState({ isDraggable: true });
          });
        }
      } else if (gridItem instanceof AutoGridItem && layout instanceof AutoGridLayout) {
        const newChildren = layout.state.children.filter((child) => child !== gridItem);
        layout.setState({ children: newChildren });
      } else {
        const warningMessage = 'Grid item has unexpected parent type';
        console.warn(warningMessage);
        logWarning(warningMessage);
      }
    }
    this.setIsDropTarget(false);
  }

  public draggedGridItemInside(gridItem: SceneGridItemLike): void {
    trackDropItemCrossLayout(gridItem);
    const layout = this.getLayout();

    if (isDashboardLayoutGrid(layout)) {
      layout.addGridItem(gridItem);
    } else {
      const warningMessage = 'Layout manager does not support addGridItem';
      console.warn(warningMessage);
      logWarning(warningMessage);
    }
    this.setIsDropTarget(false);
  }

  public onChangeTitle(title: string) {
    this.setState({ title });
  }

  public onChangeName(name: string) {
    this.onChangeTitle(name);
  }

  public onHeaderHiddenToggle(hideHeader = !this.state.hideHeader) {
    this.setState({ hideHeader });
  }

  public onChangeFillScreen(fillScreen: boolean) {
    this.setState({ fillScreen });
  }

  public onChangeRepeat(repeat: string | undefined) {
    if (repeat) {
      this.setState({ repeatByVariable: repeat });
    } else {
      this.setState({
        repeatedRows: undefined,
        $variables: removeRepeatLocalVariableFromSet(this.state.$variables, this.state.repeatByVariable),
        repeatByVariable: undefined,
      });
    }
  }

  public onCollapseToggle() {
    this.setState({ collapse: !this.state.collapse });
  }

  public getParentLayout(): RowsLayoutManager {
    return sceneGraph.getAncestor(this, RowsLayoutManager);
  }

  public scrollIntoView() {
    scrollCanvasElementIntoView(this, this.containerRef);
  }

  public getCollapsedState(): boolean {
    return this.state.collapse ?? false;
  }

  public setCollapsedState(collapse: boolean) {
    this.setState({ collapse });
  }

  public hasUniqueTitle(): boolean {
    const parentLayout = this.getParentLayout();
    const duplicateTitles = parentLayout.duplicateTitles();
    return !duplicateTitles.has(this.state.title);
  }
}
