import { html, nothing, PropertyValues } from 'lit'
import { ifDefined } from 'lit/directives/if-defined.js'
import { customElement, property, state } from 'lit/decorators.js'
import { Ref, createRef, ref } from 'lit/directives/ref.js'
import { classMap } from 'lit/directives/class-map.js'
import { repeat } from 'lit/directives/repeat.js'
import { PktInputElement } from '@/base-elements/input-element'
import { PktOptionsSlotController } from '@/controllers/pkt-options-controller'
import { PktSlotController } from '@/controllers/pkt-slot-controller'
import specs from 'componentSpecs/combobox.json'

import '../input-wrapper'
import '../icon'
import '../tag'
import '../listbox'
import PktListbox from '../listbox'
import { TTagSkin } from '../tag'

export interface IPktComboboxOption {
  description?: string
  disabled?: boolean
  fulltext?: string
  label?: string
  prefix?: string
  selected?: boolean
  tagSkinColor?: TTagSkin
  userAdded?: boolean
  value: string
}

export interface IPktCombobox {
  allowUserInput?: boolean
  typeahead?: boolean
  disabled?: boolean
  displayValueAs?: string
  errorMessage?: string
  fullwidth?: boolean
  hasError?: boolean
  helptext?: string | null
  helptextDropdown?: string | null
  helptextDropdownButton?: string | null
  id?: string
  includeSearch?: boolean
  label?: string | null
  maxlength?: number | null
  minlength?: number | null
  multiple?: boolean
  name?: string
  optionalTag?: boolean
  optionalText?: string
  options?: IPktComboboxOption[]
  defaultOptions?: IPktComboboxOption[]
  placeholder?: string | null
  requiredTag?: boolean
  requiredText?: string
  searchPlaceholder?: string
  tagPlacement?: TPktComboboxTagPlacement | null
  tagText?: string | null
  value?: string | string[]
}

export type TPktComboboxTagPlacement = 'inside' | 'outside'

declare global {
  interface HTMLElementTagNameMap {
    'pkt-combobox': PktCombobox & HTMLSelectElement
  }
}

@customElement('pkt-combobox')
export class PktCombobox extends PktInputElement implements IPktCombobox {
  private helptextSlot: Ref<HTMLElement> = createRef()

  constructor() {
    super()
    this.optionsController = new PktOptionsSlotController(this)
    this.slotController = new PktSlotController(this, this.helptextSlot)
    this.slotController.skipOptions = true
  }

  // Props / Attributes
  @property({ type: String, reflect: true }) value: string | string[] = ''
  @property({ type: Array }) options: IPktComboboxOption[] = []
  @property({ type: Array }) defaultOptions: IPktComboboxOption[] = []
  @property({ type: Boolean }) allowUserInput: boolean = false
  @property({ type: Boolean }) typeahead: boolean = false
  @property({ type: Boolean }) includeSearch: boolean = false
  @property({ type: String }) searchPlaceholder: string = ''
  @property({ type: Boolean }) multiple: boolean = false
  @property({ type: Number }) maxlength: number | null = null
  @property({ type: String }) displayValueAs: string = specs.props.displayValueAs.default
  @property({ type: String }) tagPlacement: TPktComboboxTagPlacement | null = null

  // State
  @state() private _options: IPktComboboxOption[] = []
  @state() private _isOptionsOpen = false
  @state() private _value: string[] = []
  @state() private _userInfoMessage: string = ''
  @state() private _addValueText: string | null = null
  @state() private _maxIsReached: boolean = false
  @state() private _search: string = ''
  @state() private _inputFocus: boolean = false
  @state() private _editingSingleValue: boolean = false

  // Refs
  inputRef: Ref<HTMLInputElement> = createRef()
  arrowRef: Ref<HTMLButtonElement> = createRef()
  listboxRef: Ref<PktListbox> = createRef()
  focusRef: Ref<HTMLElement> = createRef()
  optionTagRef: Ref<HTMLElement> = createRef()

