<script setup>
// ProductSearchSelect — rich article search box for order-line rows.
// The results panel is teleported to <body> with fixed positioning so it can
// never be clipped by scroll/overflow ancestors (quick-order modal body,
// line-table wrapper). Because of the teleport, ALL panel styles live in the
// non-scoped <style> block below (see the teleport + scoped CSS gotcha).
const props = defineProps({
  modelValue: { type: [String, Number], default: '' },
  display: { type: String, default: '' },          // selected product name
  options: { type: Array, default: () => [] },     // m_product records
  loading: { type: Boolean, default: false },
  disabled: { type: Boolean, default: false },
  placeholder: { type: String, default: '' },
  currency: { type: String, default: 'EUR' },
  autofocus: { type: Boolean, default: false }
})
const emit = defineEmits(['update:modelValue', 'open', 'search', 'select', 'clear'])

const config = useRuntimeConfig()
const { t } = useTranslation()

const rootEl = ref(null)
const inputEl = ref(null)
const panelEl = ref(null)

const isOpen = ref(false)
const query = ref('')
const typed = ref(false)
const activeIndex = ref(-1)
const panelStyle = ref({})

const inputValue = computed(() => (isOpen.value && typed.value) ? query.value : (props.display || ''))

const position = () => {
  const el = rootEl.value
  if (!el || typeof window === 'undefined') return
  const r = el.getBoundingClientRect()
  const vw = window.innerWidth
  const vh = window.innerHeight
  const width = Math.min(Math.max(r.width, 560), vw - 24)
  let left = r.left
  if (left + width > vw - 12) left = Math.max(12, vw - 12 - width)
  const spaceBelow = vh - r.bottom
  const openUp = spaceBelow < 340 && r.top > spaceBelow
  const maxHeight = Math.max(220, Math.min(440, (openUp ? r.top : spaceBelow) - 16))
  const style = {
    position: 'fixed',
    left: left + 'px',
    width: width + 'px',
    maxHeight: maxHeight + 'px',
    zIndex: 10050
  }
  if (openUp) style.bottom = (vh - r.top + 6) + 'px'
  else style.top = (r.bottom + 6) + 'px'
  panelStyle.value = style
}

const onDocPointerDown = (e) => {
  if (rootEl.value?.contains(e.target)) return
  if (panelEl.value?.contains(e.target)) return
  close()
}

const openPanel = () => {
  if (props.disabled || isOpen.value) return
  isOpen.value = true
  typed.value = false
  query.value = ''
  activeIndex.value = -1
  emit('open')
  nextTick(() => {
    position()
    document.addEventListener('pointerdown', onDocPointerDown, true)
    window.addEventListener('resize', position)
    window.addEventListener('scroll', position, true)
  })
}

const close = () => {
  if (!isOpen.value) return
  isOpen.value = false
  typed.value = false
  activeIndex.value = -1
  if (typeof document !== 'undefined') {
    document.removeEventListener('pointerdown', onDocPointerDown, true)
    window.removeEventListener('resize', position)
    window.removeEventListener('scroll', position, true)
  }
}

let debounceTimer = null
const onInput = (e) => {
  typed.value = true
  query.value = e.target.value
  activeIndex.value = 0
  clearTimeout(debounceTimer)
  debounceTimer = setTimeout(() => emit('search', query.value), 300)
}

const selectOpt = (opt) => {
  if (!opt) return
  emit('update:modelValue', (opt.id !== undefined && opt.id !== null) ? String(opt.id) : '')
  emit('select', opt)
  close()
  inputEl.value?.blur()
}

const onClear = () => {
  clearTimeout(debounceTimer)
  if (isOpen.value && typed.value && query.value) {
    // First press: clear the search text, back to the full list
    query.value = ''
    typed.value = false
    activeIndex.value = -1
    emit('search', '')
    inputEl.value?.focus()
    return
  }
  if (props.modelValue) emit('clear')
  query.value = ''
  typed.value = false
  inputEl.value?.focus()
  if (!isOpen.value) openPanel()
}

const onKeydown = (e) => {
  if (e.key === 'Escape') {
    // Swallow Escape while the panel is open so an enclosing modal
    // (OrderQuickAddModal listens on document) doesn't close too.
    if (isOpen.value) e.stopPropagation()
    close()
    return
  }
  if (e.key === 'Tab') { close(); return }
  if (!isOpen.value && (e.key === 'ArrowDown' || e.key === 'Enter')) { openPanel(); return }
  if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
    e.preventDefault()
    const n = props.options?.length || 0
    if (!n) return
    const dir = e.key === 'ArrowDown' ? 1 : -1
    activeIndex.value = ((activeIndex.value + dir) % n + n) % n
    nextTick(() => panelEl.value?.querySelector('.pss-item--active')?.scrollIntoView({ block: 'nearest' }))
  } else if (e.key === 'Enter') {
    e.preventDefault()
    const opt = props.options?.[activeIndex.value] ?? ((props.options?.length === 1) ? props.options[0] : null)
    if (opt) selectOpt(opt)
  }
}

