import { html } from 'lit';
import { query, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ensureArray } from '../../internal/ensure-array.mjs';
import FlowRdfElement, { FlowRdfFormControl } from '../../internal/flow-ui-rdf-element.mjs';
import { clamp } from '../../internal/math.mjs';
import { isStringFull } from '../../internal/type-checks.mjs';
import { watch } from '../../internal/watch.mjs';
import { LocalizeController } from '../../utilities/localize.mjs';
import FlowUiTreeItem from '../tree-item/tree-item.component.mjs';
import styles from './tree.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;
};
function syncCheckboxes(changedTreeItem, initialSync = false) {
  function syncParentItem(treeItem) {
    const children = treeItem.getChildrenItems({ includeDisabled: false });
    if (children.length) {
      const allChecked = children.every((item) => item.selected);
      const allUnchecked = children.every((item) => !item.selected && !item.indeterminate);
      treeItem.selected = allChecked;
      treeItem.indeterminate = !allChecked && !allUnchecked;
    }
  }
  function syncAncestors(treeItem) {
    const parentItem = treeItem.parentElement;
    if (FlowUiTreeItem.isTreeItem(parentItem)) {
      syncParentItem(parentItem);
      syncAncestors(parentItem);
    }
  }
  function syncDescendants(treeItem) {
    for (const childItem of treeItem.getChildrenItems()) {
      childItem.selected = initialSync ? treeItem.selected || childItem.selected : !childItem.disabled && treeItem.selected;
      syncDescendants(childItem);
    }
    if (initialSync) {
      syncParentItem(treeItem);
    }
  }
  syncDescendants(changedTreeItem);
  syncAncestors(changedTreeItem);
}
class FlowUiTree extends FlowRdfElement {
  constructor() {
    super(...arguments);
    this.value = [];
    this.selectionMode = "multiple";
    this.size = "medium";
    this.localize = new LocalizeController(this);
    this.clickTarget = null;
    // Initializes new items by setting the `selectable` property and the expanded/collapsed icons if any
    this.initTreeItem = (item) => {
      item.selectable = this.selectionMode === "multiple";
      item.selected = !!item.value && this.value.includes(item.value);
      ["expand", "collapse"].filter((status) => !!this.querySelector(`[slot="${status}-icon"]`)).forEach((status) => {
        const existingIcon = item.querySelector(`[slot="${status}-icon"]`);
        if (existingIcon === null) {
          item.append(this.getExpandButtonIcon(status));
        } else if (existingIcon.hasAttribute("data-default")) {
          existingIcon.replaceWith(this.getExpandButtonIcon(status));
        } else ;
      });
      syncCheckboxes(item);
    };
  }
  async connectedCallback() {
    super.connectedCallback();
    this.classList.add(FlowRdfFormControl.FLOW_UI_FORM_CONTROL_CLASS);
    this.handleTreeChanged = this.handleTreeChanged.bind(this);
    this.handleFocusIn = this.handleFocusIn.bind(this);
    this.handleFocusOut = this.handleFocusOut.bind(this);
    this.setAttribute("role", "tree");
    this.setAttribute("tabindex", "0");
    this.addEventListener("focusin", this.handleFocusIn);
    this.addEventListener("focusout", this.handleFocusOut);
    this.addEventListener("flow-ui-lazy-change", this.handleSlotChange);
    this.addEventListener("flow-ui-expand", this.handleHostExpandCollapse);
    this.addEventListener("flow-ui-collapse", this.handleHostExpandCollapse);
    this.addEventListener("flow-ui-after-expand", this.handleHostExpandCollapse);
    this.addEventListener("flow-ui-after-collapse", this.handleHostExpandCollapse);
    await this.updateComplete;
    this.mutationObserver = new MutationObserver(this.handleTreeChanged);
    this.mutationObserver.observe(this, { childList: true, subtree: true });
  }
  disconnectedCallback() {
    super.disconnectedCallback();
    this.mutationObserver.disconnect();
    this.removeEventListener("focusin", this.handleFocusIn);
    this.removeEventListener("focusout", this.handleFocusOut);
    this.removeEventListener("flow-ui-lazy-change", this.handleSlotChange);
  }
  handleClassChange() {
    this.classList.add(FlowRdfFormControl.FLOW_UI_FORM_CONTROL_CLASS);
  }
  // Generates a clone of the expand icon element to use for each tree item
  getExpandButtonIcon(status) {
    const slot = status === "expand" ? this.expandedIconSlot : this.collapsedIconSlot;
    const icon = slot.assignedElements({ flatten: true })[0];
    if (icon) {
      const clone = icon.cloneNode(true);
      [clone, ...clone.querySelectorAll("[id]")].forEach((el) => el.removeAttribute("id"));
      clone.setAttribute("data-default", "");
      clone.slot = `${status}-icon`;
      return clone;
    }
    return null;
  }
  handleTreeChanged(mutations) {
    for (const mutation of mutations) {
      const addedNodes = [...mutation.addedNodes].filter(FlowUiTreeItem.isTreeItem);
      const removedNodes = [...mutation.removedNodes].filter(FlowUiTreeItem.isTreeItem);
      addedNodes.forEach(this.initTreeItem);
      if (removedNodes.includes(this.lastFocusedItem)) {
        this.focusItem(this.getFocusableItems()[0]);
      }
    }
  }
  handleSizeChange() {
    this.syncTreeItems();
  }
  syncTreeItems(selectedItem) {
    const items = this.getAllTreeItems();
    items.forEach((items2) => {
      items2.size = this.size;
    });
    if (selectedItem) {
      if (this.selectionMode === "multiple") {
        syncCheckboxes(selectedItem);
      } else {
        for (const item of items) {
          if (item !== selectedItem) {
            item.selected = false;
          }
        }
      }
    }
  }
  selectItem(selectedItem) {
    const previousSelection = [...this.selectedItems];
    if (this.selectionMode === "multiple") {
      selectedItem.selected = !selectedItem.selected;
      if (selectedItem.lazy) {
        selectedItem.expanded = true;
      }
      this.syncTreeItems(selectedItem);
    } else if (this.selectionMode === "single" || selectedItem.isLeaf) {
      selectedItem.expanded = !selectedItem.expanded;
      selectedItem.selected = true;
      this.syncTreeItems(selectedItem);
    } else if (this.selectionMode === "leaf") {
      selectedItem.expanded = !selectedItem.expanded;
    }
    const nextSelection = this.selectedItems;
    if (previousSelection.length !== nextSelection.length || nextSelection.some((item) => !previousSelection.includes(item))) {
      Promise.all(nextSelection.map((el) => el.updateComplete)).then(() => {
        this.value = nextSelection.map((item) => item.value).filter((value) => isStringFull(value));
        this.emit("flow-ui-input");
        this.emit("flow-ui-change");
      });
    }
  }
  getAllTreeItems() {
    return [...this.querySelectorAll("flow-ui-tree-item")];
  }
  focusItem(item) {
    item?.focus();
  }
  handleKeyDown(event) {
    if (!["ArrowDown", "ArrowUp", "ArrowRight", "ArrowLeft", "Home", "End", "Enter", " "].includes(event.key)) {
      return;
    }
    const items = this.getFocusableItems();
    const isLtr = this.localize.dir() === "ltr";
    const isRtl = this.localize.dir() === "rtl";
    if (items.length > 0) {
      event.preventDefault();
      const activeItemIndex = items.findIndex((item) => item.matches(":focus"));
      const activeItem = items[activeItemIndex];
      const focusItemAt = (index) => {
        const item = items[clamp(index, 0, items.length - 1)];
        this.focusItem(item);
      };
      const toggleExpand = (expanded) => {
        activeItem.expanded = expanded;
      };
      if (event.key === "ArrowDown") {
        focusItemAt(activeItemIndex + 1);
      } else if (event.key === "ArrowUp") {
        focusItemAt(activeItemIndex - 1);
      } else if (isLtr && event.key === "ArrowRight" || isRtl && event.key === "ArrowLeft") {
        if (!activeItem || activeItem.disabled || activeItem.expanded || activeItem.isLeaf && !activeItem.lazy) {
          focusItemAt(activeItemIndex + 1);
        } else {
          toggleExpand(true);
        }
      } else if (isLtr && event.key === "ArrowLeft" || isRtl && event.key === "ArrowRight") {
        if (!activeItem || activeItem.disabled || activeItem.isLeaf || !activeItem.expanded) {
          focusItemAt(activeItemIndex - 1);
        } else {
          toggleExpand(false);
        }
      } else if (event.key === "Home") {
        focusItemAt(0);
      } else if (event.key === "End") {
        focusItemAt(items.length - 1);
      } else if (event.key === "Enter" || event.key === " ") {
        if (!activeItem.disabled) {
          this.selectItem(activeItem);
        }
      }
    }
  }
  handleClick(event) {
    const target = event.target;
    const treeItem = target.closest("flow-ui-tree-item");
    const isExpandButton = event.composedPath().some((el) => el?.classList?.contains("tree-item__expand-button"));
    if (!treeItem || treeItem.disabled || target !== this.clickTarget) {
      return;
    }
    if (this.selectionMode === "multiple" && isExpandButton) {
      treeItem.expanded = !treeItem.expanded;
    } else {
      this.selectItem(treeItem);
    }
  }
  handleMouseDown(event) {
    this.clickTarget = event.target;
  }
  handleFocusOut(event) {
    const relatedTarget = event.relatedTarget;
    if (!relatedTarget || !this.contains(relatedTarget)) {
      this.tabIndex = 0;
    }
  }
  handleFocusIn(event) {
    const target = event.target;
    if (event.target === this) {
      this.focusItem(this.lastFocusedItem || this.getAllTreeItems()[0]);
    }
    if (FlowUiTreeItem.isTreeItem(target) && !target.disabled) {
      if (this.lastFocusedItem) {
        this.lastFocusedItem.tabIndex = -1;
      }
      this.lastFocusedItem = target;
      this.tabIndex = -1;
      target.tabIndex = 0;
    }
  }
  handleHostExpandCollapse(event) {
    event.stopPropagation();
  }
  handleSlotChange() {
    const items = this.getAllTreeItems();
    items.forEach(this.initTreeItem);
  }
  async handleSelectionChange() {
    const isSelectionMultiple = this.selectionMode === "multiple";
    const items = this.getAllTreeItems();
    this.setAttribute("aria-multiselectable", isSelectionMultiple ? "true" : "false");
    for (const item of items) {
      item.selectable = isSelectionMultiple;
    }
    if (isSelectionMultiple) {
      await this.updateComplete;
      [...this.querySelectorAll(":scope > flow-ui-tree-item")].forEach(
        (treeItem) => syncCheckboxes(treeItem, true)
      );
    }
  }
  handleValueChange(previousValue) {
    this.value = ensureArray(this.value);
    if (previousValue.length !== this.value.length || this.value.some((val) => !previousValue.includes(val)) || previousValue.some((val) => !this.value.includes(val))) {
      const selected = this.value.filter((val) => !previousValue.includes(val));
      const deselected = previousValue.filter((val) => !this.value.includes(val));
      const allItems = this.getAllTreeItems();
      const selectedItems = allItems.filter((item) => selected.includes(item.value));
      const deselectedItems = allItems.filter((item) => deselected.includes(item.value));
      selectedItems.forEach((item) => {
        item.selected = true;
        syncCheckboxes(item);
      });
      deselectedItems.forEach((item) => {
        item.selected = false;
        syncCheckboxes(item);
      });
    }
  }
  /** @internal Returns the list of tree items that are selected in the tree. */
  get selectedItems() {
    const items = this.getAllTreeItems();
    const isSelected = (item) => item.selected;
    return items.filter(isSelected);
  }
  /** @internal Gets focusable tree items in the tree. */
  getFocusableItems() {
    const items = this.getAllTreeItems();
    const collapsedItems = /* @__PURE__ */ new Set();
    return items.filter((item) => {
      if (item.disabled)
        return false;
      const parent = item.parentElement?.closest("[role=treeitem]");
      if (parent && (!parent.expanded || parent.loading || collapsedItems.has(parent))) {
        collapsedItems.add(item);
      }
      return !collapsedItems.has(item);
    });
  }
  render() {
    return html`
      <div
        part="base"
        class=${classMap({
      tree: true,
      "tree--multiple": this.selectionMode === "multiple",
      "tree--small": this.size === "small",
      "tree--medium": this.size === "medium",
      "tree--large": this.size === "large"
    })}
        @click=${this.handleClick}
        @keydown=${this.handleKeyDown}
        @mousedown=${this.handleMouseDown}
      >
        <slot @slotchange=${this.handleSlotChange}></slot>
        <slot name="expand-icon" hidden aria-hidden="true"></slot>
        <slot name="collapse-icon" hidden aria-hidden="true"></slot>
      </div>
    `;
  }
}
FlowUiTree.styles = styles;
__decorateClass([
  query("slot[name=expand-icon]")
], FlowUiTree.prototype, "expandedIconSlot", 2);
__decorateClass([
  query("slot[name=collapse-icon]")
], FlowUiTree.prototype, "collapsedIconSlot", 2);
__decorateClass([
  property({ reflect: true })
], FlowUiTree.prototype, "class", 2);
__decorateClass([
  property({
    converter: {
      fromAttribute: (value) => value.split(" "),
      toAttribute: (value) => value.join(" ")
    }
  })
], FlowUiTree.prototype, "value", 2);
__decorateClass([
  property({ attribute: "selection-mode" })
], FlowUiTree.prototype, "selectionMode", 2);
__decorateClass([
  property({ reflect: true })
], FlowUiTree.prototype, "size", 2);
__decorateClass([
  watch("class")
], FlowUiTree.prototype, "handleClassChange", 1);
__decorateClass([
  watch("size", { waitUntilFirstUpdate: true })
], FlowUiTree.prototype, "handleSizeChange", 1);
__decorateClass([
  watch("selectionMode")
], FlowUiTree.prototype, "handleSelectionChange", 1);
__decorateClass([
  watch("value", { waitUntilFirstUpdate: true })
], FlowUiTree.prototype, "handleValueChange", 1);

export { FlowUiTree as default };
