import cloneDeep from 'clone-deep';
import { html, nothing } from 'lit';
import { state, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { boolean } from '../../internal/boolean.mjs';
import FlowRdfElement, { FlowRdfFormControl } from '../../internal/flow-ui-rdf-element.mjs';
import { HasSlotController } from '../../internal/slot.mjs';
import { isNil, isFunction, isDefined, isUndefined } from '../../internal/type-checks.mjs';
import { watch } from '../../internal/watch.mjs';
import { defaultStringTemplate, defaultNumberTemplate, defaultDateTimeTemplate, defaultDateTemplate, defaultTimeTemplate, defaultSelectTemplate } from './default-value-templates.mjs';
import styles from './query-builder.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;
};
const _FlowUiQueryBuilder = class _FlowUiQueryBuilder extends FlowRdfElement {
  constructor() {
    super(...arguments);
    this._value = { condition: "and", rules: [{ field: "", operator: "=", enabled: true }] };
    // Track the last value control that had focus when the enter key was pressed
    this.enterKeyElement = null;
    this.hasSlotController = new HasSlotController(
      this,
      "help-text",
      "label",
      "header-accessory",
      "footer-accessory",
      "menu"
    );
    this.value = { condition: "and", rules: [] };
    this.config = { fields: {} };
    this.size = "medium";
    this.disabled = false;
    this.label = "";
    this.helpText = "";
    this.treeLines = false;
    this.subQueryBuilder = false;
    this.borders = false;
  }
  /**
   * Register a custom field definition.
   *
   * A field definition is used to add support for a custom field type. A field type comprises:
   *
   * - a field `type` string
   * - a `template` function that returns the template for the field's value control
   * - an array of `operators` that are valid for the field
   *
   * @param type
   * @param fieldDefinition
   */
  static registerFieldDefinition(type, fieldDefinition) {
    _FlowUiQueryBuilder.fieldDefinitions.set(type, fieldDefinition);
  }
  connectedCallback() {
    super.connectedCallback();
    this.classList.add(FlowRdfFormControl.FLOW_UI_FORM_CONTROL_CLASS);
    this.disabled = boolean(this.disabled, true);
    this.borders = boolean(this.borders, true);
    this.treeLines = boolean(this.treeLines, true);
  }
  handleClassChange() {
    this.classList.add(FlowRdfFormControl.FLOW_UI_FORM_CONTROL_CLASS);
  }
  handleValueChange() {
    this._value = this.subQueryBuilder ? this.value : cloneDeep(this.value);
    if (isNil(this._value)) {
      this._value = { condition: "and", rules: [{ field: "", operator: "=", enabled: true }] };
    }
  }
  valueTemplateForRule(_rule, field) {
    const fieldDef = field?.type && _FlowUiQueryBuilder.fieldDefinitions.get(field.type);
    if (fieldDef?.template) {
      return fieldDef.template;
    } else {
      return (_rule2, _field, _qb, _update) => {
        return html`
          <flow-ui-input
            filled
            class="query-builder__rule-value-control"
            size=${this.size}
            placeholder="Value"
          ></flow-ui-input>
        `;
      };
    }
  }
  getDefault(defaultVal, fallback) {
    if (isFunction(defaultVal)) {
      return defaultVal();
    }
    return isDefined(defaultVal) ? defaultVal : fallback;
  }
  handleAddRule(ev) {
    ev.stopPropagation();
    this._value.rules.push({
      field: "",
      operator: "=",
      enabled: true
    });
    this.requestUpdate();
    this.updateComplete.then(() => {
      this.emitValueChange();
    });
  }
  /**
   * Handles removal of a rule set (displayed by a sub-query builder) in response to a remove event.
   *
   * @param ev
   * @param index
   */
  handleRemoveRule(ev, index) {
    ev.stopPropagation();
    const confirmDelete = this.emit("flow-ui-remove", { cancelable: true });
    if (!confirmDelete.defaultPrevented) {
      this._value.rules.splice(index, 1);
      this.requestUpdate();
      this.updateComplete.then(() => {
        this.emitValueChange();
      });
    }
  }
  handleSubQueryValueChange(ev) {
    ev.stopPropagation();
    this.emitValueChange();
  }
  handleFieldChanged(ev, rule) {
    ev.stopPropagation();
    const target = ev.target;
    const field = target.value;
    const fieldConfig = this.config.fields[field];
    if (!rule.field) {
      rule.operator = this.getDefault(fieldConfig.defaultOperator, "=");
      rule.value = this.getDefault(fieldConfig.defaultValue);
    }
    rule.field = field;
    this.emitValueChange();
  }
  handleOperatorChanged(ev, rule) {
    ev.stopPropagation();
    const target = ev.target;
    rule.operator = target.value;
    this.emitValueChange();
  }
  handleConditionChange(ev) {
    ev.stopPropagation();
    const target = ev.target;
    this._value.condition = target.value;
    this.emitValueChange();
  }
  handleRuleMenuSelect(ev, rule, index) {
    const menuItem = ev.detail.item;
    if (menuItem.value === "toggle") {
      rule.enabled = menuItem.checked;
    } else if (menuItem.value === "duplicate") {
      const duplicateRule = cloneDeep(rule);
      this._value.rules.splice(index, 0, duplicateRule);
    } else if (menuItem.value === "delete") {
      const confirmDelete = this.emit("flow-ui-remove", { cancelable: true });
      if (!confirmDelete.defaultPrevented) {
        this._value.rules.splice(index, 1);
        if (this._value.rules.length === 0) {
          this._value.rules.push({ field: "", operator: "=", enabled: true });
        }
      }
    }
    this.requestUpdate();
    this.updateComplete.then(() => {
      this.emitValueChange();
    });
  }
  handleValueKeyDown(ev) {
    const hasModifier = ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey;
    if (ev.key === "Enter" && !hasModifier) {
      if (ev.target === this.enterKeyElement) {
        this.enterKeyElement = null;
        this.emit("flow-ui-apply");
      } else {
        this.enterKeyElement = ev.target;
      }
    } else {
      this.enterKeyElement = null;
    }
  }
  handleToolbarMenuSelect(ev) {
    const menuItem = ev.detail.item;
    const emptyRuleSet = {
      condition: "and",
      rules: [{ field: "", operator: "=", enabled: true }]
    };
    let updateValue = false;
    if (menuItem.value === "add-group") {
      this._value.rules.push(emptyRuleSet);
      updateValue = true;
    } else if (menuItem.value === "delete-group") {
      this.emit("flow-ui-remove");
    } else if (menuItem.value === "clear-filters") {
      updateValue = false;
      this._value = emptyRuleSet;
      this.requestUpdate();
      this.updateComplete.then(() => {
        this.emitValueChange();
        this.emit("flow-ui-clear");
      });
    }
    if (updateValue) {
      this.requestUpdate();
      this.updateComplete.then(() => {
        this.emitValueChange();
      });
    }
  }
  emitValueChange() {
    this.value = this._value;
    this.emit("flow-ui-input");
    this.emit("flow-ui-change");
  }
  operatorsForField(field) {
    const fieldConfig = this.config.fields[field];
    if (!fieldConfig) {
      return ["="];
    }
    if (fieldConfig.operators) {
      return fieldConfig.operators;
    }
    return this.defaultOperatorsForFieldType(fieldConfig.type);
  }
  defaultOperatorsForFieldType(type) {
    const fieldDef = _FlowUiQueryBuilder.fieldDefinitions.get(type);
    if (fieldDef?.operators) {
      return fieldDef.operators;
    } else {
      return ["=", "!="];
    }
  }
  toolbarTemplate(ruleSet) {
    const andOrSize = this.size === "large" ? "small" : "x-small";
    return html`
      <div class="query-builder__toolbar">
        <flow-ui-tool-button-group
          mode="radio"
          size=${andOrSize}
          pill
          outline
          class="query-builder-and-or-button-group"
          value=${ruleSet.condition}
          @flow-ui-change=${this.handleConditionChange}
        >
          <flow-ui-tool-button value="and">And</flow-ui-tool-button>
          <flow-ui-tool-button value="or">Or</flow-ui-tool-button>
        </flow-ui-tool-button-group>

        <div class="spacer"></div>

        ${this.hasSlotController.test("header-accessory") ? html`
              <div part="header-accessories" class="query-builder__header-accessories">
                <slot name="header-accessory"></slot>
              </div>
            ` : nothing}

        <flow-ui-dropdown placement="bottom-end" class="query-builder__menu-dropdown">
          <flow-ui-icon-button
            class="query-builder__rule-menu-button"
            slot="trigger"
            name="ellipsis-vertical"
            library="system"
          ></flow-ui-icon-button>
          <flow-ui-menu @flow-ui-select=${(ev) => this.handleToolbarMenuSelect(ev)}>
            ${this.hasSlotController.test("menu") ? html`
                  <slot name="menu"></slot>
                  <flow-ui-divider></flow-ui-divider>
                ` : nothing}
            <flow-ui-menu-item value="add-group">Add Filter Group</flow-ui-menu-item>
            ${this.subQueryBuilder ? html`
                  <flow-ui-divider></flow-ui-divider>
                  <flow-ui-menu-item value="delete-group" class="menu-item--danger">
                    Delete Filter Group
                  </flow-ui-menu-item>
                ` : nothing}
            ${this.subQueryBuilder ? nothing : html`
                  <flow-ui-divider></flow-ui-divider>
                  <flow-ui-menu-item value="clear-filters" class="menu-item--danger">Clear Filters</flow-ui-menu-item>
                `}
          </flow-ui-menu>
        </flow-ui-dropdown>
      </div>
    `;
  }
  ruleTemplate(rule, fieldConfig, index) {
    return html`
      <div
        class=${classMap({
      "query-builder__rule": true,
      "query-builder__rule--disabled": !rule.enabled
    })}
      >
        <div class="query-builder__rule-control-container">
          <flow-ui-select
            filled
            class="query-builder__field-select"
            value=${ifDefined(rule.field)}
            size=${this.size}
            placeholder="Select field"
            hoist
            ?disabled=${!rule.enabled}
            @flow-ui-change=${(ev) => this.handleFieldChanged(ev, rule)}
          >
            ${Object.keys(this.config.fields).map((key) => {
      const field = this.config.fields[key];
      return html`
                <flow-ui-option value=${key}>${field.name}</flow-ui-option>
              `;
    })}
          </flow-ui-select>
          <flow-ui-select
            filled
            class="query-builder__operator-select"
            value=${ifDefined(rule.operator)}
            size=${this.size}
            placeholder="Operator"
            hoist
            ?disabled=${!rule.enabled || isUndefined(rule.field)}
            @flow-ui-change=${(ev) => this.handleOperatorChanged(ev, rule)}
          >
            ${this.operatorsForField(rule.field).map((operator) => {
      return html`
                <flow-ui-option value=${operator}>${operator}</flow-ui-option>
              `;
    })}
          </flow-ui-select>

          ${this.ruleValueTemplate(rule, fieldConfig)}
        </div>

        <flow-ui-dropdown placement="bottom-end" class="query-builder__menu-dropdown">
          <flow-ui-icon-button
            class="query-builder__rule-menu-button"
            slot="trigger"
            name="ellipsis-vertical"
            library="system"
          ></flow-ui-icon-button>
          <flow-ui-menu @flow-ui-select=${(ev) => this.handleRuleMenuSelect(ev, rule, index)}>
            <flow-ui-menu-item type="checkbox" value="toggle" ?checked=${rule.enabled}>Enable Filter</flow-ui-menu-item>
            <flow-ui-divider></flow-ui-divider>
            <flow-ui-menu-item value="duplicate">Duplicate</flow-ui-menu-item>
            <flow-ui-divider></flow-ui-divider>
            <flow-ui-menu-item value="delete" class="menu-item--danger">Delete</flow-ui-menu-item>
          </flow-ui-menu>
        </flow-ui-dropdown>
      </div>
    `;
  }
  ruleValueTemplate(rule, fieldConfig) {
    const template = this.valueTemplateForRule(rule, fieldConfig);
    return html`
      <div
        class="query-builder__rule-value-wrapper"
        @keydown=${this.handleValueKeyDown}
        @flow-ui-change=${(ev) => ev.stopPropagation()}
        @flow-ui-input=${(ev) => ev.stopPropagation()}
      >
        ${template(rule, fieldConfig, this, this.emitValueChange.bind(this))}
      </div>
    `;
  }
  subQueryBuilderTemplate(ruleSet, index) {
    return html`
      <flow-ui-query-builder
        sub-query-builder
        class=${classMap({
      "query-builder__rule": true,
      "query-builder--child": true,
      "query-builder--no-rules": ruleSet.rules.length === 0
    })}
        .value=${ruleSet}
        .config=${this.config}
        size=${this.size}
        ?borders=${this.borders}
        ?tree-lines=${this.treeLines}
        @flow-ui-change=${this.handleSubQueryValueChange}
        @flow-ui-remove=${(ev) => this.handleRemoveRule(ev, index)}
      ></flow-ui-query-builder>
    `;
  }
  emptyRulesetTemplate() {
    return html`
      <div class="query-builder__empty-ruleset">
        <i>A rule set cannot be empty. Please add a rule or remove the set.</i>
      </div>
    `;
  }
  render() {
    const hasLabelSlot = this.hasSlotController.test("label");
    const hasHelpTextSlot = this.hasSlotController.test("help-text");
    const hasLabel = this.label ? true : !!hasLabelSlot;
    const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;
    return html`
      <div
        part="form-control"
        class=${classMap({
      "form-control": true,
      "form-control--small": this.size === "small",
      "form-control--medium": this.size === "medium",
      "form-control--large": this.size === "large",
      "form-control--has-label": hasLabel,
      "form-control--has-help-text": hasHelpText,
      "form-control--has-border": this.borders
    })}
      >
        <label
          id="label"
          part="form-control-label"
          class="form-control__label"
          aria-hidden=${hasLabel ? "false" : "true"}
        >
          <slot name="label">${this.label}</slot>
        </label>

        <slot
          name="help-text"
          part="form-control-help-text"
          id="help-text"
          class="form-control__help-text"
          aria-hidden=${hasHelpText ? "false" : "true"}
        >
          ${this.helpText}
        </slot>

        <div
          class=${classMap({
      "query-builder": true,
      "query-builder--small": this.size === "small",
      "query-builder--medium": this.size === "medium",
      "query-builder--large": this.size === "large",
      "query-builder--disabled": this.disabled,
      "query-builder--tree-lines": this.treeLines,
      "query-builder--has-border": this.borders,
      "query-builder--no-rules": this.subQueryBuilder && this._value.rules.length === 0
    })}
        >
          ${this.toolbarTemplate(this._value)}

          <div class="query-builder__rules">
            ${this._value.rules.length ? this._value.rules.map((rule, index) => {
      if ("rules" in rule) {
        return this.subQueryBuilderTemplate(rule, index);
      } else {
        return this.ruleTemplate(rule, rule.field ? this.config.fields[rule.field] : void 0, index);
      }
    }) : this.subQueryBuilder ? this.emptyRulesetTemplate() : nothing}
          </div>

          <div part="footer" class="query-builder__footer">
            <flow-ui-tool-button
              @click=${this.handleAddRule}
              class="query-builder__add-filter-button"
              icon="plus"
              icon-library="system"
            >
              Add filter
            </flow-ui-tool-button>
          </div>
        </div>
        <div part="footer-accessories" class="query-builder__footer-auxiliary">
          <slot name="footer-accessory"></slot>
        </div>
      </div>
    `;
  }
};
_FlowUiQueryBuilder.styles = styles;
_FlowUiQueryBuilder.fieldDefinitions = /* @__PURE__ */ new Map();
__decorateClass([
  state()
], _FlowUiQueryBuilder.prototype, "_value", 2);
__decorateClass([
  property({ reflect: true })
], _FlowUiQueryBuilder.prototype, "class", 2);
__decorateClass([
  property({ attribute: false })
], _FlowUiQueryBuilder.prototype, "value", 2);
__decorateClass([
  property({ attribute: false })
], _FlowUiQueryBuilder.prototype, "config", 2);
__decorateClass([
  property({ reflect: true })
], _FlowUiQueryBuilder.prototype, "size", 2);
__decorateClass([
  property({ type: Boolean, reflect: true })
], _FlowUiQueryBuilder.prototype, "disabled", 2);
__decorateClass([
  property({ reflect: true })
], _FlowUiQueryBuilder.prototype, "label", 2);
__decorateClass([
  property({ attribute: "help-text" })
], _FlowUiQueryBuilder.prototype, "helpText", 2);
__decorateClass([
  property({ type: Boolean, attribute: "tree-lines" })
], _FlowUiQueryBuilder.prototype, "treeLines", 2);
__decorateClass([
  property({ type: Boolean, attribute: "sub-query-builder" })
], _FlowUiQueryBuilder.prototype, "subQueryBuilder", 2);
__decorateClass([
  property({ type: Boolean })
], _FlowUiQueryBuilder.prototype, "borders", 2);
__decorateClass([
  watch("class")
], _FlowUiQueryBuilder.prototype, "handleClassChange", 1);
__decorateClass([
  watch("value")
], _FlowUiQueryBuilder.prototype, "handleValueChange", 1);
let FlowUiQueryBuilder = _FlowUiQueryBuilder;
FlowUiQueryBuilder.registerFieldDefinition("string", {
  template: defaultStringTemplate,
  operators: ["=", "!=", "contains", "like", "regex"]
});
FlowUiQueryBuilder.registerFieldDefinition("number", {
  template: defaultNumberTemplate,
  operators: ["=", "!=", "<", "<=", ">=", ">"]
});
FlowUiQueryBuilder.registerFieldDefinition("datetime", {
  template: defaultDateTimeTemplate,
  operators: ["=", "!=", "<", "<=", ">=", ">", "between"]
});
FlowUiQueryBuilder.registerFieldDefinition("date", {
  template: defaultDateTemplate,
  operators: ["=", "!=", "<", "<=", ">=", ">"]
});
FlowUiQueryBuilder.registerFieldDefinition("time", {
  template: defaultTimeTemplate,
  operators: ["=", "!=", "<", "<=", ">=", ">"]
});
FlowUiQueryBuilder.registerFieldDefinition("select", {
  template: defaultSelectTemplate,
  operators: ["=", "!=", "in", "!in"]
});

export { FlowUiQueryBuilder as default };
