<template>
  <div
    :class="dropdownOnly ? 'eg-select-dropdown' : 'eg-select-wrapper'"
    :style="dropdownOnly ? overlayStyle : null"
    @mousedown="onRootMouseDown"
    @scroll.passive="dropdownOnly && _hideTooltip()"
  >
    <!-- Standalone mode: trigger button -->
    <div
      v-if="!dropdownOnly"
      class="eg-select-trigger"
      :class="{ 'eg-select-trigger--disabled': disabled, 'eg-select-trigger--open': open }"
      @click.stop="toggle"
    >
      <span v-if="selectedOpt && selectedOpt.color" class="eg-select-dot" :style="{ background: selectedOpt.color }"></span>
      <span
        class="eg-select-trigger-label"
        :class="{ 'eg-select-placeholder': !selectedOpt }"
        @mouseenter="_showTooltip(selectedOpt && selectedOpt.tooltip, $event.currentTarget)"
        @mouseleave="_hideTooltip"
      >
        {{ selectedOpt ? selectedOpt.label : placeholder }}
      </span>
      <span class="eg-select-caret">▾</span>
    </div>

    <!-- Options panel in grid dropdown-only mode (root IS the dropdown panel) -->
    <template v-if="dropdownOnly">
      <div v-if="enableSearch" class="eg-select-search" @mousedown.stop>
        <input
          ref="searchInput"
          class="eg-select-search-input"
          :value="searchQuery"
          placeholder="Qidirish..."
          autocomplete="off"
          autocorrect="off"
          autocapitalize="off"
          spellcheck="false"
          @input="onSearch"
          @keydown.stop="onSearchKey"
        />
      </div>
      <div
        v-for="(opt, idx) in filteredOptions"
        :key="String(opt.value)"
        class="eg-select-option"
        :class="{
          'eg-select-option--active': String(opt.value) === String(value),
          'eg-select-option--highlighted': idx === highlight
        }"
        @mousedown.prevent="pickOption(opt)"
        @mouseover="highlight = idx"
        @mouseenter="_showTooltip(opt.tooltip, $event.currentTarget)"
        @mouseleave="_hideTooltip"
      >
        <span v-if="opt.color" class="eg-select-dot" :style="{ background: opt.color }"></span>
        {{ opt.label }}
      </div>
      <div v-if="!filteredOptions.length" class="eg-select-empty">{{ t.noOptions }}</div>
      <div v-if="clearable && selectedOpt" class="eg-select-footer">
        <button class="eg-select-btn eg-select-btn--clear" tabindex="-1" @mousedown.prevent="pickClear">{{ t.clear }}</button>
      </div>
    </template>

    <!-- Options panel in standalone mode (floating dropdown below trigger) -->
    <div
      v-else-if="open"
      class="eg-select-dropdown eg-select-dropdown--floating"
      @mousedown.stop.prevent
      @scroll.passive="_hideTooltip"
    >
      <div v-if="enableSearch" class="eg-select-search" @mousedown.stop>
        <input
          ref="searchInput"
          class="eg-select-search-input"
          :value="searchQuery"
          placeholder="Qidirish..."
          autocomplete="off"
          autocorrect="off"
          autocapitalize="off"
          spellcheck="false"
          @input="onSearch"
          @keydown.stop="onSearchKey"
        />
      </div>
      <div
        v-for="(opt, idx) in filteredOptions"
        :key="String(opt.value)"
        class="eg-select-option"
        :class="{
          'eg-select-option--active': String(opt.value) === String(value),
          'eg-select-option--highlighted': idx === highlight
        }"
        @mousedown.prevent="pickOption(opt)"
        @mouseover="highlight = idx"
        @mouseenter="_showTooltip(opt.tooltip, $event.currentTarget)"
        @mouseleave="_hideTooltip"
      >
        <span v-if="opt.color" class="eg-select-dot" :style="{ background: opt.color }"></span>
        {{ opt.label }}
      </div>
      <div v-if="!filteredOptions.length" class="eg-select-empty">{{ t.noOptions }}</div>
      <div v-if="clearable && selectedOpt" class="eg-select-footer">
        <button class="eg-select-btn eg-select-btn--clear" tabindex="-1" @mousedown.prevent="pickClear">{{ t.clear }}</button>
      </div>
    </div>

    <div v-if="hoverTooltip" class="eg-option-tooltip" :style="hoverTooltipStyle">{{ hoverTooltip.text }}</div>
  </div>
</template>