  // Lifecycle methods
  connectedCallback(): void {
    super.connectedCallback()

    document &&
      document.body.addEventListener('click', (e: MouseEvent) => {
        if (this._isOptionsOpen && !this.contains(e.target as Node)) {
          this.handleFocusOut(e)
        }
      })

    this._options = []

    // Deep clone defaultOptions into options, preserving userAdded options
    if (this.defaultOptions && this.defaultOptions.length) {
      const userAdded = this.options?.filter((opt) => opt.userAdded) || []
      this.options = [...userAdded, ...JSON.parse(JSON.stringify(this.defaultOptions))]
      this._options = [...this.options]
    }

    // If options are provided via the options slot, we need to extract them
    if (this.optionsController.nodes.length) {
      const options: IPktComboboxOption[] = []
      this.optionsController.nodes.forEach((node: Element) => {
        if (!node.textContent && !node.getAttribute('value')) return null
        const option: IPktComboboxOption = {
          value: node.getAttribute('value') || node.textContent || '',
          label: node.textContent || node.getAttribute('value') || '',
        }
        if (node.getAttribute('data-prefix')) {
          option.prefix = node.getAttribute('data-prefix') || undefined
        }
        if (node.getAttribute('tagskincolor')) {
          option.tagSkinColor = node.getAttribute('tagskincolor') as TTagSkin
        }
        if (node.getAttribute('description')) {
          option.description = node.getAttribute('description') || undefined
        }
        option.fulltext = option.value + option.label + (option.prefix || '')
        options.push(option)
      })
      if (options.length) {
        this.options = [...options]
        this._options = [...options]
      }
    }
  }

  updated(changedProperties: PropertyValues): void {
    if (changedProperties.has('_value')) {
      this.valueChanged(this._value, changedProperties.get('_value') as string[])
    }
    if (changedProperties.has('value')) {
      this._value = Array.isArray(this.value) ? this.value : this.value ? this.value.split(',') : []
      if (!this.multiple && this._value.length > 1) {
        this._value = [this._value[0]]
      }
      this.isMaxItemsReached()
    }

    // If defaultOptions changed, update options (preserving userAdded)
    if (changedProperties.has('defaultOptions') && this.defaultOptions.length) {
      const userAdded = this.options?.filter((opt) => opt.userAdded) || []
      this.options = [...userAdded, ...JSON.parse(JSON.stringify(this.defaultOptions))]
      this._options = [...this.options]
    }

    if (changedProperties.has('options') && this.options.length) {
      // If options change, we need to update _options, but we need to preserve userAdded values
      const userAddedValues = this._options.filter((option) => option.userAdded)
      // Filter out userAddedValues that are overridden by this.options
      const filteredUserAdded = userAddedValues.filter(
        (userOpt) => !this.options.some((opt) => opt.value === userOpt.value),
      )
      // Merge, giving precedence to this.options
      this._options = [...filteredUserAdded, ...this.options]
      this._options.forEach((option) => {
        if (option.value && !option.label) {
          option.label = option.value
        }
        if (option.selected && !this._value.includes(option.value)) {
          const oldValue = [...this._value]
          this._value = [...this._value, option.value]
          this.valueChanged(this._value, oldValue)
        }
        option.fulltext = option.value + option.label + (option.prefix || '')
        option.selected = option.selected || this._value.includes(option.value)
      })
    }
    if (changedProperties.has('_search')) {
      this.dispatchEvent(
        new CustomEvent('search', {
          detail: this._search,
          bubbles: false,
        }),
      )
    }
    super.updated(changedProperties)
  }

  attributeChangedCallback(name: string, _old: string | null, value: string | null): void {
    if (name === 'value') {
      this._value = Array.isArray(this.value) ? this.value : this.value ? this.value.split(',') : []
      if (!this.multiple && this._value.length > 1) {
        this._value = [this._value[0]]
      }
    }
    if (name === 'options') {
      this._options = this.options
      this._options.forEach((option) => {
        if (option.value && !option.label) {
          option.label = option.value
        }
        if (option.selected && !this._value.includes(option.value)) {
          this._value = [...this._value, option.value]
        }
        option.fulltext = option.value + option.label + (option.prefix || '')
      })
      this._search = ''
    }
    super.attributeChangedCallback(name, _old, value)
  }

