import { html } from 'lit';
import { state, query, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { boolean } from '../../internal/boolean.mjs';
import FlowRdfElement from '../../internal/flow-ui-rdf-element.mjs';
import { clamp } from '../../internal/math.mjs';
import { isNumber, isUndefined } from '../../internal/type-checks.mjs';
import FlowUiSplitViewPane from '../split-view-pane/split-view-pane.component.mjs';
import styles from './split-view.styles.mjs';

var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
    if (decorator = decorators[i])
      result = (kind ? decorator(target, key, result) : decorator(result)) || result;
  if (kind && result)
    __defProp(target, key, result);
  return result;
};
class FlowUiSplitView extends FlowRdfElement {
  constructor() {
    super(...arguments);
    // Saved panel layouts for restoring when panels are dynamically added/removed
    // The key is a comma-separated concatenation of panel ids e.g. 'pane-1,pane-2,pane-3'
    this._panelLayouts = {};
    this._panels = [];
    this.noDragClose = false;
    this.positions = ["auto", "auto", "auto", "auto"];
    this.vertical = false;
    this.dragging = false;
  }
  connectedCallback() {
    super.connectedCallback();
    this.vertical = boolean(this.vertical, true);
    this.noDragClose = boolean(this.noDragClose, true);
    this.resizeObserver = new ResizeObserver(() => {
      this.resizePanels();
    });
    this.mutationObserver = new MutationObserver(async (mutations) => {
      const panelsAdded = mutations.map((m) => Array.from(m.addedNodes.values())).flat().filter((n) => n instanceof FlowUiSplitViewPane);
      const panelsRemoved = mutations.map((m) => Array.from(m.removedNodes.values())).flat().filter((n) => n instanceof FlowUiSplitViewPane);
      if (panelsAdded.length > 0) {
        this.handlePanelAdded(panelsAdded[0]);
      }
      if (panelsRemoved.length > 0) {
        this.handlePanelRemoved(panelsRemoved[0]);
      }
      this.layoutPanels();
    });
    this.updateComplete.then(() => {
      this.updatePanelInfo();
      this.mutationObserver.observe(this, {
        attributes: true,
        childList: true,
        subtree: true,
        attributeFilter: ["closed", "hidden", "min-size", "max-size", "closed-size", "size"]
      });
      this.resizeObserver.observe(this.splitView);
      this.layoutPanels();
    });
  }
  disconnectedCallback() {
    this.mutationObserver.disconnect();
    this.resizeObserver.disconnect();
    super.disconnectedCallback();
  }
  render() {
    const gridTemplateProperty = this.vertical ? "grid-template-rows" : "grid-template-columns";
    return html`
      <div
        class=${classMap({
      "split-view": true,
      "split-view--animating": isNumber(this.animatingPanelIndex)
    })}
        ?dragging=${this.dragging}
        @flow-ui-drag=${this.handleDrag}
        @flow-ui-drag-start=${this.handleDragStart}
        @flow-ui-drag-end=${this.handleDragEnd}
        @pointerleave=${this.handlePointerLeave}
        @flow-ui-collapse=${this.handleCollapseExpand}
        @flow-ui-expand=${this.handleCollapseExpand}
        @transitionend=${this.handleTransitionEnd}
        style=${styleMap({
      [gridTemplateProperty]: this.positions.join(" ")
    })}
      >
        <slot class="split-view__panel-slot"></slot>
      </div>
    `;
  }
  actualPanelSize(panel) {
    const panelBoundingRect = panel.getBoundingClientRect();
    return this.vertical ? panelBoundingRect.height : panelBoundingRect.width;
  }
  /**
   * Grow panels starting from startIndex in specified direction by resizing panels
   * up to their maximum size and (optionally) opening panels.
   *
   * @param startIndex panel index to start growing from
   * @param direction 1 (right/down) or -1 (left/up)
   * @param caOpen whether panels can be opened to grow space
   * @param amount Number of pixels to grow by
   * @param dryRun if true, don't actually row panels, just calculate the amount that can be grown
   */
  growPanels(startIndex, direction, canOpen, amount, dryRun = false) {
    let amountGrown = 0;
    for (let i = startIndex; i >= 0 && i < this._panels.length; i += direction) {
      const panel = this._panels[i];
      if (panel.hidden || panel.size !== "auto") {
        continue;
      }
      const panelCurrentSize = this.actualPanelSize(panel);
      const panelMaxSize = this.sizeToPixels(panel.maxSize ?? "100%");
      if (panelCurrentSize >= panelMaxSize) {
        continue;
      }
      const panelGrowableSize = panelMaxSize - panelCurrentSize;
      if (panelGrowableSize > 0) {
        const growAmount = Math.min(amount, panelGrowableSize);
        amount -= growAmount;
        amountGrown += growAmount;
      }
      if (amount <= 0) {
        break;
      }
    }
    if (amount > 0) {
      for (let i = startIndex; i >= 0 && i < this._panels.length; i += direction) {
        const panel = this._panels[i];
        if (panel.hidden || panel.closed || panel.size === "auto") {
          continue;
        }
        const panelCurrentSize = this.actualPanelSize(panel);
        const panelMaxSize = this.sizeToPixels(panel.maxSize ?? "100%");
        if (panelCurrentSize >= panelMaxSize) {
          continue;
        }
        const panelGrowableSize = panelMaxSize - panelCurrentSize;
        if (panelGrowableSize > 0) {
          const growAmount = Math.min(amount, panelGrowableSize);
          if (!dryRun) {
            panel.size = panelCurrentSize + growAmount + "px";
          }
          amount -= growAmount;
          amountGrown += growAmount;
        }
        if (amount <= 0) {
          break;
        }
      }
    }
    if (amount > 0 && canOpen) {
      for (let i = startIndex; i >= 0 && i < this._panels.length; i += direction) {
        const panel = this._panels[i];
        if (panel.hidden || !panel.closed) {
          continue;
        }
        const panelClosedSize = this.sizeToPixels(panel.closedSize ?? "0px");
        const panelOpenSize = this.sizeToPixels(isNumber(panel.size) ? panel.size : panel.minSize ?? "0px");
        if (panelOpenSize === 0 || panelClosedSize === 0) {
          continue;
        }
        const panelGrowableSize = panelOpenSize - panelClosedSize;
        if (panelGrowableSize > 0 && panelGrowableSize <= amount) {
          if (!dryRun) {
            panel.closed = false;
          }
          amount -= panelGrowableSize;
          amountGrown += panelGrowableSize;
        }
        if (amount <= 0) {
          break;
        }
      }
    }
    return amountGrown;
  }
  handleCollapseExpand(event) {
    event.stopPropagation();
    if (!this.isPanel(event.target)) {
      return;
    }
    const panel = event.target;
    const panelIndex = this._panels.indexOf(panel);
    this.animatingPanelIndex = panelIndex;
    if (panel.closed) {
      if (panel.size === "auto") {
        panel.size = this.actualPanelSize(panel) + "px";
      }
      let sizeDelta = this.sizeToPixels(panel.size) - this.sizeToPixels(panel.closedSize);
      sizeDelta -= this.growPanels(panelIndex + 1, 1, false, sizeDelta);
      if (sizeDelta > 0) {
        this.growPanels(panelIndex, -1, false, sizeDelta);
      }
    } else {
      let sizeDelta = this.sizeToPixels(panel.size) - this.sizeToPixels(panel.closedSize);
      sizeDelta -= this.shrinkPanels(panelIndex + 1, 1, false, sizeDelta);
      if (sizeDelta > 0) {
        sizeDelta -= this.shrinkPanels(panelIndex, -1, false, sizeDelta);
      }
      if (sizeDelta > 0) {
        panel.size = "auto";
      }
    }
  }
  /**
   * This is called repeatedly as the user drags a panel divider.
   *
   * @param event
   *
   */
  handleDrag(event) {
    event.stopPropagation();
    if (!this.isPanel(event.target)) {
      return;
    }
    const targetPanel = this.dragTargetPanel;
    const pointerPosition = this.vertical ? event.detail.y : event.detail.x;
    const targetPanelSize = this.actualPanelSize(targetPanel);
    const targetPanelIndex = this._panels.indexOf(targetPanel);
    const dragDelta = pointerPosition - targetPanelSize;
    const dragDirection = dragDelta > 0 ? 1 : -1;
    let dragPanelIndex;
    if (dragDirection === 1) {
      dragPanelIndex = targetPanelIndex;
    } else {
      dragPanelIndex = this._panels.findIndex((panel, i) => !panel.hidden && i > targetPanelIndex);
    }
    const dragPanel = this._panels[dragPanelIndex];
    const dragPanelSize = this.actualPanelSize(dragPanel);
    const dragPanelMaxSize = this.sizeToPixels(dragPanel.maxSize ?? "100%");
    const dragPanelMaxExpansion = dragPanelMaxSize - dragPanelSize;
    let requiredSpace = Math.min(Math.abs(dragDelta), dragPanelMaxExpansion);
    const shrinkStartIndex = dragPanelIndex + dragDirection;
    const shrinkableSpace = this.shrinkPanels(shrinkStartIndex, dragDirection, !this.noDragClose, requiredSpace, true);
    if (dragPanel.closed) {
      if (this.noDragClose) {
        return;
      }
      const closedSize = this.sizeToPixels(dragPanel.closedSize ?? "0px");
      const minSize = this.sizeToPixels(dragPanel.minSize ?? "0px");
      const requiredOpeningSpace = minSize - closedSize;
      if (requiredOpeningSpace <= requiredSpace && requiredOpeningSpace <= shrinkableSpace) {
        const amountShrunk = this.shrinkPanels(
          shrinkStartIndex,
          dragDirection,
          !this.noDragClose,
          requiredOpeningSpace
        );
        if (amountShrunk >= requiredOpeningSpace) {
          dragPanel.closed = false;
          dragPanel.size = dragPanel.minSize;
          requiredSpace -= requiredOpeningSpace;
        }
      }
    } else {
      const amountShrunk = this.shrinkPanels(
        shrinkStartIndex,
        dragDirection,
        !this.noDragClose,
        Math.min(requiredSpace, shrinkableSpace)
      );
      if (amountShrunk > 0 && dragPanel.size !== "auto") {
        dragPanel.size = dragPanelSize + amountShrunk + "px";
      }
    }
  }
  handleDragEnd() {
    this.dragging = false;
  }
  handleDragStart(event) {
    event.stopPropagation();
    if (!this.isPanel(event.target)) {
      return;
    }
    this.dragTargetPanel = event.target;
    const autoPanels = this._panels.filter((panel) => panel.size === "auto");
    if (autoPanels.length > 1) {
      this._panels.filter((panel) => panel.size === "auto").forEach((panel) => {
        const panelBoundingRect = panel.getBoundingClientRect();
        const panelCurrentSize = this.vertical ? panelBoundingRect.height : panelBoundingRect.width;
        panel.size = panelCurrentSize + "px";
      });
    }
    this.dragging = true;
  }
  handlePointerLeave() {
    if (this.dragTargetPanel) {
      this.dragTargetPanel.cancelDrag();
      this.dragging = false;
    }
  }
  handleTransitionEnd(event) {
    if (event.propertyName !== "grid-template-rows" && event.propertyName !== "grid-template-columns" || isUndefined(this.animatingPanelIndex)) {
      return;
    }
    this.animatingPanelIndex = void 0;
  }
  isPanel(element) {
    return element instanceof FlowUiSplitViewPane && this._panels.includes(element);
  }
  layoutPanels() {
    const clientBoundingRect = this.splitView.getBoundingClientRect();
    const splitViewSize = this.vertical ? clientBoundingRect.height : clientBoundingRect.width;
    if (splitViewSize === 0) {
      return;
    }
    this.savePanelLayouts();
    const numOpenPanels = this._panels.filter((panel) => !panel.closed && !panel.hidden).length;
    const panelSizes = this._panels.map((panel) => {
      if (panel.hidden) {
        return 0;
      }
      if (panel.closed) {
        return this.sizeToPixels(panel.closedSize ?? "0px");
      }
      if (typeof panel.size === "undefined" || panel.size === "auto") {
        return "auto";
      }
      if (numOpenPanels === 1) {
        return "auto";
      }
      let panelSize = clamp(
        this.sizeToPixels(panel.size),
        this.sizeToPixels(panel.minSize ?? "0px"),
        this.sizeToPixels(panel.maxSize ?? "100%")
      );
      if (panel.minSize) {
        panelSize = Math.max(panelSize, this.sizeToPixels(panel.minSize));
      }
      if (panel.maxSize) {
        panelSize = Math.min(panelSize, this.sizeToPixels(panel.maxSize));
      }
      return panelSize;
    });
    const totalFixedSize = panelSizes.filter((size) => typeof size === "number").reduce((a, b) => a + b, 0);
    const autoPanels = panelSizes.reduce((accum, panelSize, index) => {
      if (panelSize === "auto") {
        accum.push({ panelIndex: index, size: 0 });
      }
      return accum;
    }, []);
    let availableFlexSize = splitViewSize - totalFixedSize;
    let layoutIteration = 0;
    while (availableFlexSize > 0 && layoutIteration < 5) {
      availableFlexSize = this.layoutAutoPanels(autoPanels, availableFlexSize, layoutIteration === 0);
      layoutIteration++;
    }
    autoPanels.forEach((panelInfo) => {
      panelSizes[panelInfo.panelIndex] = panelInfo.size;
    });
    const totalLayoutSize = panelSizes.reduce((a, b) => isNumber(a) && isNumber(b) ? a + b : a, 0);
    if (totalLayoutSize > splitViewSize) {
      console.warn(
        `Total panel size (${totalLayoutSize}px) exceeds split view size (${splitViewSize}px). This may lead to unexpected layout issues.`
      );
    }
    this.positions = panelSizes.map((size) => size + "px");
    for (const panel of this._panels) {
      panel.divider = !panel.hidden;
    }
    const lastVisiblePanel = this._panels.slice().reverse().find((panel) => !panel.hidden);
    if (lastVisiblePanel) {
      lastVisiblePanel.divider = false;
    }
  }
  layoutAutoPanels(autoPanels, totalAutoSize, initialize = false) {
    if (autoPanels?.length === 0) {
      return totalAutoSize;
    }
    if (initialize) {
      autoPanels.forEach((panelInfo) => {
        const panel = this._panels[panelInfo.panelIndex];
        const minSize = this.sizeToPixels(panel.minSize ?? "0px");
        panelInfo.size = minSize;
        totalAutoSize -= minSize;
      });
    }
    const expandableAutoPanels = autoPanels.filter((panelInfo) => {
      const panel = this._panels[panelInfo.panelIndex];
      const panelMaxSize = this.sizeToPixels(panel.maxSize ?? "100%");
      return panelInfo.size < panelMaxSize;
    });
    if (expandableAutoPanels.length > 0) {
      const autoSizePerPanel = totalAutoSize / expandableAutoPanels.length;
      let allocatedSize = 0;
      expandableAutoPanels.forEach((panelInfo) => {
        const panel = this._panels[panelInfo.panelIndex];
        const panelMaxSize = this.sizeToPixels(panel.maxSize ?? "100%");
        const newSize = Math.min(panelInfo.size + autoSizePerPanel, panelMaxSize);
        allocatedSize += newSize - panelInfo.size;
        panelInfo.size = newSize;
      });
      totalAutoSize -= allocatedSize;
    }
    return totalAutoSize;
  }
  resizePanels() {
    const boundingRect = this.splitView.getBoundingClientRect();
    if (boundingRect.width === 0 || boundingRect.height === 0) {
      return;
    }
    const clientSize = this.vertical ? boundingRect.height : boundingRect.width;
    const totalPanelSize = this._panels.map((panel) => this.actualPanelSize(panel)).reduce((a, b) => a + b, 0);
    const sizeDelta = clientSize - totalPanelSize;
    if (sizeDelta < 0) {
      const sizeToShrink = totalPanelSize - clientSize;
      this.shrinkPanels(this._panels.length - 1, -1, true, Math.abs(sizeToShrink));
    } else if (sizeDelta > 0) {
      const sizeToGrow = clientSize - totalPanelSize;
      this.growPanels(0, 1, true, sizeToGrow);
    }
    this.layoutPanels();
  }
  /**
   * Shrink panels starting from startIndex in specified direction by resizing panels
   * down to their minimum size and (optionally) closing panels.
   *
   * @param startIndex panel index to start shrinking from
   * @param direction 1 (right/down) or -1 (left/up)
   * @param canClose whether panels can be closed to make space
   * @param amount Number of pixels to shrink by
   * @param dryRun if true, don't actually shrink panels, just calculate the amount that can be shrunk
   */
  shrinkPanels(startIndex, direction, canClose, amount, dryRun = false) {
    let amountShrunk = 0;
    for (let i = startIndex; i >= 0 && i < this._panels.length; i += direction) {
      const panel = this._panels[i];
      if (panel.hidden) {
        continue;
      }
      if (panel.size !== "auto") {
        continue;
      }
      const panelCurrentSize = this.actualPanelSize(panel);
      const panelMinSize = this.sizeToPixels(panel.minSize ?? "0px");
      const panelClosedSize = this.sizeToPixels(panel.closedSize ?? "0px");
      const panelShrinkableSize = panelCurrentSize - Math.max(panelMinSize, panelClosedSize);
      if (panelShrinkableSize > 0) {
        const shrinkAmount = Math.min(amount, panelShrinkableSize);
        amount -= shrinkAmount;
        amountShrunk += shrinkAmount;
      }
      if (amount <= 0) {
        break;
      }
    }
    if (amount > 0) {
      for (let i = startIndex; i >= 0 && i < this._panels.length; i += direction) {
        const panel = this._panels[i];
        if (panel.hidden || panel.closed || panel.size === "auto") {
          continue;
        }
        const panelCurrentSize = this.actualPanelSize(panel);
        const panelMinSize = this.sizeToPixels(panel.minSize ?? "0px");
        const panelClosedSize = this.sizeToPixels(panel.closedSize ?? "0px");
        const panelShrinkableSize = panelCurrentSize - Math.max(panelMinSize, panelClosedSize);
        if (panelShrinkableSize > 0) {
          const shrinkAmount = Math.min(amount, panelShrinkableSize);
          if (!dryRun) {
            panel.size = panelCurrentSize - shrinkAmount + "px";
          }
          amount -= shrinkAmount;
          amountShrunk += shrinkAmount;
        }
        if (amount <= 0) {
          break;
        }
      }
    }
    if (amount > 0 && canClose) {
      for (let i = startIndex; i >= 0 && i < this._panels.length; i += direction) {
        const panel = this._panels[i];
        const panelMinSize = this.sizeToPixels(panel.minSize ?? "0px");
        const panelClosedSize = this.sizeToPixels(panel.closedSize ?? "0px");
        if (panel.hidden || panel.closed || panelClosedSize === 0 || panelMinSize === 0) {
          continue;
        }
        const panelShrinkableSize = panelMinSize - panelClosedSize;
        if (panelShrinkableSize > 0 && amount >= panelShrinkableSize) {
          const shrinkAmount = Math.min(amount, panelShrinkableSize);
          if (!dryRun) {
            panel.closed = true;
          }
          amount -= shrinkAmount;
          amountShrunk += shrinkAmount;
        }
        if (amount <= 0) {
          break;
        }
      }
    }
    return amountShrunk;
  }
  sizeToPixels(size) {
    if (size.endsWith("px")) {
      return parseFloat(size);
    }
    if (size.endsWith("%")) {
      return parseFloat(size) / 100 * (this.vertical ? this.splitView.clientHeight : this.splitView.clientWidth);
    }
    return 0;
  }
  handlePanelAdded(panel) {
    this.updatePanelInfo();
    if (!this.restorePanelLayouts()) {
      if (isUndefined(panel.size) || panel.size === "auto") {
        panel.size = panel.minSize ?? "50px";
      }
      this.shrinkPanels(this._panels.length - 1, -1, true, this.sizeToPixels(panel.size));
    }
  }
  handlePanelRemoved(panel) {
    this.updatePanelInfo();
    if (!this.restorePanelLayouts()) {
      this.growPanels(this._panels.length - 1, -1, true, this.sizeToPixels(panel.size));
    }
  }
  updatePanelInfo() {
    this._panels = [...this.panelSlot.assignedElements()].filter(
      (el) => el.tagName.toLowerCase() === "flow-ui-split-view-pane"
    );
  }
  // Key used to stor panel layouts for a given set of panels
  // This is a comma-separated concatenation of panel ids e.g. 'pane-1,pane-2,pane-3'
  panelLayoutsKey() {
    return this._panels.map((panel) => panel.id).join(",");
  }
  savePanelLayouts() {
    const panelLayoutsKey = this.panelLayoutsKey();
    const panelLayouts = this._panels.map(
      (panel) => ({ id: panel.id, size: panel.size, closed: panel.closed })
    );
    this._panelLayouts[panelLayoutsKey] = panelLayouts;
  }
  restorePanelLayouts() {
    const panelLayoutsKey = this.panelLayoutsKey();
    if (this._panelLayouts[panelLayoutsKey]) {
      this._panelLayouts[panelLayoutsKey].forEach((panelLayout) => {
        const panel = this._panels.find((p) => p.id === panelLayout.id);
        if (panel) {
          panel.size = panelLayout.size;
          panel.closed = panelLayout.closed;
        }
      });
      return true;
    } else {
      return false;
    }
  }
}
FlowUiSplitView.styles = styles;
__decorateClass([
  state()
], FlowUiSplitView.prototype, "animatingPanelIndex", 2);
__decorateClass([
  query(".split-view__panel-slot")
], FlowUiSplitView.prototype, "panelSlot", 2);
__decorateClass([
  query(".split-view")
], FlowUiSplitView.prototype, "splitView", 2);
__decorateClass([
  property({ attribute: "no-drag-close", type: Boolean })
], FlowUiSplitView.prototype, "noDragClose", 2);
__decorateClass([
  property({ type: Array })
], FlowUiSplitView.prototype, "positions", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], FlowUiSplitView.prototype, "vertical", 2);
__decorateClass([
  property({ reflect: true, type: Boolean })
], FlowUiSplitView.prototype, "dragging", 2);

export { FlowUiSplitView as default };