<script>
// Approximate rendered height of one .eg-select-option row per grid `size`,
// matching the .eg-size-{small,large}/.eg-size-middle (default) CSS overrides
// below — used only to estimate whether the dropdown fits below the trigger
// before it's actually rendered (see overlayStyle).
const OPTION_ROW_H = { small: 22, middle: 28, large: 33 }

import { getMessages } from '../utils/i18n.js'

export default {
  name: 'EgSelect',
  model: { prop: 'value', event: 'input' },
  props: {
    value: { default: null },
    options: { type: Array, default: () => [] },
    // Field names to read value/label from when an option is a plain object
    // instead of a string — mirrors EgTreeSelect's valueKey/labelKey so
    // col.optionValueKey/optionLabelKey work the same way for type:'select'.
    valueKey: { type: String, default: 'value' },
    labelKey: { type: String, default: 'label' },
    enableSearch: { type: Boolean, default: false },
    placeholder: { type: String, default: '' },
    disabled: { type: Boolean, default: false },
    dropdownOnly: { type: Boolean, default: false },
    triggerRect: { type: Object, default: null },
    size: { type: String, default: 'middle' },
    locale: { type: String, default: 'en' },
    clearable: { type: Boolean, default: true }
  },
  data() {
    return {
      open: false,
      searchQuery: '',
      highlight: -1,
      // True right after Arrow/Page keyboard navigation — lets Space commit the
      // highlighted option instead of typing a literal space into the search box.
      // Reset on every text edit so normal multi-word search typing is unaffected.
      _navigatedByKeyboard: false,
      // { text, rect } while a themed option tooltip should be visible — see
      // _showTooltip/_hideTooltip. rect is a snapshot from getBoundingClientRect()
      // at the moment the hover delay elapsed.
      hoverTooltip: null,
      _tooltipTimer: null
    }
  },
  computed: {
    t() { return getMessages(this.locale) },
    normalizedOptions() {
      const vk = this.valueKey
      const lk = this.labelKey
      return (this.options || []).map(o => {
        if (typeof o === 'string') return { value: o, label: o, tooltip: o }
        const label = o[lk] != null ? o[lk] : String(o[vk])
        return { value: o[vk], label, color: o.color, tooltip: o.tooltip != null ? o.tooltip : label }
      })
    },
    filteredOptions() {
      if (!this.enableSearch || !this.searchQuery.trim()) return this.normalizedOptions
      const q = this.searchQuery.trim().toLowerCase()
      return this.normalizedOptions.filter(o => String(o.label || '').toLowerCase().includes(q))
    },
    selectedOpt() {
      const v = String(this.value == null ? '' : this.value)
      return this.normalizedOptions.find(o => String(o.value) === v) || null
    },
    overlayStyle() {
      if (!this.dropdownOnly || !this.triggerRect) return { position: 'fixed', zIndex: 9999 }
      const rect = this.triggerRect
      const searchH = this.enableSearch ? 42 : 0
      const rowH = OPTION_ROW_H[this.size] || OPTION_ROW_H.middle
      const approxH = Math.min(this.filteredOptions.length * rowH + 8 + searchH, 320)
      const spaceBelow = window.innerHeight - rect.bottom
      const top = spaceBelow >= approxH ? rect.bottom : rect.top - approxH
      return {
        position: 'fixed',
        top: top + 'px',
        left: rect.left + 'px',
        minWidth: Math.max(rect.width, 120) + 'px',
        maxWidth: Math.min(360, window.innerWidth - rect.left - 16) + 'px',
        zIndex: 9999
      }
    },
    hoverTooltipStyle() {
      if (!this.hoverTooltip) return {}
      const { rect, text } = this.hoverTooltip
      const estH = 20 + Math.ceil(text.length / 40) * 16
      const spaceBelow = window.innerHeight - rect.bottom
      const top = spaceBelow >= estH + 8 ? rect.bottom + 6 : rect.top - estH - 6
      const left = Math.min(rect.left, window.innerWidth - 280 - 8)
      return { position: 'fixed', top: top + 'px', left: Math.max(8, left) + 'px', zIndex: 10000 }
    }
  },
  watch: {
    open(val) {
      if (val && this.enableSearch) {
        this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })
      }
    }
  },
  mounted() {
    if (this.dropdownOnly) {
      const v = String(this.value == null ? '' : this.value)
      const selectedIndex = this.filteredOptions.findIndex(o => String(o.value) === v)
      this.highlight = selectedIndex
      this._scrollIndexIntoView(selectedIndex)
      if (this.enableSearch) {
        this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })
      }
    } else {
      document.addEventListener('click', this._onDocClick)
    }
  },
  beforeDestroy() {
    this._clearTooltipTimer()
    if (!this.dropdownOnly) {
      document.removeEventListener('click', this._onDocClick)
    }
  },
  methods: {
    toggle() {
      if (this.disabled) return
      this.open = !this.open
      if (this.open) {
        const v = String(this.value == null ? '' : this.value)
        this.highlight = this.normalizedOptions.findIndex(o => String(o.value) === v)
        this.searchQuery = ''
      }
    },
    pickOption(opt) {
      this.$emit('input', opt.value)
      this.$emit('select', { value: opt.value, label: opt.label })
      if (!this.dropdownOnly) {
        this.open = false
        this.searchQuery = ''
        this.highlight = -1
      }
    },
    // Reuses pickOption's write path (same input/select emit + dropdown-close
    // handling) — a clear is just "pick the empty option", not a separate flow.
    pickClear() {
      this.pickOption({ value: '', label: '' })
    },
    onSearch(e) {
      this._hideTooltip()
      this.searchQuery = e.target.value
      this.highlight = 0
      this._navigatedByKeyboard = false
    },
    onSearchKey(e) {
      const key = e.key
      const last = this.filteredOptions.length - 1
      if (key === 'Escape') {
        e.preventDefault()
        if (this.dropdownOnly) this.$emit('close')
        else { this.open = false; this.searchQuery = '' }
      } else if (key === 'ArrowDown') {
        e.preventDefault()
        this.highlight = Math.min((this.highlight < 0 ? -1 : this.highlight) + 1, last)
        this._navigatedByKeyboard = true
        this._scrollHighlightedIntoView()
      } else if (key === 'ArrowUp') {
        e.preventDefault()
        this.highlight = Math.max((this.highlight || 0) - 1, 0)
        this._navigatedByKeyboard = true
        this._scrollHighlightedIntoView()
      } else if (key === 'PageDown') {
        e.preventDefault()
        this.highlight = this.highlight < 0 ? 0 : Math.min(this.highlight + 8, last)
        this._navigatedByKeyboard = true
        this._scrollHighlightedIntoView()
      } else if (key === 'PageUp') {
        e.preventDefault()
        this.highlight = this.highlight < 0 ? 0 : Math.max(this.highlight - 8, 0)
        this._navigatedByKeyboard = true
        this._scrollHighlightedIntoView()
      } else if (key === 'Enter') {
        e.preventDefault()
        const opt = this.filteredOptions[this.highlight]
        if (opt) this.pickOption(opt)
      } else if (key === ' ' && this._navigatedByKeyboard) {
        // Space commits the option reached via Arrow/Page navigation instead of
        // typing a literal space — a plain (non-navigated) space still types normally.
        e.preventDefault()
        const opt = this.filteredOptions[this.highlight]
        if (opt) this.pickOption(opt)
      }
    },
    handleKey(e) {
      const key = e.key
      const last = this.filteredOptions.length - 1
      if (key === 'Escape') {
        e.preventDefault()
        this.$emit('close')
      } else if (key === 'Enter' || key === ' ') {
        e.preventDefault()
        const opt = this.filteredOptions[this.highlight]
        if (opt) this.pickOption(opt)
        else this.$emit('close')
      } else if (key === 'ArrowDown') {
        e.preventDefault()
        this.highlight = Math.min((this.highlight < 0 ? -1 : this.highlight) + 1, last)
        this._scrollHighlightedIntoView()
      } else if (key === 'ArrowUp') {
        e.preventDefault()
        this.highlight = Math.max((this.highlight || 0) - 1, 0)
        this._scrollHighlightedIntoView()
      } else if (key === 'PageDown') {
        e.preventDefault()
        this.highlight = this.highlight < 0 ? 0 : Math.min(this.highlight + 8, last)
        this._scrollHighlightedIntoView()
      } else if (key === 'PageUp') {
        e.preventDefault()
        this.highlight = this.highlight < 0 ? 0 : Math.max(this.highlight - 8, 0)
        this._scrollHighlightedIntoView()
      }
    },
    _scrollHighlightedIntoView() {
      this.$nextTick(() => {
        const items = this.$el.querySelectorAll('.eg-select-option')
        const el = items[this.highlight]
        if (!el) return
        const container = this.$el
        const searchEl = container.querySelector('.eg-select-search')
        const stickyH = searchEl ? searchEl.offsetHeight : 0
        const elTop = el.offsetTop
        const elBottom = elTop + el.offsetHeight
        const visTop = container.scrollTop + stickyH
        const visBottom = container.scrollTop + container.clientHeight
        if (elTop < visTop) {
          container.scrollTop = elTop - stickyH
        } else if (elBottom > visBottom) {
          container.scrollTop = elBottom - container.clientHeight
        }
      })
    },
    // Aligns option `idx` to the top of the visible list on initial open.
    // Setting scrollTop past the container's max is clamped by the browser,
    // so an option near the end of the list naturally ends up scrolled to
    // the bottom instead of leaving blank space below it.
    _scrollIndexIntoView(idx) {
      if (idx < 0) return
      this.$nextTick(() => {
        const items = this.$el.querySelectorAll('.eg-select-option')
        const el = items[idx]
        if (!el) return
        const container = this.$el
        const searchEl = container.querySelector('.eg-select-search')
        const stickyH = searchEl ? searchEl.offsetHeight : 0
        container.scrollTop = el.offsetTop - stickyH
      })
    },
    onRootMouseDown(e) {
      if (this.dropdownOnly) e.preventDefault()
    },
    _onDocClick(e) {
      if (!this.open) return
      if (this.$el && this.$el.contains(e.target)) return
      this.open = false
    },
    // Shared by option rows and the closed trigger label — shows a themed
    // popover (see .eg-option-tooltip) after a short hover delay, instead of
    // the browser's native title tooltip.
    _showTooltip(text, el) {
      this._clearTooltipTimer()
      if (!text) return
      const rect = el.getBoundingClientRect()
      this._tooltipTimer = setTimeout(() => {
        this.hoverTooltip = { text, rect }
      }, 400)
    },
    _hideTooltip() {
      this._clearTooltipTimer()
      this.hoverTooltip = null
    },
    _clearTooltipTimer() {
      if (this._tooltipTimer) {
        clearTimeout(this._tooltipTimer)
        this._tooltipTimer = null
      }
    }
  }
}
</script>

