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

component AdvancedSelect {
  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 = "Advanced Select"
    name: string = ""
    value: string = ""
    values: unknown[] = []
    options: unknown[] = []
    groups: unknown[] = []
    placeholder: string = "Select an option"
    placeholderIcon: string = ""
    searchPlaceholder: string = "Search options…"
    multiple: boolean = false
    searchable: boolean = true
    defaultOpen: boolean = false
    clearable: boolean = true
    allowEmpty: boolean = true
    tags: boolean = false
    disabled: boolean = false
    required: boolean = false
    invalid: boolean = false
    validationMessage: string = ""
    helpText: string = ""
    loading: boolean = false
    loadingLabel: string = "Loading options…"
    emptyLabel: string = "No options found"
    selectedOptionsLabel: string = "Selected options"
    clearLabel: string = "Clear selection"
    createLabel: string = "Create"
    loadMoreLabel: string = "Load more"
    searchMode: string = "contains"
    searchFields: string = "label,description"
    minSearchLength: number = 0
    searchResultLimit: number = 0
    maxSelections: number = 0
    showCounter: boolean = false
    counterTemplate: string = "{selected} selected"
    optionTemplate: string = "default"
    selectedTemplate: string = "default"
    closeOnSelect: boolean = true
    scrollToSelected: boolean = true
    fixed: boolean = false
    placement: string = "bottom"
    remote: boolean = false
    remoteUrl: string = ""
    remoteQueryParam: string = "q"
    remoteDebounce: number = 250
    remoteAutoLoad: boolean = true
    infinite: boolean = false
    hasMore: boolean = false
    page: number = 1
    class: string = ""
  }

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

  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 < Number(minSearchLength || 0)) {
        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, Number(searchResultLimit))
      }
      return matchingOptions(list)
    }

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

    shared function isSelected(option) {
      return (
        multiple
        ? selectedValues.includes(option.value)
        : selectedValue === option.value
      )
    }

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

    shared function selectedCount() {
      return multiple ? selectedValues.length : selectedValue ? 1 : 0
    }

    shared function counterText() {
      return (
        maxSelections
        ? selectedCount() + " / " + maxSelections + " selected"
        : selectedCount() + " selected"
      )
    }

    shared function selectedText() {
      return (
        multiple
        ? selectedValues.join(", ")
        : selectedOptions().length
          ? selectedOptions()[0].label
          : ""
      )
    }

    shared function triggerText() {
      return selectedCount() ? selectedText() : placeholder
    }

    shared function canSelect(option) {
      if (option.disabled) {
        return false
      }
      if (!multiple || isSelected(option)) {
        return true
      }
      if (maxSelections <= 0) {
        return true
      }
      return selectedCount() < maxSelections
    }

    shared function chooseSingle(option) {
      if (!canSelect(option)) {
        return
      }
      selectedValue = option.value
      query = ""
      if (closeOnSelect) {
        open = false
      }
    }

    shared function chooseMultiple(option) {
      if (!canSelect(option)) {
        return
      }
      if (isSelected(option)) {
        selectedValues = selectedValues.filter((item) => item !== option.value)
      } else {
        selectedValues = selectedValues.concat([option.value])
      }
      query = ""
    }

    shared function chooseOption(option) {
      if (multiple) {
        chooseMultiple(option)
        return
      }
      chooseSingle(option)
    }

    client function clearSelection(event) {
      event.stopPropagation()
      selectedValue = ""
      selectedValues = []
      query = ""
      open = false
    }

    shared function toggle() {
      if (disabled) {
        return;
      };
      open = !open;
      activeIndex = open && flatVisibleOptions().length ? 0 : -1;
    }

    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()
        if (!open) {
          open = true
        }
        moveActive(1)
      } else if (event.key === "ArrowUp") {
        event.preventDefault()
        if (!open) {
          open = true
        }
        moveActive(-1)
      } else if (event.key === "Enter" || event.key === " ") {
        event.preventDefault()
        if (!open) {
          open = true
        } else if (activeIndex >= 0) {
          chooseOption(flatVisibleOptions()[activeIndex])
        }
      } else if (event.key === "Escape") {
        open = false
      } 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 {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
      @focusout="if (!event.currentTarget.contains(event.relatedTarget)) { open = false }"
    >
      <div class="wrn-next__row">
        <label id="{name}-label" for="{name}-trigger">{label}</label>
        <small data-show="showCounter" data-text="counterText()">{counterText()}</small>
      </div>

      <div class="wrn-next__select-control">
        <button
          {...attrs}
          id="{name}-trigger"
          type="button"
          class="wrn-next__select-trigger"
          role="combobox"
          aria-haspopup="listbox"
          aria-expanded="{open}"
          aria-controls="{name}-listbox"
          aria-labelledby="{name}-label"
          aria-invalid="{invalid}"
          disabled="{disabled}"
          @click="toggle()"
          @keydown="handleKeydown(event)"
        >
          <span class="wrn-next__select-value">
            <span data-show="!multiple" data-text="triggerText()">{triggerText()}</span>
            <span
              data-show="multiple && selectedTemplate === 'count' && selectedCount() > 0"
              data-text="selectedCount() + ' selected'"
            >{selectedCount()} selected</span>
            <span
              data-show="multiple && selectedTemplate === 'text' && selectedCount() > 0"
              data-text="selectedText()"
            >{selectedText()}</span>
            <span
              class="wrn-next__select-tags"
              aria-label="{selectedOptionsLabel}"
              data-show="multiple && selectedTemplate !== 'count' && selectedTemplate !== 'text' && selectedCount() > 0"
            >
              {#each allOptions() as option}
                <span data-show="isSelected(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}
                  {option.label}
                </span>
              {/each}
            </span>
            <span data-show="multiple && selectedCount() === 0">
              {#if placeholderIcon}<i class="{placeholderIcon}" aria-hidden="true"></i>{/if}
              <span class="wrn-next__placeholder">{placeholder}</span>
            </span>
          </span>
          <span
            class="wrn-next__select-chevron icon-[lucide--chevrons-up-down]"
            aria-hidden="true"
          ></span>
        </button>

        <button
          type="button"
          class="wrn-next__clear-select"
          data-show="clearable && allowEmpty && selectedCount() > 0 && !disabled"
          aria-label="{clearLabel}"
          title="{clearLabel}"
          @click="clearSelection(event)"
        >
          <span class="icon-[lucide--x]" aria-hidden="true"></span>
        </button>
      </div>

      <div
        class="wrn-next__select-dropdown {fixed ? 'wrn-next__select-dropdown--fixed' : ''}"
        data-show="open"
        style="{open ? '' : 'display: none'}"
      >
          {#if searchable}
            <label class="wrn-next__select-search">
              <span class="wrn-visually-hidden">{searchPlaceholder}</span>
              <span class="wrn-next__search-icon" aria-hidden="true">⌕</span>
              <input
                type="search"
                value="{query}"
                placeholder="{searchPlaceholder}"
                autocomplete="off"
                @input="query = event.target.value; activeIndex = flatVisibleOptions().length ? 0 : -1"
                @keydown="handleKeydown(event)"
              />
            </label>
          {/if}

          <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"
              aria-multiselectable="{multiple}"
            >
              {#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)"
                        @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)"
                  @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}

              {#if !loading && !flatVisibleOptions().length}
                <div class="wrn-next__select-message">{emptyLabel}</div>
              {/if}
            </div>

          {#if tags && query && !allOptions().some((option) => option.label.toLowerCase() === query.toLowerCase())}
            <button type="button" class="wrn-next__create-option">{createLabel} “{query}”</button>
          {/if}
          <button
            type="button"
            class="wrn-next__load-more"
            data-wrn-select-load-more
            data-show="infinite && hasMore"
          >{loadMoreLabel}</button>
      </div>

      <input
        type="hidden"
        name="{name}"
        value="{multiple ? selectedValues.join(',') : selectedValue}"
        required="{required}"
        aria-describedby="{validationMessage ? `${name}-validation` : helpText ? `${name}-help` : ''}"
      />

      {#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__row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 1rem;
    }

    .wrn-next__select-search {
      position: relative;
      display: block;
    }

    .wrn-next__select-search input {
      padding-left: 2.25em;
    }

    .wrn-next__select-tags {
      display: flex;
      flex-wrap: wrap;
      gap: 0.4rem;
    }

    .wrn-next__select-tags > span {
      display: inline-flex;
      align-items: center;
      gap: 0.35rem;
      padding: 0.3rem 0.55rem;
      border-radius: 999px;
      color: var(--wrn-component-color);
      background: color-mix(in srgb, var(--wrn-component-color) 12%, transparent);
      font-size: 0.75em;
      font-weight: 700;
    }

    .wrn-next__select-tags img {
      width: 1.25rem;
      height: 1.25rem;
      border-radius: 50%;
      object-fit: cover;
    }

    .wrn-next__select-value {
      display: flex;
      min-width: 0;
      align-items: center;
      gap: 0.55em;
    }

    .wrn-next__placeholder {
      overflow: hidden;
      color: var(--wrn-color-muted);
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* 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;
        }
  }
}