  // Render methods
  render() {
    return html`
      <pkt-input-wrapper
        .label=${this.label}
        .helptext=${this.helptext}
        .helptextDropdown=${ifDefined(this.helptextDropdown)}
        .helptextDropdownButton=${ifDefined(this.helptextDropdownButton)}
        ?fullwidth=${this.fullwidth}
        ?hasError=${this.hasError}
        ?inline=${this.inline}
        ?disabled=${this.disabled}
        .errorMessage=${this.errorMessage}
        ?optionalTag=${this.optionalTag}
        .optionalText=${this.optionalText}
        ?requiredTag=${this.requiredTag}
        .requiredText=${this.requiredText}
        .tagText=${this.tagText}
        ?useWrapper=${this.useWrapper}
        .forId=${this.allowUserInput || this.typeahead ? this.id + '-input' : this.id + '-arrow'}
        class="pkt-combobox__wrapper"
        @labelClick=${this.handleInputClick}
      >
        <div class="pkt-contents" ${ref(this.helptextSlot)} name="helptext" slot="helptext"></div>
        <div class="pkt-combobox" @focusout=${this.handleFocusOut}>
          <div
            class=${classMap({
              'pkt-combobox__input': true,
              'pkt-combobox__input--fullwidth': this.fullwidth,
              'pkt-combobox__input--open': this._isOptionsOpen,
              'pkt-combobox__input--error': this.hasError,
              'pkt-combobox__input--disabled': this.disabled,
            })}
            tabindex="-1"
            @click=${this.handleInputClick}
          >
            ${this.placeholder &&
            (!this._value.length || (this.multiple && this.tagPlacement == 'outside')) &&
            !this._inputFocus
              ? html`<span class="pkt-combobox__placeholder">${this.placeholder}</span>`
              : this.tagPlacement !== 'outside'
                ? this.renderSingleOrMultipleValues()
                : nothing}
            ${this.renderInputField()}
            <div
              class="pkt-btn pkt-btn--tertiary pkt-combobox__arrow"
              @click=${this.handleArrowClick}
              @keydown=${this.handleArrowClick}
              id="${this.id}-arrow"
              ${ref(this.arrowRef)}
              aria-expanded=${this._isOptionsOpen}
              aria-controls="${this.id}-listbox"
              aria-haspopup="listbox"
              aria-label="Åpne liste"
              ?disabled=${this.disabled}
              ?data-disabled=${this.disabled}
              role="button"
              tabindex="${this.disabled ? '-1' : '0'}"
            >
              <pkt-icon
                class=${classMap({
                  'pkt-combobox__arrow-icon': true,
                  'pkt-combobox__arrow-icon--open': this._isOptionsOpen,
                })}
                name="chevron-thin-down"
              ></pkt-icon>
            </div>
            <div ${ref(this.focusRef)} tabindex="-1" @keydown=${this.handleArrowClick}></div>
          </div>

          <pkt-listbox
            id="${this.id}-listbox"
            .options=${this._options}
            .isOpen=${this._isOptionsOpen}
            .searchPlaceholder=${this.searchPlaceholder}
            .label="Liste: ${this.label || ''}"
            ?includeSearch=${this.includeSearch}
            ?isMultiSelect=${this.multiple}
            ?allowUserInput=${this.allowUserInput && !this._maxIsReached}
            ?maxIsReached=${this._maxIsReached}
            .customUserInput=${ifDefined(this._addValueText)}
            .userMessage=${this._userInfoMessage}
            @search=${this.handleSearch}
            @option-toggle=${this.handleOptionToggled}
            @select-all=${this.addAllOptions}
            @close-options=${() => (this._isOptionsOpen = false)}
            .searchValue=${this._search || null}
            .maxLength=${this.maxlength || 0}
            ${ref(this.listboxRef)}
          ></pkt-listbox>
        </div>

        ${this.tagPlacement === 'outside' && this.multiple
          ? html`<div class="pkt-combobox__tags-outside">
              ${this.renderSingleOrMultipleValues()}
            </div>`
          : nothing}
      </pkt-input-wrapper>
    `
  }