const onFocus = (e) => {
  openPanel()
  // Select existing text so typing immediately starts a new search
  if (e?.target?.value) e.target.select()
}

watch(() => [props.loading, props.options], () => {
  if (!isOpen.value) return
  if (activeIndex.value >= (props.options?.length || 0)) {
    activeIndex.value = (props.options?.length || 0) ? 0 : -1
  }
  nextTick(position)
}, { deep: false })

onMounted(() => {
  if (props.autofocus) nextTick(() => inputEl.value?.focus())
})
onUnmounted(() => {
  clearTimeout(debounceTimer)
  close()
})

// ---- display helpers -------------------------------------------------------
const fmtMoney = new Intl.NumberFormat('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
const fmtQty = new Intl.NumberFormat('de-DE', { maximumFractionDigits: 2 })

const curSymbol = computed(() => {
  const c = String(props.currency || '').toUpperCase()
  return { EUR: '€', USD: '$', GBP: '£' }[c] || (props.currency || '€')
})

// M_ProductPrice is a single object when a pricelist version was resolved,
// otherwise still the raw expanded array — handle both.
const priceOf = (o) => {
  const p = o?.product_price ?? o?.M_ProductPrice?.PriceStd ?? o?.M_ProductPrice?.[0]?.PriceStd ?? null
  return (p === null || p === undefined || isNaN(Number(p))) ? null : Number(p)
}
const stockOf = (o) => Array.isArray(o?.M_Storage)
  ? o.M_Storage.reduce((acc, cur) => acc + Number(cur?.QtyOnHand ?? 0), 0)
  : null

const imgSrc = (g) => {
  const f = g?.formats?.small ?? g?.formats?.thumbnail
  return f ? (config.public.strapi + '/files-api/' + f.hash + f.ext) : ''
}

// Highlight matched substrings without v-html
const segments = (text) => {
  const s = String(text ?? '')
  const q = (typed.value ? query.value : '').trim()
  if (!q) return [{ text: s, hit: false }]
  const parts = []
  const lower = s.toLowerCase()
  const ql = q.toLowerCase()
  let i = 0
  while (true) {
    const idx = lower.indexOf(ql, i)
    if (idx === -1) { parts.push({ text: s.slice(i), hit: false }); break }
    if (idx > i) parts.push({ text: s.slice(i, idx), hit: false })
    parts.push({ text: s.slice(idx, idx + q.length), hit: true })
    i = idx + q.length
  }
  return parts.filter(p => p.text !== '')
}
</script>

<template>
  <div ref="rootEl" class="pss" :class="{ 'pss--open': isOpen, 'pss--disabled': disabled }">
    <div class="pss-control" @click="inputEl?.focus()">
      <i class="ti ti-search pss-icon"></i>
      <input
        ref="inputEl"
        class="pss-input"
        type="text"
        :value="inputValue"
        :placeholder="placeholder || ((t.search || 'Suchen') + ' — Name, Art.-Nr., SKU, EAN …')"
        :disabled="disabled"
        autocomplete="off"
        spellcheck="false"
        role="combobox"
        :aria-expanded="isOpen"
        @focus="onFocus"
        @input="onInput"
        @keydown="onKeydown"
      />
      <span v-if="loading && isOpen" class="pss-side-icon pss-spin"><i class="ti ti-loader-2"></i></span>
      <button
        v-else-if="modelValue || (isOpen && typed && query)"
        type="button"
        class="pss-clear"
        tabindex="-1"
        title="Leeren"
        @mousedown.prevent
        @click="onClear"
      ><i class="ti ti-x"></i></button>
      <span v-else class="pss-side-icon"><i class="ti ti-selector"></i></span>
    </div>

    <Teleport to="body">
      <div v-if="isOpen" ref="panelEl" class="pss-panel" :style="panelStyle">
        <div v-if="loading" class="pss-state">
          <i class="ti ti-loader-2 pss-spin-i"></i>
          <span>Artikel werden geladen …</span>
        </div>
        <div v-else-if="!options?.length" class="pss-state">
          <i class="ti ti-package-off"></i>
          <span>Keine Artikel gefunden<template v-if="typed && query"> für „{{ query }}“</template></span>
        </div>
        <ul v-else class="pss-list" role="listbox">
          <li
            v-for="(opt, i) in options"
            :key="opt.id ?? i"
            class="pss-item"
            :class="{ 'pss-item--active': i === activeIndex, 'pss-item--selected': String(opt.id) === String(modelValue) }"
            role="option"
            :aria-selected="String(opt.id) === String(modelValue)"
            @mousedown.prevent
            @click="selectOpt(opt)"
            @mousemove="activeIndex = i"
          >
            <div class="pss-thumb">
              <img v-if="imgSrc(opt.gallery)" :src="imgSrc(opt.gallery)" :alt="opt?.gallery?.name || opt.Name" loading="lazy" />
              <i v-else class="ti ti-photo"></i>
            </div>
            <div class="pss-main">
              <div class="pss-name">
                <template v-for="(seg, si) in segments(opt.Name)" :key="si"><mark v-if="seg.hit">{{ seg.text }}</mark><template v-else>{{ seg.text }}</template></template>
              </div>
              <div class="pss-meta">
                <span v-if="opt.Value" class="pss-meta-part">
                  <i class="ti ti-hash"></i>
                  <template v-for="(seg, si) in segments(opt.Value)" :key="si"><mark v-if="seg.hit">{{ seg.text }}</mark><template v-else>{{ seg.text }}</template></template>
                </span>
                <span v-if="opt.SKU" class="pss-meta-part">
                  SKU&nbsp;<template v-for="(seg, si) in segments(opt.SKU)" :key="si"><mark v-if="seg.hit">{{ seg.text }}</mark><template v-else>{{ seg.text }}</template></template>
                </span>
                <span v-if="opt.UPC" class="pss-meta-part">EAN&nbsp;{{ opt.UPC }}</span>
              </div>
            </div>
            <div class="pss-side">
              <div v-if="priceOf(opt) !== null" class="pss-price">{{ fmtMoney.format(priceOf(opt)) }} {{ curSymbol }}</div>
              <div v-if="stockOf(opt) !== null" class="pss-stock" :class="stockOf(opt) > 0 ? 'pss-stock--ok' : 'pss-stock--zero'">
                <i class="ti" :class="stockOf(opt) > 0 ? 'ti-circle-check' : 'ti-alert-circle'"></i>
                {{ fmtQty.format(stockOf(opt)) }} auf Lager
              </div>
              <i v-if="String(opt.id) === String(modelValue)" class="ti ti-check pss-selected-check"></i>
            </div>
          </li>
        </ul>
        <div v-if="!loading && options?.length" class="pss-foot">
          <span>{{ options.length }} Artikel</span>
          <span class="pss-foot-hint"><kbd>↑</kbd><kbd>↓</kbd> wählen · <kbd>↵</kbd> übernehmen</span>
        </div>
      </div>
    </Teleport>
  </div>
</template>

<!-- Non-scoped on purpose: the panel is teleported to <body>, where scoped
     attributes are unreliable. All classes are pss- prefixed to stay isolated. -->
<style>
.pss { position: relative; width: 100%; min-width: 0; }

.pss-control {
  display: flex; align-items: center; gap: 8px;
  min-height: 42px;
  padding: 0 10px 0 12px;
  background: var(--theme-bg-primary, #fff);
  border: 1.5px solid var(--theme-border, #d8dde6);
  border-radius: 10px;
  cursor: text;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.pss--open .pss-control,
.pss-control:focus-within {
  border-color: #10b981;
  box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.14);
}
.pss--disabled .pss-control { opacity: 0.6; pointer-events: none; }

.pss-icon { color: #94a3b8; font-size: 17px; flex-shrink: 0; }

.pss-input {
  flex: 1; min-width: 0;
  border: none; outline: none; background: transparent; box-shadow: none;
  font-size: 14px; font-weight: 500;
  color: var(--theme-text-primary, #0f172a);
  padding: 9px 0;
}
.pss-input::placeholder { color: #9aa5b4; font-weight: 400; }

.pss-side-icon { color: #b3bcc9; font-size: 16px; display: inline-flex; flex-shrink: 0; }
.pss-spin i, .pss-spin-i { animation: pss-spin 1s linear infinite; color: #10b981; }
@keyframes pss-spin { to { transform: rotate(360deg); } }

.pss-clear {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; flex-shrink: 0;
  border: none; border-radius: 7px;
  background: var(--theme-bg-secondary, #f1f5f9);
  color: var(--theme-text-secondary, #64748b);
  font-size: 13px; cursor: pointer;
  transition: all 0.12s;
}
.pss-clear:hover { background: #fee2e2; color: #dc2626; }

/* ---- teleported results panel ---- */
.pss-panel {
  display: flex; flex-direction: column;
  background: var(--theme-bg-primary, #fff);
  border: 1px solid var(--theme-border, #e2e8f0);
  border-radius: 14px;
  box-shadow: 0 18px 50px rgba(15, 23, 42, 0.22), 0 4px 12px rgba(15, 23, 42, 0.1);
  overflow: hidden;
}

.pss-state {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 30px 18px;
  color: var(--theme-text-secondary, #64748b);
  font-size: 13.5px;
}
.pss-state i { font-size: 20px; }

.pss-list {
  list-style: none; margin: 0; padding: 6px;
  overflow-y: auto; flex: 1; min-height: 0;
  overscroll-behavior: contain;
}
.pss-list::-webkit-scrollbar { width: 10px; }
.pss-list::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 6px; border: 2.5px solid var(--theme-bg-primary, #fff); }

.pss-item {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 10px;
  border-radius: 10px;
  cursor: pointer;
}
.pss-item--active { background: rgba(16, 185, 129, 0.09); }
.pss-item--selected .pss-name { color: #059669; }

.pss-thumb {
  width: 46px; height: 46px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 9px; overflow: hidden;
  background: var(--theme-bg-secondary, #f1f5f9);
  border: 1px solid var(--theme-border, #e8edf3);
  color: #b3bcc9; font-size: 20px;
}
.pss-thumb img { width: 100%; height: 100%; object-fit: cover; }

.pss-main { flex: 1; min-width: 0; }
.pss-name {
  font-size: 14px; font-weight: 600; line-height: 1.3;
  color: var(--theme-text-primary, #111827);
  overflow: hidden; text-overflow: ellipsis;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.pss-name mark, .pss-meta mark {
  background: rgba(250, 204, 21, 0.45);
  color: inherit; border-radius: 3px; padding: 0 1px;
}
.pss-meta {
  display: flex; gap: 12px; flex-wrap: wrap;
  margin-top: 2px;
  font-size: 12px; color: var(--theme-text-secondary, #64748b);
}
.pss-meta-part { display: inline-flex; align-items: center; gap: 2px; white-space: nowrap; }
.pss-meta-part i { font-size: 12px; opacity: 0.7; }

.pss-side {
  margin-left: auto; flex-shrink: 0;
  display: flex; flex-direction: column; align-items: flex-end; gap: 3px;
  text-align: right;
}
.pss-price {
  font-size: 14px; font-weight: 700;
  color: var(--theme-text-primary, #0f172a);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.pss-stock {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 8px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; white-space: nowrap;
}
.pss-stock i { font-size: 12px; }
.pss-stock--ok { background: #ecfdf5; color: #047857; }
.pss-stock--zero { background: #fff7ed; color: #c2410c; }
.pss-selected-check { color: #10b981; font-size: 15px; }

.pss-foot {
  display: flex; align-items: center; justify-content: space-between;
  padding: 7px 14px;
  border-top: 1px solid var(--theme-border, #eef1f5);
  background: var(--theme-bg-secondary, #f8fafc);
  font-size: 11.5px;
  color: var(--theme-text-secondary, #94a3b8);
  flex-shrink: 0;
}
.pss-foot-hint { display: inline-flex; align-items: center; gap: 4px; }
.pss-foot-hint kbd {
  padding: 0 5px; border-radius: 4px;
  background: var(--theme-bg-primary, #fff);
  border: 1px solid var(--theme-border, #e2e8f0);
  font-size: 10px; font-family: inherit;
  color: var(--theme-text-secondary, #64748b);
}

/* ---- dark mode ---- */
[data-mode="dark"] .pss-control { background: #0f172a; border-color: #334155; }
[data-mode="dark"] .pss-input { color: #f1f5f9; }
[data-mode="dark"] .pss-clear { background: #1e293b; color: #cbd5e1; }
[data-mode="dark"] .pss-clear:hover { background: #451a1a; color: #f87171; }
[data-mode="dark"] .pss-panel { background: #0f172a; border-color: #1e293b; box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6); }
[data-mode="dark"] .pss-list::-webkit-scrollbar-thumb { background: #334155; border-color: #0f172a; }
[data-mode="dark"] .pss-item--active { background: rgba(16, 185, 129, 0.14); }
[data-mode="dark"] .pss-thumb { background: #1e293b; border-color: #334155; }
[data-mode="dark"] .pss-name { color: #f1f5f9; }
[data-mode="dark"] .pss-item--selected .pss-name { color: #34d399; }
[data-mode="dark"] .pss-name mark, [data-mode="dark"] .pss-meta mark { background: rgba(250, 204, 21, 0.28); color: #fef9c3; }
[data-mode="dark"] .pss-price { color: #f1f5f9; }
[data-mode="dark"] .pss-stock--ok { background: rgba(16, 185, 129, 0.15); color: #34d399; }
[data-mode="dark"] .pss-stock--zero { background: rgba(249, 115, 22, 0.15); color: #fb923c; }
[data-mode="dark"] .pss-foot { background: #0b1220; border-top-color: #1e293b; }
[data-mode="dark"] .pss-foot-hint kbd { background: #1e293b; border-color: #334155; color: #cbd5e1; }

@media (max-width: 640px) {
  .pss-panel { border-radius: 12px; }
  .pss-side { display: none; }
}
</style>