<style>
/* Standalone wrapper.
   Every custom property below is read as var(--eg-x, var(--eg-x-default)):
   --eg-x is an optional override slot a host/theme may set (only
   src/excel-grid.css's dark mode and custom host theming ever set the
   bare name); --eg-x-default is always defined on .eg-shell (ExcelGrid.vue)
   for every built-in theme. A bare var(--eg-x) with no fallback resolves to
   nothing in the (default, most common) case where only the "-default" var
   is set — and an unresolvable var() in a shorthand like `border` doesn't
   fall back to any other CSS rule, it makes the whole declaration compute
   to its initial value (no border at all). This component's dropdown is
   also duplicated (with the fallback already applied) in ExcelGrid.vue's
   own <style> for cascade-order reasons, so keep the token names in sync
   with that copy when changing tokens here. */
.eg-select-wrapper {
  position: relative;
  display: inline-block;
  min-width: 120px;
  font-size: var(--eg-font-size, var(--eg-font-size-default));
  font-family: inherit;
}
.eg-select-trigger {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  min-height: 30px;
  padding: 4px 8px;
  box-sizing: border-box;
  border: 1px solid var(--eg-color-border, var(--eg-color-border-default));
  border-radius: var(--eg-radius, var(--eg-radius-default));
  background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));
  cursor: pointer;
  user-select: none;
  color: var(--eg-color-text, var(--eg-color-text-default));
  transition: border-color .15s ease, box-shadow .15s ease;
}
.eg-select-trigger:hover {
  border-color: var(--eg-color-accent, var(--eg-color-accent-default));
}
.eg-select-trigger--open {
  border-color: var(--eg-color-accent, var(--eg-color-accent-default));
  box-shadow: 0 0 0 2px var(--eg-color-accent-soft, var(--eg-color-accent-soft-default));
}
.eg-select-trigger--disabled {
  background: var(--eg-color-cell-readonly, var(--eg-color-cell-readonly-default));
  cursor: default;
  color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));
}
.eg-select-trigger-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.eg-select-placeholder {
  color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));
}
.eg-select-caret {
  flex-shrink: 0;
  color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));
  font-size: 12px;
}