  renderInputField() {
    return this.typeahead || this.allowUserInput
      ? html`
          <div class="pkt-combobox__input-div combobox__input">
            <input
              type="text"
              id="${this.id}-input"
              name=${(this.name || this.id) + '-input'}
              @input=${this.handleInput}
              @keydown=${this.handleInputKeydown}
              @focus=${this.handleFocus}
              @blur=${this.handleBlur}
              autocomplete="off"
              role="combobox"
              aria-label=${ifDefined(this.label)}
              aria-autocomplete=${this.typeahead ? 'both' : 'list'}
              aria-controls="${this.id}-listbox"
              aria-multiselectable=${ifDefined(this.multiple ? 'true' : undefined)}
              aria-activedescendant=${ifDefined(
                this._value[0] && !!this.findValueInOptions(this._value[0])
                  ? `${this.id}-listbox-${this.findIndexInOptions(this._value[0])}`
                  : undefined,
              )}
              ${ref(this.inputRef)}
            />
          </div>
        `
      : html`
          <input
            type="hidden"
            id="${this.id}-input"
            name=${(this.name || this.id) + '-input'}
            .value=${this._value.join(',')}
            ${ref(this.inputRef)}
          />
        `
  }

  renderSingleOrMultipleValues() {
    const isSingleValueDisplay = !this.multiple

    // enkeltverdi som tekst
    const singleValueContent = !this._editingSingleValue
      ? this.renderValueTag(this.findValueInOptions(this._value[0]))
      : null

    // Multiple vises som tags
    const multipleValuesContent = repeat(
      this._value,
      (value: string) => value,
      (value: string) => {
        const option = this.findValueInOptions(value)
        const tagSkinColor = this.options.find((o) => o.value === value)?.tagSkinColor
        return html`
          <pkt-tag
            skin=${tagSkinColor || 'blue-dark'}
            ?closeTag=${!this.disabled}
            @close=${() => this.handleTagRemove(value)}
          >
            ${this.renderValueTag(option)}
          </pkt-tag>
        `
      },
    )

    return isSingleValueDisplay ? singleValueContent : multipleValuesContent
  }

  renderValueTag(option: IPktComboboxOption | null) {
    if (!option) return ''
    switch (this.displayValueAs) {
      case 'prefixAndValue':
        return html`<span data-focusfix=${this.id}>${option.prefix || ''} ${option.value}</span>`
      case 'value':
        return html`<span data-focusfix=${this.id}>${option.value}</span>`
      case 'label':
      default:
        return html`<span data-focusfix=${this.id}>${option.label || option.value}</span>`
    }
  }

  // Event handlers

  handleInput(e: InputEvent): void {
    e.stopPropagation()
    e.stopImmediatePropagation()

    if (this.disabled) return

    this.touched = true
    const input = e.target as HTMLInputElement
    this._search = input.value
    this.checkForMatches()

    if (this.typeahead) {
      if (this._search) {
        this._options = this.options.filter((option) =>
          option.fulltext?.toLowerCase().includes(this._search.toLowerCase()),
        )

        if (e.inputType !== 'deleteContentBackward') {
          const matchingOptions = this._options.filter(
            (option) =>
              !option.selected &&
              option.label?.toLowerCase().startsWith(this._search.toLowerCase()),
          )

          if (
            matchingOptions.length > 0 &&
            this.inputRef.value &&
            this.inputRef.value.type !== 'hidden'
          ) {
            const match = matchingOptions[0]

            if (match?.label) {
              input.value = match.label
              window.setTimeout(
                () => input.setSelectionRange(this._search.length, input.value.length),
                0,
              )
              input.selectionDirection = 'backward'
            }
          }
        }
      } else {
        this._options = [...this.options]
      }
    }
  }

  private handleFocus(): void {
    if (this.disabled) return

    if (
      !this.multiple &&
      this._value[0] &&
      this.inputRef.value &&
      this.inputRef.value.type !== 'hidden'
    ) {
      const option = this.findValueInOptions(this._value[0])
      this._editingSingleValue = true
      this.inputRef.value.value =
        this.displayValueAs === 'label' && option?.label ? option.label : this._value[0]
    }
    this._inputFocus = true
    this._search = ''
    this._options = [...this.options]
    this._isOptionsOpen = true
    this.onFocus()

    this.requestUpdate() // Ensure the UI updates
  }

