text: /**
   * Copyright 2019 Adobe. All rights reserved.
   * This file is licensed to you under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License. You may obtain a copy
   * of the License at http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software distributed under
   * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
   * OF ANY KIND, either express or implied. See the License for the specific language
   * governing permissions and limitations under the License.
   */
  
  import {BaseComponent} from '../../../coral-base-component';
  import {Collection} from '../../../coral-collection';
  import {Icon} from '../../../coral-component-icon';
  import treeItem from '../templates/treeItem';
  import {transform, commons, i18n, validate} from '../../../coral-utils';
  
  const CLASSNAME = '_coral-TreeView-item';
  
  /**
   Enumeration for {@link TreeItem} variants.
   
   @typedef {Object} TreeItemVariantEnum
   
   @property {String} DRILLDOWN
   Default variant with icon to expand/collapse subtree.
   @property {String} LEAF
   Variant for leaf items. Icon to expand/collapse subtree is hidden.
   */
  const variant = {
    /* Default variant with icon to expand/collapse subtree. */
    DRILLDOWN: 'drilldown',
    /* Variant for leaf items. Icon to expand/collapse subtree is hidden. */
    LEAF: 'leaf'
  };
  
  const ALL_VARIANT_CLASSES = [];
  
  for (const variantValue in variant) {
    ALL_VARIANT_CLASSES.push(`${CLASSNAME}--${variant[variantValue]}`);
  }
  
  const IS_TOUCH_DEVICE = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
  
  /**
   @class Coral.Tree.Item
   @classdesc A Tree item component
   @htmltag coral-tree-item
   @extends {HTMLElement}
   @extends {BaseComponent}
   */
  class TreeItem extends BaseComponent(HTMLElement) {
    /** @ignore */
    constructor() {
      super();
      
      // Prepare templates
      this._elements = {
        // Create or fetch the content zones
        content: this.querySelector('coral-tree-item-content') || document.createElement('coral-tree-item-content')
      };
      treeItem.call(this._elements, {Icon, commons});
  
      if (!this._elements.icon) {
        this._elements.icon = this._elements.header.querySelector('._coral-TreeView-indicator');
      }
  
      // Tells the collection to automatically detect the items and handle the events
      this.items._startHandlingItems();
    }
    
    /**
     The parent tree. Returns <code>null</code> if item is the root.
     
     @type {HTMLElement}
     @readonly
     */
    get parent() {
      return this._parent || null;
    }
    
    /**
     The content of this tree item.
     
     @type {TreeItemContent}
     @contentzone
     */
    get content() {
      return this._getContentZone(this._elements.content);
    }
    set content(value) {
      this._setContentZone('content', value, {
        handle: 'content',
        tagName: 'coral-tree-item-content',
        insert: function(content) {
          this._elements.header.appendChild(content);
        }
      });
    }
    
    /**
     The Collection Interface that allows interacting with the items that the component contains.
     
     @type {Collection}
     @readonly
     */
    get items() {
      // Construct the collection on first request
      if (!this._items) {
        this._items = new Collection({
          host: this,
          itemTagName: 'coral-tree-item',
          itemSelector: ':scope > coral-tree-item',
          container: this._elements.subTreeContainer,
          filter: this._filterItem.bind(this),
          onItemAdded: this._onItemAdded,
          onItemRemoved: this._onItemRemoved
        });
      }
    
      return this._items;
    }
    
    /**
     Whether the item is expanded. Expanded cannot be set to <code>true</code> if the item is disabled.
     
     @type {Boolean}
     @default false
     @htmlattribute expanded
     @htmlattributereflected
     */
    get expanded() {
      return this._expanded || false;
    }
    set expanded(value) {
      value = transform.booleanAttr(value);
      const triggerEvent = this.expanded !== value;
      
      this._expanded = value;
      this._reflectAttribute('expanded', this._expanded);
    
      const header = this._elements.header;
      const subTreeContainer = this._elements.subTreeContainer;
    
      this.classList.toggle('is-open', this._expanded);
      this.classList.toggle('is-collapsed', !this._expanded);
  
      if (this.variant !== variant.DRILLDOWN) {
        header.removeAttribute('aria-expanded');
        header.removeAttribute('aria-owns');
      }
      else if (this.items.length > 0) {
        header.setAttribute('aria-expanded', this._expanded);
        header.setAttribute('aria-owns', subTreeContainer.id);
      }
      
      if (this._expanded) {
        subTreeContainer.removeAttribute('aria-hidden');
      }
      else {
        subTreeContainer.setAttribute('aria-hidden', !this._expanded);
      }
  
      if (IS_TOUCH_DEVICE) {
        const icon = header.querySelector('._coral-TreeView-indicator');
        icon.setAttribute('aria-label', i18n.get(this._expanded ? 'Collapse' : 'Expand'));
      }
      
      this.trigger('coral-tree-item:_expandedchanged');
      
      // Do animation in next frame to avoid a forced reflow
      window.requestAnimationFrame(() => {
        // Don't animate on initialization
        if (this._animate) {
          // Remove height as we want the drawer to naturally grow if content is added later
          commons.transitionEnd(subTreeContainer, () => {
            if (this.expanded) {
              subTreeContainer.style.height = '';
            }
            else {
              subTreeContainer.hidden = true;
            }
    
            // Trigger once the animation is over to inform coral-tree
            if (triggerEvent) {
              this.trigger('coral-tree-item:_afterexpandedchanged');
            }
          });
      
          // Force height to enable transition
          if (!this.expanded) {
            subTreeContainer.style.height = `${subTreeContainer.scrollHeight}px`;
          }
          else {
            subTreeContainer.hidden = false;
          }
    
          // We read the offset height to force a reflow, this is needed to start the transition between absolute values
          // https://blog.alexmaccaw.com/css-transitions under Redrawing
          // eslint-disable-next-line no-unused-vars
          const offsetHeight = subTreeContainer.offsetHeight;
          
          subTreeContainer.style.height = this.expanded ? `${subTreeContainer.scrollHeight}px` : 0;
        }
        else {
          // Make sure it's animated next time
          this._animate = true;
      
          // Hide it on initialization if closed
          if (!this.expanded) {
            subTreeContainer.style.height = 0;
            subTreeContainer.hidden = true;
          }
        }
      });
    }
    
    /**
     The item's variant. See {@link TreeItemVariantEnum}.
     
     @type {String}
     @default TreeItemVariant.DRILLDOWN
     @htmlattribute variant
     @htmlattributereflected
     */
    get variant() {
      return this._variant || variant.DRILLDOWN;
    }
    set variant(value) {
      value = transform.string(value).toLowerCase();
      this._variant = validate.enumeration(variant, value) && value || variant.DRILLDOWN;
    
      // removes every existing variant
      this.classList.remove(...ALL_VARIANT_CLASSES);
      this.classList.add(`${CLASSNAME}--${this._variant}`);
    }
    
    /**
     Whether the item is selected.
     
     @type {Boolean}
     @default false
     @htmlattribute selected
     @htmlattributereflected
     */
    get selected() {
      return this._selected || false;
    }
    set selected(value) {
      this._selected = transform.booleanAttr(value);
      this._reflectAttribute('selected', this._selected);
    
      this._elements.header.classList.toggle('is-selected', this._selected);
      this._elements.header.setAttribute('aria-selected', this._selected);
  
      const selectedState =  this._elements.selectedState;
      selectedState.textContent = i18n.get(this._selected ? 'selected' : 'not selected');
  
      if (IS_TOUCH_DEVICE) {
        selectedState.setAttribute('aria-pressed', this._selected);
      }
      
      this.trigger('coral-tree-item:_selectedchanged');
    }
    
    /**
     Whether this item is disabled.
     
     @type {Boolean}
     @default false
     @htmlattribute disabled
     @htmlattributereflected
     */
    get disabled() {
      return this._disabled || false;
    }
    set disabled(value) {
      this._disabled = transform.booleanAttr(value);
      this._reflectAttribute('disabled', this._disabled);
    
      this._elements.header.classList.toggle('is-disabled', this._disabled);
      this._elements.header.setAttribute('aria-disabled', this._disabled);
      
      this.trigger('coral-tree-item:_disabledchanged');
    }
    
    /**
     @ignore
     */
    get hidden() {
      return this.hasAttribute('hidden');
    }
    set hidden(value) {
      this._reflectAttribute('hidden', transform.booleanAttr(value));
    
      // We redefine hidden to trigger an event
      this.trigger('coral-tree-item:_hiddenchanged');
    }
    
    /** @private */
    _filterItem(item) {
      // Handle nesting check for parent tree item
      // Use parentNode for added items
      // Use _parent for removed items
      return item.parentNode && item.parentNode.parentNode === this || item._parent === this;
    }
    
    /** @private */
    _onItemAdded(item) {
      item._parent = this;
  
      const header = this._elements.header;
      const subTreeContainer = this._elements.subTreeContainer;
      if (!header.hasAttribute('aria-owns')) {
        header.setAttribute('aria-owns', subTreeContainer.id);
      }
    }
    
    /** @private */
    _onItemRemoved(item) {
      item._parent = undefined;
  
      // If there are no items the subTreeContainer
      if (!this.items.length) {
        this._elements.header.removeAttribute('aria-owns');
      }
    }
    
    /**
     Handles the focus of the item.
   
     @ignore
     */
    focus() {
      this._elements.header.setAttribute('tabindex', '0');
      this._elements.header.focus();
    }
    
    /**
     Returns {@link TreeItem} variants.
     
     @return {TreeItemVariantEnum}
     */
    static get variant() { return variant; }
    
    get _contentZones() { return {'coral-tree-item-content': 'content'}; }
    
    /** @ignore */
    static get observedAttributes() {
      return super.observedAttributes.concat(['selected', 'disabled', 'variant', 'expanded', 'hidden']);
    }
    
    /** @ignore */
    render() {
      super.render();
      
      this.classList.add(CLASSNAME);
  
      const header = this._elements.header;
      const subTreeContainer = this._elements.subTreeContainer;
      const content = this._elements.content;
      const selectedState =  this._elements.selectedState;
    
      // a11ys
      content.id = content.id || commons.getUID();
      this.setAttribute('role', 'presentation');
      header.setAttribute('aria-labelledby', `${content.id} ${selectedState.id}`);
      header.setAttribute('aria-selected', this.selected);
      subTreeContainer.setAttribute('aria-labelledby', content.id);
      selectedState.textContent = i18n.get(this.selected ? 'selected' : 'not selected');
  
      if (IS_TOUCH_DEVICE) {
        const icon = this._elements.icon || header.querySelector('._coral-TreeView-indicator');
        if (icon && !icon.id) {
          icon.id = commons.getUID();
        }
        icon.setAttribute('role', 'button');
        icon.setAttribute('tabindex', '-1');
        icon.setAttribute('aria-labelledby', icon.id + ' ' + content.id);
        icon.setAttribute('aria-label', i18n.get(this.expanded ? 'Collapse' : 'Expand'));
        icon.setAttribute('style', 'outline: none !important');
        icon.removeAttribute('aria-hidden');
  
        selectedState.setAttribute('role', 'button');
        selectedState.setAttribute('tabindex', '-1');
        selectedState.setAttribute('aria-labelledby', content.id + ' ' + selectedState.id);
        selectedState.setAttribute('aria-pressed', this.selected);
        selectedState.setAttribute('style', 'outline: none !important');
      }
    
      // Default reflected attributes
      if (!this._variant) { this.variant = variant.DRILLDOWN; }
      this.expanded = this.expanded;
      
      // Render the template and set element references
      const frag = document.createDocumentFragment();
    
      const templateHandleNames = ['header', 'icon', 'subTreeContainer'];
      
      const subTree = this.querySelector('._coral-TreeView');
      if (subTree) {
        const items = subTree.querySelectorAll('coral-tree-item');
        for (let i = 0; i < items.length; i++) {
          subTreeContainer.appendChild(items[i]);
        }
      }
      
      // Add templates into the frag
      frag.appendChild(header);
      frag.appendChild(subTreeContainer);
    
      // Assign the content zones, moving them into place in the process
      this.content = content;
    
      // Move any remaining elements into the content sub-component
      while (this.firstChild) {
        const child = this.firstChild;
        if (child.nodeName === 'CORAL-TREE-ITEM') {
          // Adding parent attribute to access the parent directly
          child._parent = this;
          // Add tree items to the sub tree container
          subTreeContainer.appendChild(child);
        }
        else if (child.nodeType === Node.TEXT_NODE ||
          child.nodeType === Node.ELEMENT_NODE && templateHandleNames.indexOf(child.getAttribute('handle')) === -1) {
          // Add non-template elements to the content
          content.appendChild(child);
        }
        else {
          // Remove anything else element
          this.removeChild(child);
        }
      }
  
      if (this.variant === variant.DRILLDOWN && this.items.length && !header.hasAttribute('aria-owns')) {
        header.setAttribute('aria-owns', subTreeContainer.id);
      }
    
      // Lastly, add the fragment into the container
      this.appendChild(frag);
    }
    
    /**
     Triggered when {@link TreeItem#selected} changed.
   
     @typedef {CustomEvent} coral-tree-item:_selectedchanged
     
     @private
     */
    
    /**
     Triggered when {@link TreeItem#expanded} changed.
   
     @typedef {CustomEvent} coral-tree-item:_expandedchanged
     
     @private
     */
    
    /**
     Triggered when {@link TreeItem#hidden} changed.
   
     @typedef {CustomEvent} coral-tree-item:_hiddenchanged
     
     @private
     */
    
    /**
     Triggered when {@link TreeItem#disabled} changed.
   
     @typedef {CustomEvent} coral-tree-item:_disabledchanged
     
     @private
     */
  }
  
  export default TreeItem;
  