import { addResizeObserver } from '@wix/bex-core';
import {
  action,
  computed,
  makeObservable,
  observable,
  runInAction,
} from 'mobx';
import { ToolbarResponsivePriorityState } from './ToolbarResponsivePriorityState';
import { ToolbarState } from '../../components/CollectionToolbars/ToolbarState';
import { ToolbarLeftGroupState } from './ToolbarLeftGroupState';
import { computeResponsiveTargetState } from './computeResponsiveTargetState';
import {
  OVERFLOW_BUTTON_WIDTH,
  SEARCH_COLLAPSED_WIDTH,
  SEARCH_EXPANDED_WIDTH,
} from './toolbarResponsiveConstants';

export interface ToolbarResponsiveStateParams {
  readonly toolbar: ToolbarState<any, any>;
}

// Buffer (in px) added to the expand threshold to prevent flickering near the
// expand/collapse boundary. Tuned to ~2-3x the inter-element gap (SP2 = 12px) --
// large enough to absorb measurement rounding and flex reflow, small enough that
// the dead zone per group isn't visually noticeable during gradual resizing.
const EXPAND_THRESHOLD_BUFFER = 30;

export class ToolbarResponsiveState {
  readonly actions: ToolbarResponsivePriorityState;
  readonly search: ToolbarResponsivePriorityState;
  readonly filters: ToolbarResponsivePriorityState;

  private readonly _groups: ToolbarResponsivePriorityState[];

  _shouldOverflow = false;

  _measurementElement: Element | null = null;
  _contentElement: Element | null = null;

  // Width of non-group content that is removed from the DOM during overflow
  // (e.g., tableGridSwitchButton). Must be accounted for in the exit-overflow
  // calculation to prevent flicker.
  _overflowHiddenContentWidth = 0;

  readonly toolbar: ToolbarState<any, any>;

  leftGroupState: ToolbarLeftGroupState<any, any> | null = null;

  get container() {
    return this.toolbar.toolbar.container;
  }

  constructor(params: ToolbarResponsiveStateParams) {
    this.toolbar = params.toolbar;

    this.actions = new ToolbarResponsivePriorityState({
      responsive: this,
    });

    this.filters = new ToolbarResponsivePriorityState({
      responsive: this,
    });

    this.search = new ToolbarResponsivePriorityState({
      responsive: this,
      expandedWidth: SEARCH_EXPANDED_WIDTH,
      collapsedWidth: SEARCH_COLLAPSED_WIDTH,
    });

    this._groups = [this.actions, this.filters, this.search];

    makeObservable(this, {
      _shouldOverflow: observable.ref,
      _overflowHiddenContentWidth: observable.ref,
      responsiveDisabled: computed,
      _applyTargetState: action,
    });
  }

  get responsiveDisabled() {
    const {
      toolbar: {
        toolbar: { _syncedProps },
      },
      container: { internalExperiments },
    } = this;

    return (
      !_syncedProps?.useResponsiveLayout ||
      !internalExperiments?.enabled('specs.cairo.EnableResponsiveToolbar11420')
    );
  }

  _scheduleRecompute() {
    if (this.responsiveDisabled) {
      return;
    }
    this._recomputeThrottled?.();
  }

  private _computeResponsiveInput() {
    const el = this._measurementElement;
    const contentEl = this._contentElement;
    if (!el || !contentEl) {
      return null;
    }

    // The left group (title/tabs/views) has flex:1 and absorbs freed space when
    // right-side items shrink. Account for its excess width so the algorithm sees
    // the true available space and can expand items back when the container grows.
    const leftGroupExcess = this.leftGroupState?.getExcessWidth() ?? 0;
    const containerWidth = el.clientWidth + leftGroupExcess;
    const contentWidth = contentEl.scrollWidth;

    const groups = this._groups.map((g) => ({
      expandedWidth: g.expandedWidth,
      collapsedWidth: g.collapsedWidth,
      currentlyShrunk: g._shouldShrink,
    }));

    // When overflowing, the overflowable groups are removed from the DOM and
    // replaced by the overflow button. Compute groupsTotal to reflect what the
    // algorithm expects (all groups at their current widths) so that
    // availableWidth = containerWidth - nonGroupContent is accurate.
    let groupsTotal: number;
    if (this._shouldOverflow) {
      // contentWidth includes the overflow button but not the overflowable groups.
      // groupsTotal must match: only non-overflowed groups + overflow button.
      const overflowableIndices =
        ToolbarResponsiveState._overflowableGroupIndices;
      const nonOverflowedTotal = groups.reduce(
        (sum, g, i) =>
          overflowableIndices.includes(i)
            ? sum
            : sum + (g.currentlyShrunk ? g.collapsedWidth : g.expandedWidth),
        0,
      );
      groupsTotal = nonOverflowedTotal + OVERFLOW_BUTTON_WIDTH;
    } else {
      groupsTotal = groups.reduce(
        (sum, g) =>
          sum + (g.currentlyShrunk ? g.collapsedWidth : g.expandedWidth),
        0,
      );
    }

    // nonGroupContent that is hidden during overflow but would reappear on exit.
    // Must be added back so the algorithm doesn't overestimate available space.
    const hiddenContentAdjustment = this._shouldOverflow
      ? this._overflowHiddenContentWidth
      : 0;

    return {
      availableWidth:
        containerWidth - (contentWidth - groupsTotal) - hiddenContentAdjustment,
      groups,
    };
  }

  // Indices of groups that can be absorbed into the overflow menu.
  // actions = 0, filters = 1 (search = 2 stays visible).
  private static readonly _overflowableGroupIndices = [0, 1];

  _applyTargetState() {
    const input = this._computeResponsiveInput();
    if (!input) {
      return;
    }

    const result = computeResponsiveTargetState({
      ...input,
      expandThresholdBuffer: EXPAND_THRESHOLD_BUFFER,
      currentlyOverflowing: this._shouldOverflow,
      overflowButtonWidth: OVERFLOW_BUTTON_WIDTH,
      overflowableGroupIndices:
        ToolbarResponsiveState._overflowableGroupIndices,
    });

    this._groups.forEach((g, i) => {
      g._shouldShrink = result.groups[i].shouldShrink;
    });
    this._shouldOverflow = result.shouldOverflow;
  }

  private _recomputeThrottled?: ReturnType<
    typeof this.container.lodash.throttle
  >;
  private _rafId = 0;

  init() {
    const { responsiveDisabled } = this;

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

    this._recomputeThrottled = this.container.lodash.throttle(() => {
      this._rafId = window.requestAnimationFrame(() => {
        runInAction(() => this._applyTargetState());
      });
    }, 200);

    const containerEl = this.toolbar.toolbar.toolbarRect.element;
    if (!containerEl) {
      return () => {
        this._recomputeThrottled?.cancel();
      };
    }

    const disposers = [
      addResizeObserver(containerEl, this._recomputeThrottled),
    ];

    return () => {
      disposers.forEach((d) => d?.());
      this._recomputeThrottled?.cancel();
      cancelAnimationFrame(this._rafId);
    };
  }
}