  private handleFocusOut(e: FocusEvent): void {
    if (this.disabled) return

    // Triggered when focus completely leaves the combobox and its children
    if (
      (e.relatedTarget as Element)?.closest('pkt-combobox')?.id !== this.id &&
      (e.relatedTarget as Element)?.closest('pkt-combobox')?.id !== this.id &&
      (e.target as Element)?.getAttribute('data-focusfix') !== this.id &&
      e.relatedTarget !== this.focusRef.value &&
      e.relatedTarget !== this.inputRef.value &&
      e.relatedTarget !== this.arrowRef.value &&
      this._isOptionsOpen
    ) {
      this._inputFocus = false
      this._addValueText = null
      this._userInfoMessage = ''
      this._search = ''

      // If value in text input, check if it should be added
      if (
        this.inputRef.value &&
        this.inputRef.value.type !== 'hidden' &&
        this.inputRef.value.value !== ''
      ) {
        const val = this.inputRef.value.value
        const valInOptions = this.findValueInOptions(val)
        if (!this._value.includes(val) && !valInOptions) {
          if (this.allowUserInput) {
            this.addNewUserValue(val)
          } else if (!this.multiple) {
            this.removeValue(this._value[0])
          }
        } else if (valInOptions && !this._value.includes(valInOptions.value)) {
          this.setSelected(valInOptions.value)
        }
        this.inputRef.value.value = ''
      }
      this._isOptionsOpen = false
      this.onBlur()
    }
  }

  private handleBlur(): void {
    this._inputFocus = false
    this._editingSingleValue = false
    this.onBlur()
  }

  private handleInputClick(e: MouseEvent): void {
    if (this.disabled) return

    if (
      e.currentTarget &&
      e.currentTarget !== this.arrowRef.value &&
      this.inputRef.value?.type !== 'hidden'
    ) {
      this.inputRef.value?.focus()
    } else {
      this.handleArrowClick(e)
    }
  }

  private handleArrowClick(e: MouseEvent | KeyboardEvent): void {
    if (this.disabled) return

    if (e instanceof KeyboardEvent && e.key) {
      if (e.key !== 'Enter' && e.key !== ' ' && e.key !== 'ArrowDown') return
    }

    e.stopImmediatePropagation()
    e.preventDefault()

    this._isOptionsOpen = !this._isOptionsOpen

    if (this._isOptionsOpen) {
      this.listboxRef.value?.focusFirstOrSelectedOption()
    } else {
      this.arrowRef.value?.focus()
    }
  }

  private handleOptionToggled(e: CustomEvent) {
    this.toggleValue(e.detail)
  }

  private handleSearch(e: CustomEvent) {
    e.stopPropagation()
    this._search = e.detail.toLowerCase()
  }

  private handleInputKeydown(e: KeyboardEvent): void {
    switch (e.key) {
      case ',':
      case 'Enter':
        e.preventDefault()
        this.addValue()
        break
      case 'Backspace':
        if (!this._search && this.inputRef.value?.type === 'hidden') this.removeLastValue(e)
        break
      case 'Tab':
      case 'ArrowDown':
        if (!e.shiftKey) {
          this.listboxRef.value?.focusFirstOrSelectedOption()
          e.preventDefault()
        }
        break
      case 'Escape':
        this._isOptionsOpen = false
        this.arrowRef.value?.focus() // Return focus to the button
        e.preventDefault()
        break
      default:
        break
    }
  }

  private handleTagRemove(value: string | null): void {
    this.removeSelected(value)
  }

  private blurInput(): void {
    if (this.inputRef.value && this.inputRef.value.matches(':focus')) {
      this.inputRef.value.blur()
    }
  }

