/*!
 * (C) Gasco https://gasco-web-component.herokuapp.com/ - MIT License
 */
import { Component, Element, Host, Listen, Prop, State, Watch, forceUpdate, h } from '@stencil/core';
import { chevronForward } from 'ionicons/icons';
import { raf } from '../../utils/helpers';
import { printGascoError } from '../../utils/logging';
import { createColorClasses, hostContext } from '../../utils/theme';
/**
 * @slot - Content is placed between the named slots if provided without a slot.
 * @slot start - Content is placed to the left of the item text in LTR, and to the right in RTL.
 * @slot end - Content is placed to the right of the item text in LTR, and to the left in RTL.
 * @slot helper - Content is placed under the item and displayed when no error is detected.
 * @slot error - Content is placed under the item and displayed when an error is detected.
 *
 * @part native - The native HTML button, anchor or div element that wraps all child elements.
 * @part detail-icon - The chevron icon for the item. Only applies when `detail="true"`.
 */
export class GascoItem {
  constructor() {
    this.labelColorStyles = {};
    this.itemStyles = new Map();
    this.multipleInputs = false;
    this.focusable = true;
    /**
     * If `true`, a button tag will be rendered and the item will be tappable.
     */
    this.button = true;
    /**
     * The icon to use when `detail` is set to `true`.
     */
    this.detailIcon = chevronForward;
    /**
     * If `true`, the user cannot interact with the item.
     */
    this.disabled = false;
    /**
     * If `true`, the user cannot interact with the item.
     */
    this.divider = false;
    this.subItem = false;
    /**
     * How the bottom border should be displayed on the item.
     */
    this.lines = 'full';
    /**
     * If `true`, a character counter will display the ratio of characters used and the total character limit. Only applies when the `maxlength` property is set on the inner `gasco-input` or `gasco-textarea`.
     */
    this.counter = false;
    /**
     * The type of the button. Only used when an `onclick` or `button` property is present.
     */
    this.type = 'button';
  }
  counterFormatterChanged() {
    this.updateCounterOutput(this.getFirstInput());
  }
  handleGascoChange(ev) {
    if (this.counter && ev.target === this.getFirstInput()) {
      this.updateCounterOutput(ev.target);
    }
  }
  labelColorChanged(ev) {
    const { color } = this;
    // There will be a conflict with item color if
    // we apply the label color to item, so we ignore
    // the label color if the user sets a color on item
    if (color === undefined) {
      this.labelColorStyles = ev.detail;
    }
  }
  itemStyle(ev) {
    ev.stopPropagation();
    const tagName = ev.target.tagName;
    const updatedStyles = ev.detail;
    const newStyles = {};
    const childStyles = this.itemStyles.get(tagName) || {};
    let hasStyleChange = false;
    Object.keys(updatedStyles).forEach((key) => {
      if (updatedStyles[key]) {
        const itemKey = `item-${key}`;
        if (!childStyles[itemKey]) {
          hasStyleChange = true;
        }
        newStyles[itemKey] = true;
      }
    });
    if (!hasStyleChange && Object.keys(newStyles).length !== Object.keys(childStyles).length) {
      hasStyleChange = true;
    }
    if (hasStyleChange) {
      this.itemStyles.set(tagName, newStyles);
      forceUpdate(this);
    }
  }
  connectedCallback() {
    if (this.counter) {
      this.updateCounterOutput(this.getFirstInput());
    }
    this.hasStartEl();
  }
  componentDidUpdate() {
    // Do not use @Listen here to avoid making all items
    // appear as clickable to screen readers
    const input = this.getFirstInput();
    if (input && !this.clickListener) {
      this.clickListener = (ev) => this.delegateFocus(ev, input);
      this.el.addEventListener('click', this.clickListener);
    }
  }
  disconnectedCallback() {
    const input = this.getFirstInput();
    if (input && this.clickListener) {
      this.el.removeEventListener('click', this.clickListener);
      this.clickListener = undefined;
    }
  }
  componentDidLoad() {
    raf(() => {
      this.setMultipleInputs();
      this.focusable = this.isFocusable();
    });
  }
  // If the item contains multiple clickable elements and/or inputs, then the item
  // should not have a clickable input cover over the entire item to prevent
  // interfering with their individual click events
  setMultipleInputs() {
    // The following elements have a clickable cover that is relative to the entire item
    const covers = this.el.querySelectorAll('gasco-checkbox, gasco-datetime, gasco-select, gasco-radio');
    // The following elements can accept focus alongside the previous elements
    // therefore if these elements are also a child of item, we don't want the
    // input cover on top of those interfering with their clicks
    const inputs = this.el.querySelectorAll('gasco-input, gasco-range, gasco-autocomplete');
    // The following elements should also stay clickable when an input with cover is present
    const clickables = this.el.querySelectorAll('gasco-button, a, button');
    // Check for multiple inputs to change the position of the input cover to relative
    // for all of the covered inputs above
    this.multipleInputs =
      covers.length + inputs.length > 1 ||
        covers.length + clickables.length > 1 ||
        (covers.length > 0 && this.isClickable());
  }
  // If the item contains an input including a checkbox, datetime, select, or radio
  // then the item will have a clickable input cover that covers the item
  // that should get the hover, focused and activated states UNLESS it has multiple
  // inputs, then those need to individually get each click
  hasCover() {
    const inputs = this.el.querySelectorAll('gasco-checkbox, gasco-datetime, gasco-select, gasco-radio');
    return inputs.length === 1 && !this.multipleInputs;
  }
  // If the item has an href or button property it will render a native
  // anchor or button that is clickable
  isClickable() {
    return this.href !== undefined || this.button;
  }
  canActivate() {
    return this.isClickable() || this.hasCover();
  }
  isFocusable() {
    const focusableChild = this.el.querySelector('.gasco-focusable');
    return this.canActivate() || focusableChild !== null;
  }
  getFirstInput() {
    const inputs = this.el.querySelectorAll('gasco-input, gasco-textarea');
    return inputs[0];
  }
  // This is needed for WebKit due to a delegatesFocus bug where
  // clicking on the left padding of an item is not focusing the input
  // but is opening the keyboard. It will no longer be needed with
  // iOS 14.
  delegateFocus(ev, input) {
    const clickedItem = ev.target.tagName === 'GASCO-ITEM';
    let firstActive = false;
    // If the first input is the same as the active element we need
    // to focus the first input again, but if the active element
    // is another input inside of the item we shouldn't switch focus
    if (document.activeElement) {
      firstActive = input.querySelector('input, textarea') === document.activeElement;
    }
    // Only focus the first input if we clicked on an gasco-item
    // and the first input exists
    if (clickedItem && (firstActive || !this.multipleInputs)) {
      input.fireFocusEvents = false;
      input.setBlur();
      input.setFocus();
      raf(() => {
        input.fireFocusEvents = true;
      });
    }
  }
  updateCounterOutput(inputEl) {
    var _a, _b;
    const { counter, counterFormatter, defaultCounterFormatter } = this;
    if (counter && !this.multipleInputs && (inputEl === null || inputEl === void 0 ? void 0 : inputEl.maxlength) !== undefined) {
      const length = (_b = (_a = inputEl === null || inputEl === void 0 ? void 0 : inputEl.value) === null || _a === void 0 ? void 0 : _a.toString().length) !== null && _b !== void 0 ? _b : 0;
      if (counterFormatter === undefined) {
        this.counterString = defaultCounterFormatter(length, inputEl.maxlength);
      }
      else {
        try {
          this.counterString = counterFormatter(length, inputEl.maxlength);
        }
        catch (e) {
          printGascoError('Exception in provided `counterFormatter`.', e);
          // Fallback to the default counter formatter when an exception happens
          this.counterString = defaultCounterFormatter(length, inputEl.maxlength);
        }
      }
    }
  }
  defaultCounterFormatter(length, maxlength) {
    return `${length} / ${maxlength}`;
  }
  hasStartEl() {
    const startEl = this.el.querySelector('[slot="start"]');
    if (startEl !== null) {
      this.el.classList.add('item-has-start-slot');
    }
  }
  render() {
    const { download, fill, labelColorStyles, lines, disabled, subItem, href, divider, rel, shape, target, } = this;
    const childStyles = {};
    const clickable = this.isClickable();
    const canActivate = this.canActivate();
    const TagType = clickable ? (href === undefined ? 'button' : 'a') : 'div';
    const attrs = TagType === 'button'
      ? { type: this.type }
      : {
        download,
        href,
        rel,
        target,
      };
    this.itemStyles.forEach((value) => {
      Object.assign(childStyles, value);
    });
    const exactRouter = attrs.href === window.location.pathname ? ' router-active' : '';
    const ariaDisabled = disabled || childStyles['item-interactive-disabled'] ? 'true' : null;
    const fillValue = fill || 'none';
    const inList = hostContext('gasco-list', this.el);
    return (h(Host, { "aria-disabled": ariaDisabled, class: Object.assign(Object.assign(Object.assign({}, childStyles), labelColorStyles), createColorClasses(this.color, {
        item: true,
        [`item-lines-${lines}`]: lines !== undefined,
        [`item-fill-${fillValue}`]: true,
        [`item-shape-${shape}`]: shape !== undefined,
        'item-divider': divider,
        'item-disabled': disabled,
        'sub-item': subItem,
        'in-list': inList,
        'item-multiple-inputs': this.multipleInputs,
        'gasco-activatable': canActivate,
        'gasco-focusable': this.focusable,
        'item-rtl': document.dir === 'rtl',
      })), role: inList ? 'listitem' : null },
      h(TagType, Object.assign({}, attrs, { part: "native", disabled: disabled, class: `item-native${exactRouter}` }),
        h("slot", { name: "start" }),
        h("div", { class: "item-inner" },
          h("div", { class: "input-wrapper" },
            h("slot", null)),
          h("slot", { name: "end" }),
          h("div", { class: "item-inner-highlight" })),
        h("div", { class: "item-highlight" })),
      h("slot", { name: "sub-list" }),
      h("div", { class: "item-bottom" },
        h("slot", { name: "error" }),
        h("slot", { name: "helper" }))));
  }
  static get is() { return "gasco-item"; }
  static get encapsulation() { return "shadow"; }
  static get delegatesFocus() { return true; }
  static get originalStyleUrls() { return {
    "$": ["gasco-item.md.scss"]
  }; }
  static get styleUrls() { return {
    "$": ["gasco-item.md.css"]
  }; }
  static get properties() { return {
    "color": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "Color",
        "resolved": "\"danger\" | \"dark\" | \"light\" | \"medium\" | \"primary\" | \"secondary\" | \"success\" | \"tertiary\" | \"warning\" | string & Record<never, never>",
        "references": {
          "Color": {
            "location": "import",
            "path": "../../interface"
          }
        }
      },
      "required": false,
      "optional": true,
      "docs": {
        "tags": [],
        "text": "The color to use from your application's color palette.\nDefault options are: `\"primary\"`, `\"secondary\"`, `\"tertiary\"`, `\"success\"`, `\"warning\"`, `\"danger\"`, `\"light\"`, `\"medium\"`, and `\"dark\"`.\nFor more information on colors, see [theming](/docs/theming/basics)."
      },
      "attribute": "color",
      "reflect": true
    },
    "button": {
      "type": "boolean",
      "mutable": false,
      "complexType": {
        "original": "boolean",
        "resolved": "boolean",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "If `true`, a button tag will be rendered and the item will be tappable."
      },
      "attribute": "button",
      "reflect": false,
      "defaultValue": "true"
    },
    "detail": {
      "type": "boolean",
      "mutable": false,
      "complexType": {
        "original": "boolean",
        "resolved": "boolean",
        "references": {}
      },
      "required": false,
      "optional": true,
      "docs": {
        "tags": [],
        "text": "If `true`, a detail arrow will appear on the item. Defaults to `false` unless the `mode`\nis `ios` and an `href` or `button` property is present."
      },
      "attribute": "detail",
      "reflect": false
    },
    "detailIcon": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "string",
        "resolved": "string",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "The icon to use when `detail` is set to `true`."
      },
      "attribute": "detail-icon",
      "reflect": false,
      "defaultValue": "chevronForward"
    },
    "disabled": {
      "type": "boolean",
      "mutable": false,
      "complexType": {
        "original": "boolean",
        "resolved": "boolean",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "If `true`, the user cannot interact with the item."
      },
      "attribute": "disabled",
      "reflect": false,
      "defaultValue": "false"
    },
    "divider": {
      "type": "boolean",
      "mutable": false,
      "complexType": {
        "original": "boolean",
        "resolved": "boolean",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "If `true`, the user cannot interact with the item."
      },
      "attribute": "divider",
      "reflect": false,
      "defaultValue": "false"
    },
    "download": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "string | undefined",
        "resolved": "string",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "This attribute instructs browsers to download a URL instead of navigating to\nit, so the user will be prompted to save it as a local file. If the attribute\nhas a value, it is used as the pre-filled file name in the Save prompt\n(the user can still change the file name if they want)."
      },
      "attribute": "download",
      "reflect": false
    },
    "fill": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "'outline' | 'solid'",
        "resolved": "\"outline\" | \"solid\"",
        "references": {}
      },
      "required": false,
      "optional": true,
      "docs": {
        "tags": [],
        "text": "The fill for the item. If `'solid'` the item will have a background. If\n`'outline'` the item will be transparent with a border. Only available in `md` mode."
      },
      "attribute": "fill",
      "reflect": false
    },
    "shape": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "'round'",
        "resolved": "\"round\"",
        "references": {}
      },
      "required": false,
      "optional": true,
      "docs": {
        "tags": [],
        "text": "The shape of the item. If \"round\" it will have increased\nborder radius."
      },
      "attribute": "shape",
      "reflect": false
    },
    "href": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "string | undefined",
        "resolved": "string",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "Contains a URL or a URL fragment that the hyperlink points to.\nIf this property is set, an anchor tag will be rendered."
      },
      "attribute": "href",
      "reflect": false
    },
    "rel": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "string | undefined",
        "resolved": "string",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "Specifies the relationship of the target object to the link object.\nThe value is a space-separated list of [link types](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types)."
      },
      "attribute": "rel",
      "reflect": false
    },
    "subItem": {
      "type": "boolean",
      "mutable": false,
      "complexType": {
        "original": "boolean",
        "resolved": "boolean",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": ""
      },
      "attribute": "sub-item",
      "reflect": false,
      "defaultValue": "false"
    },
    "lines": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "'full' | 'inset' | 'none'",
        "resolved": "\"full\" | \"inset\" | \"none\"",
        "references": {}
      },
      "required": false,
      "optional": true,
      "docs": {
        "tags": [],
        "text": "How the bottom border should be displayed on the item."
      },
      "attribute": "lines",
      "reflect": false,
      "defaultValue": "'full'"
    },
    "counter": {
      "type": "boolean",
      "mutable": false,
      "complexType": {
        "original": "boolean",
        "resolved": "boolean",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "If `true`, a character counter will display the ratio of characters used and the total character limit. Only applies when the `maxlength` property is set on the inner `gasco-input` or `gasco-textarea`."
      },
      "attribute": "counter",
      "reflect": false,
      "defaultValue": "false"
    },
    "routerAnimation": {
      "type": "unknown",
      "mutable": false,
      "complexType": {
        "original": "AnimationBuilder | undefined",
        "resolved": "(baseEl: any, opts?: any) => Animation",
        "references": {
          "AnimationBuilder": {
            "location": "import",
            "path": "../../interface"
          }
        }
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "When using a router, it specifies the transition animation when navigating to\nanother page using `href`."
      }
    },
    "target": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "string | undefined",
        "resolved": "string",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "Specifies where to display the linked URL.\nOnly applies when an `href` is provided.\nSpecial keywords: `\"_blank\"`, `\"_self\"`, `\"_parent\"`, `\"_top\"`."
      },
      "attribute": "target",
      "reflect": false
    },
    "type": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "'submit' | 'reset' | 'button'",
        "resolved": "\"button\" | \"reset\" | \"submit\"",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "The type of the button. Only used when an `onclick` or `button` property is present."
      },
      "attribute": "type",
      "reflect": false,
      "defaultValue": "'button'"
    },
    "counterFormatter": {
      "type": "unknown",
      "mutable": false,
      "complexType": {
        "original": "CounterFormatter",
        "resolved": "(inputLength: number, maxLength: number) => string",
        "references": {
          "CounterFormatter": {
            "location": "import",
            "path": "./gasco-item-interface"
          }
        }
      },
      "required": false,
      "optional": true,
      "docs": {
        "tags": [],
        "text": "A callback used to format the counter text.\nBy default the counter text is set to \"itemLength / maxLength\"."
      }
    }
  }; }
  static get states() { return {
    "multipleInputs": {},
    "focusable": {},
    "counterString": {}
  }; }
  static get elementRef() { return "el"; }
  static get watchers() { return [{
      "propName": "counterFormatter",
      "methodName": "counterFormatterChanged"
    }]; }
  static get listeners() { return [{
      "name": "gascoChange",
      "method": "handleGascoChange",
      "target": undefined,
      "capture": false,
      "passive": false
    }, {
      "name": "gascoColor",
      "method": "labelColorChanged",
      "target": undefined,
      "capture": false,
      "passive": false
    }, {
      "name": "gascoStyle",
      "method": "itemStyle",
      "target": undefined,
      "capture": false,
      "passive": false
    }]; }
}
