import { GridStack } from 'gridstack';
import pickBy from 'lodash/pickBy';
import { breakpoints } from '../../../../utils/breakpoints';
import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';

const CURSOR_GRABBING_CLASS = '!gl-cursor-grabbing';
var script = {
  name: 'GlGridLayout',
  props: {
    /**
     * The grid configuration object.
     *
     * @typedef {Object} DashboardGrid
     * @property {Array<Object>} panels - The dashboard panels. If empty, it will render an empty grid
     * @property {string} panels[].id - Each panel must have a unique ID.
     * @property {string} panels[].title - The panel title to render.
     * @property {Object} panels[].gridAttributes - Layout settings for the panel.
     * @property {number} panels[].gridAttributes.width - Width of the panel in grid units.
     * @property {number} panels[].gridAttributes.height - Height of the panel in grid units.
     * @property {number} panels[].gridAttributes.xPos - X position of the panel in the grid, expressed in grid units, starts from 0.
     * @property {number} panels[].gridAttributes.yPos - Y position of the panel in the grid, expressed in grid units, starts from 0.
     *
     * @type {DashboardGrid}
     */
    value: {
      type: Object,
      required: true
    },
    /**
     * Set to `false` to enable user-driven modification of the grid layout.
     */
    isStaticGrid: {
      type: Boolean,
      required: false,
      default: true
    },
    /**
     * Adjusts the cell height of the grid. Setting this too high can leave unwanted whitespace
     * between grid panels. Reduce the number to allow for a more compact grid.
     * For more information, see:
     * https://gitlab.com/gitlab-org/gitlab-services/design.gitlab.com/-/issues/3051
     */
    cellHeight: {
      type: Number,
      required: false,
      /* Magic number:
       * After allowing for padding, and the panel title row, this leaves us with minimum 48px height for the cell content.
       * This means text/content with our spacing scale can fit up to 49px without scrolling.
       */
      default: 137,
      validator: value => value > 0
    },
    /**
     * Sets a default minimum height for grid panels. This can still be overriden on a per-panel
     * basis by setting `value.panels[].gridAttributes.minHeight`
     */
    minCellHeight: {
      type: Number,
      required: false,
      default: 1,
      validator: value => value > 0
    }
  },
  data() {
    return {
      grid: undefined,
      gridPanels: []
    };
  },
  computed: {
    gridConfig() {
      return this.value.panels.map(panel => {
        const {
          gridAttributes,
          ...otherProps
        } = panel;
        return {
          ...this.getPanelGridItemConfig(panel),
          props: otherProps
        };
      });
    }
  },
  watch: {
    isStaticGrid(value) {
      var _this$grid;
      (_this$grid = this.grid) === null || _this$grid === void 0 ? void 0 : _this$grid.setStatic(value);
    },
    gridConfig: {
      handler(config) {
        var _this$grid2;
        (_this$grid2 = this.grid) === null || _this$grid2 === void 0 ? void 0 : _this$grid2.load(config);
      },
      deep: true
    },
    /**
     * Data flow:
     * 1. Initial: mounted → initGridStack() → grid.load(gridConfig) →
     *    grid.getGridItems() → initGridPanelSlots → gridPanels populated with DOM references
     * 2. Updates: value.panels changes → two parallel paths:
     *    a. gridConfig changes → grid.load() updates grid layout (but not gridPanels)
     *    b. this watcher updates gridPanels with new panel properties
     */
    'value.panels': {
      handler(newPanels) {
        if (this.gridPanels.length === 0) return;

        // Only update panels that have changed to improve performance
        newPanels.forEach(updatedPanel => {
          const panel = this.gridPanels.find(p => p.id === updatedPanel.id);
          if (panel) {
            // Exclude `gridAttributes` from being included in the panel props as it's not a valid prop for the panel component
            const panelPropsWithoutGridAttributes = pickBy(updatedPanel, (_, k) => k !== 'gridAttributes');
            panel.props = {
              ...panelPropsWithoutGridAttributes
            };
          }
        });
      },
      deep: true
    }
  },
  mounted() {
    this.initGridStack();
  },
  beforeDestroy() {
    var _this$grid3;
    const removeDom = Boolean(this.$el.parentElement);
    (_this$grid3 = this.grid) === null || _this$grid3 === void 0 ? void 0 : _this$grid3.destroy(removeDom);
  },
  methods: {
    // TODO: Refactor this to use render methods once Vue 3 migration is complete
    // https://gitlab.com/gitlab-org/gitlab/-/issues/549095
    async mountGridComponents(panels) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
        scrollIntoView: false
      };
      // Ensure new panels are always rendered first
      await this.$nextTick();
      panels.forEach(panel => {
        var _this$$refs$panelWrap;
        const wrapper = (_this$$refs$panelWrap = this.$refs.panelWrappers) === null || _this$$refs$panelWrap === void 0 ? void 0 : _this$$refs$panelWrap.find(w => w.id === panel.id);
        const widgetContentEl = panel.el.querySelector('.grid-stack-item-content');
        if (wrapper && widgetContentEl) {
          widgetContentEl.appendChild(wrapper);
        }
      });
      if (options.scrollIntoView) {
        const mostRecent = panels[panels.length - 1];
        mostRecent.el.scrollIntoView({
          behavior: 'smooth'
        });
      }
    },
    getGridItemForElement(el) {
      return this.gridConfig.find(item => item.id === el.getAttribute('gs-id'));
    },
    initGridPanelSlots(gridElements) {
      if (!gridElements) return;
      this.gridPanels = gridElements.map(el => ({
        ...this.getGridItemForElement(el),
        el
      }));
      this.mountGridComponents(this.gridPanels);
    },
    initGridStack() {
      // See https://github.com/gridstack/gridstack.js/tree/master/doc#grid-options
      this.grid = GridStack.init({
        // Uniform gap between panels
        margin: '8px',
        // CSS Selector for finding the drag handle element
        handle: '.grid-stack-item-handle',
        // Setting 1 in minRow prevents the grid collapsing when all panels are removed
        minRow: 1,
        // Define the number of columns for anything below a set width, defaults to fill the available space
        columnOpts: {
          breakpoints: [{
            w: breakpoints.md,
            c: 1
          }]
        },
        cellHeight: this.cellHeight,
        alwaysShowResizeHandle: true,
        animate: true,
        float: true,
        // Toggles user-customization of grid layout
        staticGrid: this.isStaticGrid
      }, this.$refs.grid).load(this.gridConfig);

      // Sync Vue components array with gridstack items
      this.initGridPanelSlots(this.grid.getGridItems());
      this.grid.on('dragstart', () => {
        this.$el.classList.add(CURSOR_GRABBING_CLASS);
      });
      this.grid.on('dragstop', () => {
        this.$el.classList.remove(CURSOR_GRABBING_CLASS);
      });
      this.grid.on('change', (_, items) => {
        if (!items) return;
        this.emitLayoutChanges(items);
      });
      this.grid.on('added', (_, items) => {
        this.addGridPanels(items);
      });
      this.grid.on('removed', (_, items) => {
        this.removeGridPanels(items);
      });
    },
    getPanelGridItemConfig(_ref) {
      let {
        gridAttributes: {
          xPos,
          yPos,
          width,
          height,
          minHeight,
          minWidth,
          maxHeight,
          maxWidth
        },
        id
      } = _ref;
      const filterUndefinedValues = obj => pickBy(obj, value => value !== undefined);

      // GridStack renders undefined layout values so we need to filter them out.
      return filterUndefinedValues({
        x: xPos,
        y: yPos,
        w: width,
        h: height,
        minH: minHeight || this.minCellHeight,
        minW: minWidth,
        maxH: maxHeight,
        maxW: maxWidth,
        id
      });
    },
    convertToGridAttributes(gridStackItem) {
      return {
        yPos: gridStackItem.y,
        xPos: gridStackItem.x,
        width: gridStackItem.w,
        height: gridStackItem.h
      };
    },
    removeGridPanels(items) {
      items.forEach(item => {
        const index = this.gridPanels.findIndex(c => c.id === item.id);
        this.gridPanels.splice(index, 1);
        // Finally, remove the gridstack element
        item.el.remove();
      });
    },
    addGridPanels(items) {
      const newPanels = items.map(_ref2 => {
        let {
          grid,
          ...rest
        } = _ref2;
        return {
          ...rest
        };
      });
      this.gridPanels.push(...newPanels);
      this.mountGridComponents(newPanels, {
        scrollIntoView: true
      });
    },
    emitLayoutChanges(items) {
      /**
       * Uses JSON parse and stringify to remove object references.
       * Lodash's `cloneDeep` retains circular references.
       * See https://github.com/lodash/lodash/issues/4710#issuecomment-606892867 for details on cloneDeep circular references
       * See https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm for the underlying mechanism used by Lodash
       */
      const newValue = JSON.parse(JSON.stringify(this.value));
      items.forEach(item => {
        const panel = newValue.panels.find(p => p.id === item.id);
        if (!panel) return;
        panel.gridAttributes = {
          ...panel.gridAttributes,
          ...this.convertToGridAttributes(item)
        };
      });
      this.$emit('input', newValue);
    }
  }
};

