<template>
  <!-- Palette de commandes globale (Cmd/Ctrl+K). Recherche : actions rapides + pages +
       actions contextuelles de la page + clients/équipe (fuzzy via le hub).
       C'est l'escape hatch qui permet de déclutterer : rien de caché n'est perdu. -->
  <q-dialog v-model="open" @show="onShow" @hide="onHide" position="top" transition-show="fade" transition-hide="fade">
    <q-card class="cmdp">
      <div class="cmdp-input-row">
        <q-icon name="search" size="22px" class="cmdp-search-icon" />
        <input
          ref="inputEl"
          v-model="query"
          class="cmdp-input"
          type="text"
          placeholder="Rechercher une action, une page, un client…"
          @keydown.down.prevent="move(1)"
          @keydown.up.prevent="move(-1)"
          @keydown.enter.prevent="runHighlighted"
          @keydown.esc.prevent="open = false"
        />
        <span class="cmdp-hint">esc</span>
      </div>

      <q-scroll-area class="cmdp-results" :style="{ height: resultsHeight }">
        <template v-if="visible.length">
          <template v-for="(grp, gi) in grouped" :key="grp.label">
            <div class="cmdp-group-label">{{ grp.label }}</div>
            <div
              v-for="it in grp.items" :key="it._key"
              class="cmdp-item" :class="{ 'is-active': it._idx === highlight }"
              @mousemove="highlight = it._idx"
              @click="runItem(it)"
            >
              <q-icon :name="it.icon || 'chevron_right'" size="20px" class="cmdp-item-icon" :style="it.color ? { color: it.color } : null" />
              <div class="cmdp-item-body">
                <div class="cmdp-item-label">{{ it.label }}</div>
                <div v-if="it.caption" class="cmdp-item-caption">{{ it.caption }}</div>
              </div>
              <span v-if="it.typeLabel" class="cmdp-item-type">{{ it.typeLabel }}</span>
            </div>
            <q-separator v-if="gi < grouped.length - 1" spaced />
          </template>
        </template>
        <div v-else class="cmdp-empty">
          <q-spinner v-if="searching" size="20px" />
          <span v-else>Aucun résultat pour « {{ query }} »</span>
        </div>
      </q-scroll-area>

      <div class="cmdp-footer">
        <span><kbd>↑</kbd><kbd>↓</kbd> naviguer</span>
        <span><kbd>↵</kbd> ouvrir</span>
        <span><kbd>⌘</kbd><kbd>K</kbd> palette</span>
      </div>
    </q-card>
  </q-dialog>
</template>