/* Dropdown panel */
.eg-select-dropdown {
  background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));
  border: 1px solid var(--eg-color-border, var(--eg-color-border-default));
  border-radius: var(--eg-radius, var(--eg-radius-default));
  box-shadow: var(--eg-shadow-dropdown, var(--eg-shadow-dropdown-default));
  overflow-y: auto;
  max-height: 320px;
  padding: 4px 0;
}
.eg-select-dropdown--floating {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 100%;
  z-index: 999;
  margin-top: 2px;
}

/* Search input inside dropdown */
.eg-select-search {
  padding: 6px 8px;
  border-bottom: 1px solid var(--eg-color-border-cell, var(--eg-color-border-cell-default));
  position: sticky;
  top: 0;
  background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));
  z-index: 1;
}
.eg-select-search-input {
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--eg-color-border, var(--eg-color-border-default));
  border-radius: var(--eg-radius, var(--eg-radius-default));
  padding: 4px 8px;
  font-size: var(--eg-font-size, var(--eg-font-size-default));
  outline: none;
  color: var(--eg-color-text, var(--eg-color-text-default));
  background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));
  font-family: inherit;
}
.eg-select-search-input:focus {
  border-color: var(--eg-color-accent, var(--eg-color-accent-default));
  box-shadow: 0 0 0 2px var(--eg-color-accent-soft, var(--eg-color-accent-soft-default));
}

