import SelectStyles from "../styles/SelectStyles.wrn"

component ComboBox {
  outputs {
    search(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
    select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
    change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
    clear(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
    open(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
    close(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
    load(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
    error(payload: { error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)
  }

  props {
    size: string = "default"
    color: string = "primary"
    label: string = "ComboBox"
    name: string = ""
    value: string = ""
    options: unknown[] = []
    groups: unknown[] = []
    placeholder: string = "Search or select an option"
    searchPlaceholder: string = "Start typing…"
    clearable: boolean = true
    allowCustomValue: boolean = false
    disabled: boolean = false
    required: boolean = false
    invalid: boolean = false
    validationMessage: string = ""
    helpText: string = ""
    loading: boolean = false
    loadingLabel: string = "Loading suggestions…"
    emptyLabel: string = "No matching options"
    clearLabel: string = "Clear value"
    toggleLabel: string = "Toggle suggestions"
    searchMode: string = "contains"
    searchFields: string = "label,description"
    minSearchLength: number = 0
    searchResultLimit: number = 0
    optionTemplate: string = "default"
    defaultOpen: boolean = false
    closeOnSelect: boolean = true
    fixed: boolean = false
    placement: string = "bottom"
    autocomplete: string = "off"
    remote: boolean = false
    remoteUrl: string = ""
    remoteQueryParam: string = "q"
    remoteDebounce: number = 250
    remoteAutoLoad: boolean = true
    infinite: boolean = false
    hasMore: boolean = false
    page: number = 1
    loadMoreLabel: string = "Load more"
    class: string = ""
  }

  state open = defaultOpen
  state query: string = ""
  state selectedValue = value
  state activeIndex: number = -1

  functions {
    shared function allOptions() {
      return [...groups.flatMap((group) => group.options || []), ...options]
    }

    shared function searchableText(option) {
      return ((option.label || "") + " " + (option.description || "")).toLowerCase()
    }

    shared function matches(option) {
      if (!query || query.length < minSearchLength) {
        return true
      }
      if (searchMode === "startsWith") {
        return searchableText(option).startsWith(query.toLowerCase())
      }
      if (searchMode === "exact") {
        return searchableText(option) === query.toLowerCase()
      }
      return searchableText(option).includes(query.toLowerCase())
    }

    shared function matchingOptions(list) {
      return list.filter((option) => matches(option))
    }

    shared function visibleOptions(list) {
      if (searchResultLimit > 0) {
        return matchingOptions(list).slice(0, searchResultLimit)
      }
      return matchingOptions(list)
    }

    shared function flatVisibleOptions() {
      return visibleOptions(allOptions())
    }

    shared function isSelected(option) {
      return selectedValue === option.value
    }

    shared function selectedOptions() {
      return allOptions().filter((option) => isSelected(option))
    }

    shared function selectedText() {
      return selectedOptions().length ? selectedOptions()[0].label : selectedValue
    }

    shared function inputText() {
      return query || selectedText()
    }

    shared function shouldShowDropdown() {
      if (!open) {
        return false
      }
      if (loading) {
        return true
      }
      if (remote && query.length !== 0 && query.length < minSearchLength) {
        return true
      }
      if (flatVisibleOptions().length) {
        return true
      }
      return infinite && hasMore
    }

    shared function canSelect(option) {
      return !option.disabled
    }

    shared function chooseOption(option) {
      if (!canSelect(option)) {
        return
      }
      selectedValue = option.value
      query = option.label
      activeIndex = optionIndex(option)
      if (closeOnSelect) {
        open = false
      }
    }

    client function updateQuery(event) {
      query = event.target.value
      selectedValue = allowCustomValue ? event.target.value : ""
      open = true
      activeIndex = flatVisibleOptions().length ? 0 : -1
    }

    client function clearValue(event) {
      if (event) {
        event.stopPropagation()
      }
      query = ""
      selectedValue = ""
      activeIndex = -1
      open = false
    }

    shared function openDropdown() {
      if (!disabled) {
        open = true
        activeIndex = flatVisibleOptions().length ? 0 : -1
      }
    }

    shared function closeDropdown() {
      open = false
    }

    shared function toggle() {
      if (open) {
        closeDropdown()
      } else {
        openDropdown()
      }
    }

    shared function setValue(nextValue) {
      selectedValue = nextValue
      query = ""
    }

    shared function moveActive(direction) {
      if (!flatVisibleOptions().length) {
        return
      }
      activeIndex = (activeIndex + direction + flatVisibleOptions().length) % flatVisibleOptions().length
    }

    client function handleKeydown(event) {
      if (disabled) {
        return
      }
      if (event.key === "ArrowDown") {
        event.preventDefault()
        openDropdown()
        moveActive(1)
      } else if (event.key === "ArrowUp") {
        event.preventDefault()
        openDropdown()
        moveActive(-1)
      } else if (event.key === "Enter" && open && activeIndex >= 0) {
        event.preventDefault()
        chooseOption(flatVisibleOptions()[activeIndex])
      } else if (event.key === "Escape") {
        closeDropdown()
      } else if (event.key === "Home" && open) {
        event.preventDefault()
        activeIndex = 0
      } else if (event.key === "End" && open) {
        event.preventDefault()
        activeIndex = flatVisibleOptions().length - 1
      }
    }

    shared function optionIndex(option) {
      return flatVisibleOptions().findIndex((item) => item.value === option.value)
    }
  }

  view {
    <div
      class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--advanced-select wrn-next--combobox {open ? 'wrn-next--open' : ''} {fixed ? 'wrn-next--advanced-select-fixed' : ''} {invalid ? 'wrn-next--invalid' : ''} {disabled ? 'wrn-next--disabled' : ''} {class}"
      data-placement="{placement}"
      data-search-mode="{searchMode}"
      data-remote="{remote ? 'true' : 'false'}"
      data-remote-url="{remoteUrl}"
      data-remote-query-param="{remoteQueryParam}"
      data-remote-debounce="{remoteDebounce}"
      data-remote-auto-load="{remoteAutoLoad ? 'true' : 'false'}"
      data-infinite="{infinite ? 'true' : 'false'}"
      data-page="{page}"
      data-wrn-select
      data-wrn-combobox
      @focusout="if (!event.currentTarget.contains(event.relatedTarget)) { closeDropdown() }"
    >
      <label id="{name}-label" for="{name}-input">{label}</label>

      <div class="wrn-next__select-control wrn-next__combobox-control">
        <span class="wrn-next__search-icon icon-[lucide--search]" aria-hidden="true"></span>
        <input
          {...attrs}
          id="{name}-input"
          class="wrn-next__select-trigger wrn-next__combobox-input"
          type="text"
          role="combobox"
          name="{allowCustomValue ? name : ''}"
          value="{inputText()}"
          placeholder="{placeholder}"
          autocomplete="{autocomplete}"
          aria-autocomplete="list"
          aria-haspopup="listbox"
          aria-expanded="{open}"
          aria-controls="{name}-listbox"
          aria-labelledby="{name}-label"
          aria-invalid="{invalid}"
          disabled="{disabled}"
          required="{required && allowCustomValue}"
          @focus="openDropdown()"
          @click="openDropdown()"
          @input="updateQuery(event)"
          @keydown="handleKeydown(event)"
        />

        <button
          type="button"
          class="wrn-next__clear-select"
          data-show="clearable && inputText() && !disabled"
          aria-label="{clearLabel}"
          title="{clearLabel}"
          @click="clearValue(event)"
        >
          <span class="icon-[lucide--x]" aria-hidden="true"></span>
        </button>
        <button
          type="button"
          class="wrn-next__combobox-toggle"
          aria-label="{toggleLabel}"
          tabindex="-1"
          disabled="{disabled}"
          @click="toggle()"
        >
          <span class="wrn-next__select-chevron icon-[lucide--chevrons-up-down]" aria-hidden="true"></span>
        </button>
      </div>

      <div
        class="wrn-next__select-dropdown {fixed ? 'wrn-next__select-dropdown--fixed' : ''}"
        data-show="shouldShowDropdown()"
        style="{shouldShowDropdown() ? '' : 'display: none'}"
      >
        <div
          class="wrn-next__select-message"
          data-show="remote && query.length !== 0 && query.length < minSearchLength"
        >Enter at least {minSearchLength} characters.</div>
        <div class="wrn-next__select-message" data-show="loading" role="status">
          <i class="wrn-spinner wrn-spinner--inline" aria-hidden="true"></i>
          {loadingLabel}
        </div>

        <div
          id="{name}-listbox"
          class="wrn-next__select-list"
          data-show="(!remote || (query.length === 0 && remoteAutoLoad) || query.length >= minSearchLength) && !loading"
          role="listbox"
        >
          {#each groups as group}
            {#if visibleOptions(group.options || []).length}
              <div class="wrn-next__option-group" role="group" aria-label="{group.label}">
                <div class="wrn-next__group-label">{group.label}</div>
                {#each group.options || [] as option}
                  <button
                    type="button"
                    class="wrn-next__select-option {isSelected(option) ? 'wrn-next__select-option--selected' : ''} {optionIndex(option) === activeIndex ? 'wrn-next__select-option--active' : ''}"
                    data-option-value="{option.value}"
                    data-show="matches(option)"
                    role="option"
                    aria-selected="{isSelected(option)}"
                    disabled="{!canSelect(option)}"
                    @mouseenter="activeIndex = optionIndex(option)"
                    @mousedown="event.preventDefault()"
                    @click="chooseOption(option)"
                  >
                    {#if optionTemplate === "icon" && option.icon}<i class="{option.icon}" aria-hidden="true"></i>{/if}
                    {#if optionTemplate === "avatar" && option.avatar}<img src="{option.avatar}" alt="" />{/if}
                    {#if optionTemplate === "color" && option.color}<i class="wrn-next__color-dot" style="--option-color: {option.color}"></i>{/if}
                    <span><strong>{option.label}</strong>{#if option.description}<small>{option.description}</small>{/if}</span>
                    <i class="wrn-next__check icon-[lucide--check]" data-show="isSelected(option)" aria-hidden="true"></i>
                  </button>
                {/each}
              </div>
            {/if}
          {/each}

          {#each options as option}
            <button
              type="button"
              class="wrn-next__select-option {isSelected(option) ? 'wrn-next__select-option--selected' : ''} {optionIndex(option) === activeIndex ? 'wrn-next__select-option--active' : ''}"
              data-option-value="{option.value}"
              data-show="matches(option)"
              role="option"
              aria-selected="{isSelected(option)}"
              disabled="{!canSelect(option)}"
              @mouseenter="activeIndex = optionIndex(option)"
              @mousedown="event.preventDefault()"
              @click="chooseOption(option)"
            >
              {#if optionTemplate === "icon" && option.icon}<i class="{option.icon}" aria-hidden="true"></i>{/if}
              {#if optionTemplate === "avatar" && option.avatar}<img src="{option.avatar}" alt="" />{/if}
              {#if optionTemplate === "color" && option.color}<i class="wrn-next__color-dot" style="--option-color: {option.color}"></i>{/if}
              <span><strong>{option.label}</strong>{#if option.description}<small>{option.description}</small>{/if}</span>
              <i class="wrn-next__check icon-[lucide--check]" data-show="isSelected(option)" aria-hidden="true"></i>
            </button>
          {/each}
        </div>

        <button
          type="button"
          class="wrn-next__load-more"
          data-wrn-select-load-more
          data-show="infinite && hasMore"
        >{loadMoreLabel}</button>
      </div>

      {#if !allowCustomValue}
        <input
          type="hidden"
          name="{name}"
          value="{selectedValue}"
          required="{required}"
          data-error="{name}"
          aria-describedby="{validationMessage ? `${name}-validation` : helpText ? `${name}-help` : ''}"
        />
      {/if}

      {#if helpText && !validationMessage}<small id="{name}-help">{helpText}</small>{/if}
      <small
        id="{name}-validation"
        class="wrn-next__validation"
        data-error="{name}"
      >{validationMessage}</small>
    </div>
    <SelectStyles hidden />
  }

  style {
    .wrn-next__combobox-toggle {
      position: absolute;
      top: 50%;
      right: 0;
      z-index: 1;
      display: grid;
      width: 2.25em;
      height: 100%;
      padding: 0;
      place-items: center;
      border: 0;
      color: var(--wrn-color-muted);
      background: transparent;
      cursor: pointer;
      transform: translateY(-50%);
    }

    .wrn-next--combobox .wrn-next__combobox-input {
      padding-right: 5.25em;
      padding-left: 2.5em;
      cursor: text;
    }

    /* Shared component foundation. */

        .wrn-component {
          --wrn-component-color: var(--wrn-color-primary);
          --wrn-component-scale: 1;
          margin: 0;
          color: var(--wrn-color-text);
          font-family: var(--wrn-font-sans, inherit);
        }

        .wrn-component--color-primary {
          --wrn-component-color: var(--wrn-color-primary);
        }

        .wrn-component--color-secondary {
          --wrn-component-color: var(--wrn-color-secondary);
        }

        .wrn-component--color-success {
          --wrn-component-color: var(--wrn-color-success);
        }

        .wrn-component--color-warning {
          --wrn-component-color: var(--wrn-color-warning);
        }

        .wrn-component--color-danger {
          --wrn-component-color: var(--wrn-color-danger);
        }

        .wrn-component--color-info {
          --wrn-component-color: var(--wrn-color-info);
        }

        .wrn-component--size-xs {
          --wrn-component-scale: 0.78;
        }

        .wrn-component--size-sm {
          --wrn-component-scale: 0.88;
        }

        .wrn-component--size-default,
        .wrn-component--size-md {
          --wrn-component-scale: 1;
        }

        .wrn-component--size-lg {
          --wrn-component-scale: 1.14;
        }

        .wrn-component--size-xl {
          --wrn-component-scale: 1.28;
        }

        .wrn-component:not(.wrn-btn) {
          font-size: calc(1em * var(--wrn-component-scale));
        }

        .wrn-component :is(a, button, input, select, textarea):focus-visible {
          outline-color: var(--wrn-component-color);
        }

        .wrn-component p {
          margin: 0;
          color: var(--wrn-color-muted);
          line-height: 1.6;
        }
  }
}