  private checkForMatches() {
    // sjekker om verdiene bruker skriver inn finnes, er valgt eller kan legges til
    //setter riktig infomelding til bruker
    const inputValue = this.inputRef.value?.value || this._search || ''
    const searchValue = inputValue.trim().toLowerCase() || ''

    if (!searchValue) {
      if (!this.multiple && this._value[0]) {
        this.removeValue(this._value[0])
      }

      this.resetComboboxInput(false)
      return
    }

    const matchedValues = this._value.find((value) => value.toLowerCase() === searchValue)
    const matchedOptions: IPktComboboxOption[] = this._options.filter(
      (option) => option.label?.toLowerCase().includes(searchValue) ?? false,
    )
    const matchedOption = matchedOptions.find(
      (option) =>
        option.label?.toLowerCase() === searchValue || option.value.toLowerCase() === searchValue,
    )

    // sett riktig infomelding til bruker
    switch (true) {
      case (matchedOptions.length === 0 || !matchedOption) && this.allowUserInput:
        this._addValueText = inputValue
        this._userInfoMessage = ''
        break

      case matchedOptions.length === 0 && !this.allowUserInput:
        this._addValueText = null
        this._userInfoMessage = 'Ingen match i søket'
        break

      case !!matchedValues:
        this._addValueText = null
        this._userInfoMessage = 'Verdien er allerede valgt'
        break

      case matchedOptions.length > 1:
        this._addValueText = null
        this._userInfoMessage = ''
        break

      default:
        this._addValueText = null
        this._userInfoMessage = '' // Default for å fjerne melding
    }
  }

  private findValueInOptions(value: string | null): IPktComboboxOption | null {
    return (
      this.options.find((option) => {
        return option.value === value || option.label === value
      }) || null
    )
  }

  private findIndexInOptions(value: string | null): number {
    return this._options.findIndex((option) => {
      return option.value === value || option.label === value
    })
  }

  private isMaxItemsReached(): boolean {
    const isReached = this.maxlength !== null && this._value.length >= this.maxlength
    if (!isReached) {
      this._maxIsReached = false
    } else {
      this._maxIsReached = true
    }
    return isReached
  }

  public toggleValue(value: string | null): void {
    if (this.disabled) return

    this.touched = true
    this._userInfoMessage = ''
    this._addValueText = null

    const valueFromOptions: string | null = this.findValueInOptions(value)?.value || null
    const isSelected: boolean = this._value.includes(value || valueFromOptions || '')
    const isInOption: boolean = !!valueFromOptions
    const isDisabled: boolean = this._options.find((o) => o.value === value)?.disabled || false
    const isEmpty: boolean = !value?.trim()
    const isSingle: boolean = !this.multiple
    const isMultiple: boolean = this.multiple
    const isMaxItemsReached: boolean = this.isMaxItemsReached()

    let shouldOptionsBeOpen: boolean = false
    let shouldResetInput: boolean = true
    let userInfoMessage: string | null = ''
    let searchValue: string | null = ''

    if (isDisabled) return

    // Dersom ikke i listen og allowUserInput er true
    if (!isInOption && this.allowUserInput && !isEmpty) {
      this.addNewUserValue(value)
      userInfoMessage = 'Ny verdi lagt til'
      shouldOptionsBeOpen = !isMultiple
    }

    // Dersom ikke i listen men allowUserInput er false
    else if (!isInOption && !this.allowUserInput) {
      if (isSingle && this._value[0]) {
        this.removeValue(this._value[0])
      }
      shouldResetInput = false
      shouldOptionsBeOpen = true
      userInfoMessage = 'Ingen treff i søket'
    }

    // Dersom verdien er valgt allerede
    else if (isSelected) {
      this.removeValue(valueFromOptions)
      shouldOptionsBeOpen = true
    }

    // Dersom verdien er en tom streng, og det er enkeltvalg
    else if (isEmpty && isSingle) {
      this.removeAllSelected()
      shouldOptionsBeOpen = true
    }

    // Dersom det er enkeltvalg
    else if (isSingle) {
      this._value[0] && this.removeSelected(this._value[0])
      this.setSelected(valueFromOptions)
      shouldOptionsBeOpen = false
      if (this.inputRef.value && this.inputRef.value.type !== 'hidden') {
        this.inputRef.value.value = ''
        this.inputRef.value.blur()
      }
    }

    // Dersom det er flervalg og mulig å legge til fler
    else if (isMultiple && !isMaxItemsReached) {
      this.setSelected(valueFromOptions)
      shouldOptionsBeOpen = true
    }

    // Dersom det er flervalg og maks antall er nådd
    else if (isMultiple && isMaxItemsReached) {
      this._userInfoMessage = 'Maks antall valg nådd'
      shouldResetInput = false
      searchValue = value
    }

    // Dersom ingen av de over passer
    else {
      isSingle && this.removeAllSelected()
      this._userInfoMessage = 'Ingen gyldig verdi valgt'
      shouldResetInput = false
      shouldOptionsBeOpen = true
      searchValue = value
    }

    this._isOptionsOpen = shouldOptionsBeOpen
    if (!shouldOptionsBeOpen) {
      window.setTimeout(() => {
        this.focusRef.value?.focus()
      }, 0)
    }
    this._userInfoMessage = userInfoMessage
    this._search = searchValue || ''
    this.resetComboboxInput(shouldResetInput)
    isMultiple && this.isMaxItemsReached()
  }