/* script */
const __vue_script__ = script;

/* template */
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{ref:"grid",staticClass:"grid-stack",attrs:{"data-testid":"gridstack-grid"}},_vm._l((_vm.gridPanels),function(panel){return _c('div',{key:panel.id,ref:"panelWrappers",refInFor:true,staticClass:"gl-h-full",class:{ 'gl-cursor-grab': !_vm.isStaticGrid },attrs:{"id":panel.id,"data-testid":"gridstack-panel"}},[_vm._t("panel",null,null,{ panel: panel.props })],2)}),0)};
var __vue_staticRenderFns__ = [];

  /* style */
  const __vue_inject_styles__ = undefined;
  /* scoped */
  const __vue_scope_id__ = undefined;
  /* module identifier */
  const __vue_module_identifier__ = undefined;
  /* functional template */
  const __vue_is_functional_template__ = false;
  /* style inject */
  
  /* style inject SSR */
  
  /* style inject shadow dom */
  

  
  const __vue_component__ = /*#__PURE__*/__vue_normalize__(
    { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
    __vue_inject_styles__,
    __vue_script__,
    __vue_scope_id__,
    __vue_is_functional_template__,
    __vue_module_identifier__,
    false,
    undefined,
    undefined,
    undefined
  );

export { __vue_component__ as default };