<script setup>
import { ref, computed, nextTick, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { HUB_URL } from 'src/config/hub'
import { navItems as allNavItems } from 'src/config/nav'
import { usePermissions } from 'src/composables/usePermissions'
import { useConversations } from 'src/composables/useConversations'
import { useCreateSignal } from 'src/composables/useCreateSignal'
import { useCommandPalette } from 'src/composables/useCommandPalette'

const route = useRoute()
const router = useRouter()
const { can } = usePermissions()
const { newDialogOpen, newDialogChannel, openComposeDraft, panelOpen } = useConversations()
const { requestCreate } = useCreateSignal()
const { open, contextActions } = useCommandPalette()
const emit = defineEmits(['nl']) // commande en langage naturel → MainLayout ouvre le copilote STAFF

const inputEl = ref(null)
const query = ref('')
const highlight = ref(0)
const searching = ref(false)
const entityResults = ref([])
let debounce = null

// ── Actions rapides intégrées (miroir du FAB « créer rapide ») ──
const quickActions = computed(() => {
  const a = []
  if (can('view_clients')) {
    a.push({ id: 'create', label: 'Créer…', caption: 'tâche · intervention · soumission', icon: 'add_box', color: '#21ba45', group: 'Actions', keywords: 'nouveau job ticket devis creer', run: () => { requestCreate(); if (!route.path.startsWith('/planification')) router.push('/planification') } })
    a.push({ id: 'email', label: 'Nouveau courriel', icon: 'mail', color: '#c10015', group: 'Actions', keywords: 'email mail message ecrire', run: () => { newDialogChannel.value = 'email'; newDialogOpen.value = true } })
    a.push({ id: 'sms', label: 'Nouveau texto', icon: 'sms', color: '#21a56a', group: 'Actions', keywords: 'sms texto message', run: () => { newDialogChannel.value = 'sms'; newDialogOpen.value = true } })
    a.push({ id: 'comms', label: 'Ouvrir les communications', icon: 'forum', group: 'Actions', keywords: 'boite inbox conversations fils', run: () => { panelOpen.value = false; router.push('/communications') } })
  }
  return a
})

// ── Pages (nav filtrée par capacités) ──
const pageActions = computed(() =>
  allNavItems
    .filter(n => !n.requires || can(n.requires))
    .map(n => ({ id: 'nav-' + n.path, label: n.label, icon: 'north_east', group: 'Aller à', keywords: n.path, run: () => router.push(n.path) })),
)

// ── Actions contextuelles enregistrées par la page courante ──
const pageContext = computed(() =>
  contextActions().map((a, i) => ({ ...a, id: a.id || ('ctx-' + i), group: a.group || 'Sur cette page', icon: a.icon || 'bolt' })),
)

function matches (item, q) {
  if (!q) return true
  const hay = (item.label + ' ' + (item.caption || '') + ' ' + (item.keywords || '')).toLowerCase()
  return q.toLowerCase().split(/\s+/).every(tok => hay.includes(tok))
}

// Items filtrés, dans l'ordre de collecte : contexte → actions → pages → entités.
const filtered = computed(() => {
  const q = query.value.trim()
  const out = []
  for (const it of pageContext.value) if (matches(it, q)) out.push(it)
  for (const it of quickActions.value) if (matches(it, q)) out.push(it)
  for (const it of pageActions.value) if (matches(it, q)) out.push(it)
  for (const it of entityResults.value) out.push(it)  // déjà filtrées côté serveur
  // Escape hatch en langage naturel : « Demander à l'assistant » (dispatch/planif). Toujours proposé
  // quand on tape → une COMMANDE (« assigne le job X à Simon ») n'a aucun match d'action/page, donc cet
  // item devient l'unique résultat (surligné) et Entrée ouvre le copilote STAFF. Voir project_ops_nl_commands_training.
  if (q) out.push({ id: 'nl-command', group: 'Assistant', icon: 'auto_awesome', color: '#7c6cf6', label: `Demander à l’assistant : « ${q} »`, caption: 'Interpréter en langage naturel — dispatch, planification…', keywords: q, run: () => emit('nl', q) })
  return out
})

// Rang des groupes GLOBAUX ; tout groupe inconnu (= action de page) passe en tête (0).
// 'Assistant' (langage naturel) en DERNIER pour ne pas voler la position aux vrais résultats.
const GLOBAL_RANK = { 'Actions': 1, 'Clients / Équipe': 2, 'Aller à': 3, 'Assistant': 9 }
// Groupé POUR L'AFFICHAGE, puis aplati dans CE MÊME ordre pour attribuer `_idx`
// → la navigation clavier suit exactement l'ordre visuel (pas de saut).
const grouped = computed(() => {
  const byGroup = {}; const order = []
  for (const it of filtered.value) { if (!byGroup[it.group]) { byGroup[it.group] = []; order.push(it.group) } byGroup[it.group].push(it) }
  order.sort((a, b) => (a in GLOBAL_RANK ? GLOBAL_RANK[a] : 0) - (b in GLOBAL_RANK ? GLOBAL_RANK[b] : 0)) // tri stable
  let idx = 0; const groups = []
  for (const label of order) {
    const items = byGroup[label].map(it => { const _idx = idx++; return { ...it, _idx, _key: (it.id || it.route || it.label) + '#' + _idx } })
    groups.push({ label, items })
  }
  return groups
})
// Liste plate dans l'ordre d'affichage (pour le clavier).
const visible = computed(() => grouped.value.flatMap(g => g.items))

const resultsHeight = computed(() => Math.min(420, Math.max(120, visible.value.length * 52 + 40)) + 'px')

// ── Recherche d'entités (clients + équipe), fuzzy via le hub (même endpoint que la topbar) ──
function onQuery () {
  clearTimeout(debounce)
  const q = query.value.trim()
  highlight.value = 0
  if (q.length < 2) { entityResults.value = []; searching.value = false; return }
  searching.value = true
  debounce = setTimeout(() => runEntitySearch(q), 250)
}
async function runEntitySearch (q) {
  try {
    const r = await fetch(`${HUB_URL}/collab/customer-search?q=${encodeURIComponent(q)}&team=1`)
    const matchesArr = r.ok ? ((await r.json()).matches || []) : []
    entityResults.value = matchesArr.slice(0, 8).map(m => m.kind === 'équipe'
      ? { id: 't-' + m.name, group: 'Clients / Équipe', typeLabel: 'Équipe', icon: 'groups', label: m.customer_name || m.name, caption: 'Membre de l’équipe · ' + m.email, run: () => openTeam(m.email) }
      : { id: 'c-' + m.name, group: 'Clients / Équipe', typeLabel: 'Client', icon: m.matched === 'adresse' ? 'location_on' : m.matched === 'téléphone' ? 'call' : m.matched === 'courriel' ? 'mail' : 'person', label: m.customer_name || m.name, caption: [m.matched, m.address, m.email].filter(Boolean).join(' · ') || m.name, run: () => router.push('/clients/' + m.name) })
  } catch { entityResults.value = [] }
  searching.value = false
}
async function openTeam (email) {
  if (route.path.startsWith('/communications/c/')) { await router.push('/communications'); await nextTick() }
  openComposeDraft({ to: email, channel: 'email' })
}

// query → relance la recherche entité
watch(query, onQuery)

// ── Clavier ──
function move (dir) {
  const n = visible.value.length
  if (!n) return
  highlight.value = (highlight.value + dir + n) % n
}
function runHighlighted () {
  const it = visible.value[highlight.value]
  if (it) runItem(it)
}
function runItem (it) {
  open.value = false
  if (typeof it.run === 'function') it.run()
}

function onShow () {
  query.value = ''
  entityResults.value = []
  highlight.value = 0
  nextTick(() => inputEl.value && inputEl.value.focus())
}
function onHide () { query.value = ''; entityResults.value = [] }
</script>

<style scoped>
.cmdp { width: 640px; max-width: 92vw; margin-top: 8vh; border-radius: 14px; box-shadow: 0 24px 60px rgba(0,0,0,.28); overflow: hidden; background: var(--ops-surface, #fff); }
.cmdp-input-row { display: flex; align-items: center; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--ops-border); }
.cmdp-search-icon { color: var(--ops-text-muted); }
.cmdp-input { flex: 1; border: none; outline: none; background: transparent; font-size: 1.05rem; color: var(--ops-text); }
.cmdp-hint { font-size: 0.68rem; color: var(--ops-text-muted); border: 1px solid var(--ops-border); border-radius: 6px; padding: 1px 6px; text-transform: uppercase; }
.cmdp-results { max-height: 60vh; }
.cmdp-group-label { font-size: 0.7rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--ops-text-muted); padding: 12px 16px 4px; }
.cmdp-item { display: flex; align-items: center; gap: 12px; padding: 9px 16px; cursor: pointer; }
.cmdp-item.is-active { background: var(--ops-bg-light); }
.cmdp-item-icon { color: var(--ops-text-muted); flex: 0 0 auto; }
.cmdp-item-body { flex: 1; min-width: 0; }
.cmdp-item-label { font-size: 0.94rem; color: var(--ops-text); font-weight: 550; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cmdp-item-caption { font-size: 0.78rem; color: var(--ops-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cmdp-item-type { font-size: 0.7rem; color: var(--ops-text-muted); flex: 0 0 auto; }
.cmdp-empty { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 32px; color: var(--ops-text-muted); font-size: 0.9rem; }
.cmdp-footer { display: flex; gap: 16px; padding: 8px 16px; border-top: 1px solid var(--ops-border); font-size: 0.72rem; color: var(--ops-text-muted); }
.cmdp-footer kbd { font-family: inherit; border: 1px solid var(--ops-border); border-radius: 5px; padding: 0 5px; margin-right: 2px; background: var(--ops-bg-light); }
</style>