  private setSelected(value: string | null): void {
    if (this._value.includes(value as string)) return

    if (this.multiple && this.isMaxItemsReached()) {
      this._userInfoMessage = 'Maks antall valg nådd'
      return
    }

    !this.multiple && this.removeAllSelected()

    this._value = value ? [...this._value, value] : this._value
    this._options = this._options.map((option) => {
      if (option.value === value) {
        option.selected = true
      }
      return option
    })

    this.resetComboboxInput(true)
  }

  private removeSelected(value: string | null): void {
    if (!value) return
    this._value = this._value.filter((v) => v !== value)
    const _opt = this.findValueInOptions(value)
    if (_opt) {
      _opt.selected = false
      if (_opt.userAdded) {
        this._options = [...this._options.filter((o) => o.value !== value)]
        this.options = [...this.options.filter((o) => o.value !== value)]
      } else {
        this._options = [...this._options, _opt]
      }
    } else if (!value && !this.multiple) {
      this._options = this._options.map((option) => {
        option.selected = false
        return option
      })
    }
  }

  private addAllOptions(): void {
    if (!this.multiple) return
    if (this.maxlength && this._options.length > this.maxlength) {
      this._userInfoMessage = 'For mange valgt'
      return
    }

    this._value = this._options.map((option) => option.value)
    this._options = this._options.map((option) => {
      option.selected = true
      return option
    })
    this.requestUpdate()
  }

  private removeAllSelected(): void {
    this._value = []
    this._options = this._options.map((option) => {
      option.selected = false
      return option
    })
    this._options = this._options.filter((option) => !option.userAdded)
    this.requestUpdate()
  }

  private addValue(): void {
    const input = this.inputRef.value?.value.trim() || ''
    this._search = input
    this.toggleValue(input)
  }

  private removeValue(value: string | null): void {
    this._value = this.multiple ? this._value.filter((v) => v !== value) : []
    this.removeSelected(value)
  }

  private addNewUserValue(value: string | null): void {
    if (!value || value.trim() === '') return

    if (!this.multiple) {
      this._value[0] && this.removeSelected(this._value[0])
      this._value = [value]
      this._isOptionsOpen = false
      this.blurInput()
    } else if (!this.findValueInOptions(value)) {
      if (this.isMaxItemsReached()) return
      this._value = [...this._value, value]
    }

    const newOption: IPktComboboxOption = { value, label: value, userAdded: true }

    this.options = [newOption, ...this.options]
    this._options = [newOption, ...this._options]
    this.setSelected(value)
    this.requestUpdate()
  }

  private resetComboboxInput(shouldResetInput: boolean = true): void {
    this._addValueText = null
    if (this.inputRef.value && this.inputRef.value.type !== 'hidden' && shouldResetInput) {
      this._search = ''
      if (this.multiple) {
        this.inputRef.value.value = ''
      } else {
        const option = this.findValueInOptions(this._value[0])
        window.setTimeout(() => {
          if (!this.inputRef.value || this.inputRef.value.type === 'hidden') return
          this.inputRef.value.value =
            this.displayValueAs === 'label' && option?.label ? option.label : this._value[0] || ''
        }, 0)
        this._userInfoMessage = ''
      }
    }
    this._options = [...this.options]
  }

  private removeLastValue(e: Event): void {
    if (this._value.length === 0) return

    e.preventDefault()

    const val = this._value[this._value.length - 1]
    val && this.removeSelected(val)

    this.isMaxItemsReached()
  }
}