/* Option rows */
.eg-select-option {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 6px 12px;
  font-size: var(--eg-font-size, var(--eg-font-size-default));
  cursor: pointer;
  white-space: normal;
  word-break: break-word;
  user-select: none;
  color: var(--eg-color-text, var(--eg-color-text-default));
}
.eg-select-option:hover,
.eg-select-option--highlighted {
  background: var(--eg-color-option-hover-bg, var(--eg-color-option-hover-bg-default));
}
.eg-select-option--active {
  font-weight: 600;
  color: var(--eg-color-accent, var(--eg-color-accent-default));
}
.eg-select-option--active.eg-select-option--highlighted,
.eg-select-option--active:hover {
  background: var(--eg-color-option-active-hover-bg, var(--eg-color-option-active-hover-bg-default));
}

/* Empty state — no options at all, or a search query matched nothing */
.eg-select-empty {
  padding: 14px 12px;
  text-align: center;
  font-size: var(--eg-font-size, var(--eg-font-size-default));
  color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));
  user-select: none;
}

/* Color dot */
.eg-select-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex-shrink: 0;
  display: inline-block;
  margin-top: 3px;
}

/* "Clear" footer — same recipe as EgDatePicker.vue's .eg-dp-footer/.eg-dp-btn,
   just a single right-aligned button (no "Today" equivalent here). Also
   duplicated in ExcelGrid.vue's own <style> — see the comment above
   .eg-select-wrapper there for why, and keep both copies token-identical. */
.eg-select-footer {
  display: flex;
  justify-content: flex-end;
  padding: 4px 6px 6px;
  border-top: 1px solid var(--eg-color-border, var(--eg-color-border-default));
}
.eg-select-btn {
  background: none;
  border: 1px solid var(--eg-color-border, var(--eg-color-border-default));
  border-radius: var(--eg-radius, var(--eg-radius-default));
  padding: 4px 10px;
  cursor: pointer;
  font-size: 12px;
  color: var(--eg-color-text, var(--eg-color-text-default));
}
.eg-select-btn:hover { background: var(--eg-color-option-hover-bg, var(--eg-color-option-hover-bg-default)); }
.eg-select-btn--clear { color: var(--eg-color-danger, #c62828); border-color: var(--eg-color-danger, #f1b0b0); }
.eg-select-btn--clear:hover { background: #fff0f0; }

/* Themed option/trigger tooltip — same recipe as .eg-select-dropdown
   (cell-bg/border/radius) plus .eg-validation-tooltip's shadow, so it reads
   as part of the grid's own chrome in every built-in theme instead of a
   generic OS tooltip box. */
.eg-option-tooltip {
  position: fixed;
  max-width: 280px;
  background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));
  border: 1px solid var(--eg-color-border, var(--eg-color-border-default));
  border-radius: var(--eg-radius, var(--eg-radius-default));
  box-shadow: var(--eg-shadow-tooltip, var(--eg-shadow-tooltip-default));
  color: var(--eg-color-text, var(--eg-color-text-default));
  font-size: var(--eg-font-size, var(--eg-font-size-default));
  padding: 6px 10px;
  pointer-events: none;
  white-space: normal;
  word-break: break-word;
  z-index: 10000;
}
</style>
