import { isEqual } from 'lodash';
import React from 'react';

import {
  CustomVariable,
  MultiValueVariable,
  sceneGraph,
  SceneObjectBase,
  type SceneObjectState,
  VariableDependencyConfig,
  type VariableValueSingle,
  type VizPanel,
} from '@grafana/scenes';
import { type OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';

import { ConditionalRenderingGroup } from '../../conditional-rendering/group/ConditionalRenderingGroup';
import { DashboardStateChangedEvent, RepeatsUpdatedEvent } from '../../edit-pane/shared';
import { getCloneKey, getLocalVariableValueSet } from '../../utils/clone';
import { getMultiVariableValues } from '../../utils/utils';
import { scrollCanvasElementIntoView } from '../layouts-shared/scrollCanvasElementIntoView';
import { type DashboardLayoutItem } from '../types/DashboardLayoutItem';

import { getOptions } from './AutoGridItemEditor';
import { AutoGridItemRenderer } from './AutoGridItemRenderer';
import { AutoGridLayout } from './AutoGridLayout';

export interface AutoGridItemState extends SceneObjectState {
  body: VizPanel;
  hideWhenNoData?: boolean;
  repeatedPanels?: VizPanel[];
  variableName?: string;
  isHidden?: boolean;
  conditionalRendering?: ConditionalRenderingGroup;
  repeatedConditionalRendering?: ConditionalRenderingGroup[];
}

export class AutoGridItem extends SceneObjectBase<AutoGridItemState> implements DashboardLayoutItem {
  public static Component = AutoGridItemRenderer;

  protected _variableDependency = new VariableDependencyConfig(this, {
    variableNames: this.state.variableName ? [this.state.variableName] : [],
    onVariableUpdateCompleted: () => this.performRepeat(),
  });

  public readonly isDashboardLayoutItem = true;
  public containerRef = React.createRef<HTMLDivElement>();
  private _prevRepeatValues?: VariableValueSingle[];

  public constructor(state: AutoGridItemState) {
    super({ ...state, conditionalRendering: state?.conditionalRendering ?? ConditionalRenderingGroup.createEmpty() });
    this.addActivationHandler(() => this._activationHandler());
  }

  private _activationHandler() {
    if (this.state.variableName) {
      this.performRepeat();
    }

    this._subs.add(this.subscribeToEvent(DashboardStateChangedEvent, () => this.handleEditChange()));

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

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

  public getOptions(): OptionsPaneCategoryDescriptor[] {
    return getOptions(this);
  }

  public setElementBody(body: VizPanel): void {
    this.setState({ body });
  }

  public performRepeat() {
    if (!this.state.variableName || sceneGraph.hasVariableDependencyInLoadingState(this)) {
      return;
    }

    const variable =
      sceneGraph.lookupVariable(this.state.variableName, this) ??
      new CustomVariable({
        name: '_____default_sys_repeat_var_____',
        options: [],
        value: '',
        text: '',
        query: 'A',
      });

    if (!(variable instanceof MultiValueVariable)) {
      console.error('DashboardGridItem: Variable is not a MultiValueVariable');
      return;
    }

    const { values, texts } = getMultiVariableValues(variable);

    if (isEqual(this._prevRepeatValues, values)) {
      return;
    }

    const panelToRepeat = this.state.body;
    const repeatedPanels: VizPanel[] = [];

    // when variable has no options (due to error or similar) it will not render any panels at all
    // adding a placeholder in this case so that there is at least empty panel that can display error
    const emptyVariablePlaceholderOption = {
      values: [''],
      texts: variable.hasAllValue() ? ['All'] : ['None'],
    };

    const variableValues = values.length ? values : emptyVariablePlaceholderOption.values;
    const variableTexts = texts.length ? texts : emptyVariablePlaceholderOption.texts;

    // Loop through variable values and create repeats
    for (let index = 0; index < variableValues.length; index++) {
      const isSource = index === 0;
      const clone = isSource
        ? panelToRepeat
        : panelToRepeat.clone({
            key: getCloneKey(panelToRepeat.state.key!, index),
            repeatSourceKey: panelToRepeat.state.key,
          });

      clone.setState({ $variables: getLocalVariableValueSet(variable, variableValues[index], variableTexts[index]) });

      if (index > 0) {
        repeatedPanels.push(clone);
      }
    }

    let repeatedConditionalRendering: ConditionalRenderingGroup[] | undefined;

    if (this.state.conditionalRendering) {
      repeatedConditionalRendering = repeatedPanels.reduce<ConditionalRenderingGroup[]>((acc, panel) => {
        const conditionalRendering = this.state.conditionalRendering!.clone();
        conditionalRendering.setTarget(panel);
        acc.push(conditionalRendering);

        return acc;
      }, []);

      this.state.conditionalRendering.setTarget(panelToRepeat);
    }

    this.setState({ repeatedPanels, repeatedConditionalRendering });
    this._prevRepeatValues = values;
    this.publishEvent(new RepeatsUpdatedEvent(this), true);
  }

  public getPanelCount() {
    return (this.state.repeatedPanels?.length ?? 0) + 1;
  }

  public setRepeatByVariable(variableName: string | undefined) {
    const stateUpdate: Partial<AutoGridItemState> = { variableName };

    if (!variableName) {
      stateUpdate.repeatedPanels = undefined;
    }

    if (this.state.body.state.$variables) {
      this.state.body.setState({ $variables: undefined });
    }

    this._variableDependency.setVariableNames(variableName ? [variableName] : []);

    this.setState(stateUpdate);
    this.performRepeat();
  }

  public getParentGrid(): AutoGridLayout {
    if (!(this.parent instanceof AutoGridLayout)) {
      throw new Error('Parent is not a AutoGridLayout');
    }

    return this.parent;
  }

  public getBoundingBox(): { width: number; height: number; top: number; left: number } {
    const rect = this.containerRef.current!.getBoundingClientRect();

    return {
      width: rect.width,
      height: rect.height,
      top: this.containerRef.current!.offsetTop,
      left: this.containerRef.current!.offsetLeft,
    };
  }

  public handleEditChange() {
    this._prevRepeatValues = undefined;

    this.performRepeat();
  }

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