<template>
  <!-- Quick actions -->
  <div class="issue-actions q-mb-sm">
    <TicketStatusControl doctype="Issue" :docname="docName" :status="doc.status"
      @changed="p => { if (p && p.status) doc.status = p.status; $emit('reply-sent', docName) }" />
    <FlowQuickButton flat dense size="sm" icon="account_tree" label="Flow"
      tooltip="Lancer / modifier un flow pour ce ticket"
      category="incident" applies-to="Issue" trigger-event="on_issue_opened" />
  </div>

  <!-- Client lié (comme une conversation : cherchable par nom, association 1 clic) -->
  <div class="cust-banner q-mb-sm">
    <q-icon name="person" size="18px" :color="doc.customer ? 'green-7' : 'grey-5'" />
    <template v-if="doc.customer">
      <span class="cust-name erp-link" @click="$emit('navigate', 'Customer', doc.customer)">{{ customerLabel || doc.customer }}</span>
      <q-btn flat dense round size="xs" icon="link_off" color="grey-6" :loading="linkingCust" @click="unlinkCustomer">
        <q-tooltip>Délier le client</q-tooltip>
      </q-btn>
    </template>
    <q-select v-else dense borderless use-input hide-dropdown-icon input-debounce="300"
      :options="custOptions" option-value="value" option-label="label" emit-value map-options
      :loading="loadingCust" placeholder="Lier à un compte client (nom)…" style="flex:1;min-width:170px"
      @filter="searchCustomers" @update:model-value="linkCustomer">
      <template #option="scope">
        <q-item v-bind="scope.itemProps" dense>
          <q-item-section>
            <q-item-label>{{ scope.opt.label }}</q-item-label>
            <q-item-label caption>{{ scope.opt.sub }}</q-item-label>
          </q-item-section>
        </q-item>
      </template>
      <template #no-option>
        <q-item dense><q-item-section class="text-caption text-grey-6">Tapez un nom de client…</q-item-section></q-item>
      </template>
    </q-select>
  </div>

  <div class="issue-field-grid">
    <div class="if-row">
      <span class="if-label">Statut</span>
      <InlineField :value="doc.status" field="status" doctype="Issue" :docname="docName"
        type="select" :options="['Open', 'Replied', 'On Hold', 'Resolved', 'Closed']"
        @saved="v => doc.status = v.value" />
    </div>
    <div class="if-row">
      <span class="if-label">Priorite</span>
      <InlineField :value="doc.priority" field="priority" doctype="Issue" :docname="docName"
        type="select" :options="['Low', 'Medium', 'High', 'Urgent']"
        @saved="v => doc.priority = v.value" />
    </div>
    <div class="if-row">
      <span class="if-label">Ouvert le</span>
      <span class="if-value">{{ formatDate(doc.opening_date) }}</span>
    </div>
    <div class="if-row" v-if="doc.resolution_time">
      <span class="if-label">Resolu le</span>
      <span class="if-value">{{ doc.resolution_time }}</span>
    </div>
    <div class="if-row">
      <span class="if-label">Type</span>
      <InlineField :value="doc.issue_type" field="issue_type" doctype="Issue" :docname="docName"
        type="select" :options="['Support', 'Installation', 'Demenagement', 'Facturation', 'Reseau', 'Autre']"
        placeholder="Type" @saved="v => doc.issue_type = v.value" />
    </div>
    <div class="if-row" v-if="doc.service_location">
      <span class="if-label">Adresse</span>
      <span class="if-value">{{ doc.service_location }}</span>
    </div>
    <div class="if-row" v-if="doc.raised_by">
      <span class="if-label">Soumis par</span>
      <span class="if-value">{{ doc.raised_by }}</span>
    </div>
    <div class="if-row">
      <span class="if-label">Assigné à</span>
      <q-select v-model="assignees" dense borderless multiple use-chips emit-value map-options
        :options="userOptions" option-value="value" option-label="label"
        input-class="editable-input" style="min-width:140px;flex:1"
        :loading="loadingUsers"
        @update:model-value="saveAssignees">
        <template #selected-item="scope">
          <q-chip dense removable size="sm" color="green-1" text-color="green-8"
            @remove="scope.removeAtIndex(scope.index)">
            {{ scope.opt.label || scope.opt }}
          </q-chip>
        </template>
      </q-select>
    </div>
  </div>

  <!-- Tags -->
  <div class="q-mt-sm">
    <div class="info-block-title" style="display:flex;align-items:center">
      Tags
      <q-btn flat dense round size="xs" icon="settings" color="grey-5" class="q-ml-xs"
        @click="showTagManager = !showTagManager">
        <q-tooltip>Gérer les tags</q-tooltip>
      </q-btn>
    </div>
    <div class="row items-center q-gutter-xs q-mb-xs">
      <q-chip v-for="tag in currentTags" :key="tag" dense removable size="sm"
        :style="{ background: getTagColor(tag), color: '#fff' }"
        @remove="removeTag(tag)">
        {{ tag }}
      </q-chip>
    </div>
    <q-select dense outlined use-input input-debounce="100" emit-value map-options
      :options="filteredTagOptions" option-value="value" option-label="label"
      :model-value="null" :loading="loadingTags"
      placeholder="Ajouter un tag..."
      style="max-width:250px" input-class="text-caption"
      @filter="filterTags" @update:model-value="addTag"
      new-value-mode="add-unique" @new-value="addNewTag">
      <template #option="scope">
        <q-item v-bind="scope.itemProps" dense>
          <q-item-section side>
            <div style="width:10px;height:10px;border-radius:50%" :style="{ background: scope.opt.color || '#6b7280' }" />
          </q-item-section>
          <q-item-section>
            <q-item-label>{{ scope.opt.label }}</q-item-label>
            <q-item-label caption v-if="scope.opt.category">{{ scope.opt.category }}</q-item-label>
          </q-item-section>
        </q-item>
      </template>
      <template #no-option>
        <q-item dense>
          <q-item-section class="text-caption text-grey-6">Tapez pour créer un tag</q-item-section>
        </q-item>
      </template>
    </q-select>

    <!-- Tag Manager -->
    <div v-if="showTagManager" class="tag-manager q-mt-sm">
      <div class="text-caption text-weight-bold text-grey-7 q-mb-xs">Gérer les tags ({{ allDispatchTags.length }})</div>
      <div v-for="t in allDispatchTags" :key="t.name" class="tag-mgr-row">
        <div class="tag-mgr-dot" :style="{ background: t.color || '#6b7280' }"
          @click="cycleTagColor(t)">
          <q-tooltip>Changer la couleur</q-tooltip>
        </div>
        <span v-if="editingTagName !== t.name" class="tag-mgr-label" @dblclick="startEditTag(t)">{{ t.label }}</span>
        <input v-else v-model="editingTagValue" class="tag-mgr-edit"
          @keyup.enter="saveTagRename(t)" @keyup.escape="editingTagName = null" @blur="saveTagRename(t)" />
        <span class="tag-mgr-cat text-caption text-grey-5">{{ t.category }}</span>
        <q-btn flat dense round size="xs" icon="delete" color="negative"
          @click="deleteDispatchTag(t)" class="tag-mgr-del">
          <q-tooltip>Supprimer ce tag</q-tooltip>
        </q-btn>
      </div>
      <div v-if="!allDispatchTags.length" class="text-caption text-grey-5">Aucun tag</div>
    </div>
  </div>

  <!-- ═══ DISPATCH / PROJECT TASKS ═══ -->
  <div class="q-mt-md dispatch-section">
    <div class="row items-center q-mb-xs">
      <div class="info-block-title" style="margin-bottom:0">
        <q-icon name="account_tree" size="16px" class="q-mr-xs" />
        Tâches ({{ dispatchJobs.length }})
      </div>
      <q-space />
      <!-- Un SEUL point d'entrée « Ajouter » (plus deux icônes côte à côte) : tâche simple, ou projet multi-étapes d'où découlent les tâches. -->
      <q-btn-dropdown dense size="sm" color="primary" no-caps unelevated icon="add" label="Ajouter">
        <q-list dense style="min-width:230px">
          <q-item clickable v-close-popup @click="showCreateDialog = true">
            <q-item-section avatar><q-icon name="add_task" color="primary" /></q-item-section>
            <q-item-section><q-item-label>Tâche</q-item-label><q-item-label caption>Tâche simple liée à ce ticket</q-item-label></q-item-section>
          </q-item>
          <q-item clickable v-close-popup @click="showProjectWizard = true">
            <q-item-section avatar><q-icon name="playlist_add" color="green-7" /></q-item-section>
            <q-item-section><q-item-label>Projet</q-item-label><q-item-label caption>Modèle multi-étapes (installation…) — les tâches en découlent</q-item-label></q-item-section>
          </q-item>
        </q-list>
      </q-btn-dropdown>
    </div>

    <!-- Nested task tree -->
    <div v-if="dispatchJobs.length" class="dispatch-jobs-list">
      <!-- Root jobs (no parent) -->
      <template v-for="job in rootJobs" :key="job.name">
        <TaskNode :job="job" :all-jobs="dispatchJobs" :depth="0"
          @fire-webhook="fireWebhook" @deleted="onJobDeleted" @updated="onJobUpdated" />
      </template>

      <!-- Orphan jobs (parent_job references something outside this ticket) -->
      <template v-for="job in orphanJobs" :key="job.name">
        <TaskNode :job="job" :all-jobs="dispatchJobs" :depth="0"
          @fire-webhook="fireWebhook" @deleted="onJobDeleted" @updated="onJobUpdated" />
      </template>
    </div>

    <div v-else class="text-caption text-grey-5 q-pa-sm">
      Aucune tâche liée à ce ticket.
    </div>
  </div>

  <div class="q-mt-sm">
    <div class="info-block-title">Sujet</div>
    <InlineField :value="doc.subject" field="subject" doctype="Issue" :docname="docName"
      placeholder="Sujet du ticket" @saved="v => doc.subject = v.value" />
  </div>
  <div v-if="doc.issue_split_from" class="q-mt-md">
    <div class="info-block-title">Ticket parent</div>
    <div class="text-body2 erp-link" style="cursor:pointer" @click="$emit('navigate', 'Issue', doc.issue_split_from)">
      {{ doc.issue_split_from }}
    </div>
  </div>
  <div class="q-mt-md">
    <div class="info-block-title">Description</div>
    <InlineField :value="doc.description || ''" field="description" doctype="Issue" :docname="docName"
      type="textarea" placeholder="Ajouter une description..."
      @saved="v => doc.description = v.value" />
  </div>
  <div v-if="doc.resolution_details" class="q-mt-md">
    <div class="info-block-title">Resolution</div>
    <div class="modal-desc" v-html="richHtml(doc.resolution_details)"></div>
  </div>
  <!-- Fil unifié : échanges (Communication) + notes (Comment) fusionnés, chronologique, en bulles -->
  <div v-if="thread.length" class="q-mt-md">
    <div class="info-block-title">Conversation ({{ thread.length }})</div>
    <div class="thread-wrap">
      <div v-for="m in thread" :key="m.id" class="msg-bubble" :class="'msg-' + m.kind">
        <div class="msg-head">
          <span class="msg-avatar">{{ initials(m.who) }}</span>
          <span class="msg-who">{{ m.who }}</span>
          <span class="msg-when">{{ formatDateTime(m.when) }}</span>
        </div>
        <div class="msg-body" v-html="richHtml(m.content)"></div>
      </div>
    </div>
  </div>
  <div v-if="files.length" class="q-mt-md">
    <div class="info-block-title">Pieces jointes ({{ files.length }})</div>
    <div v-for="f in files" :key="f.name" class="q-py-xs">
      <a :href="erpFileUrl(f.file_url)" target="_blank" class="erp-link">
        <q-icon name="attach_file" size="14px" /> {{ f.file_name }}
      </a>
    </div>
  </div>
  <div v-if="!doc.description && !doc.resolution_details && !thread.length" class="text-center text-grey-5 q-pa-lg">
    Aucun contenu pour ce ticket
  </div>

  <!-- Delete -->
  <div class="q-mt-lg" style="border-top:1px solid #fee2e2;padding-top:12px">
    <q-btn flat dense no-caps size="sm" icon="delete" label="Supprimer ce ticket"
      color="negative" @click="confirmDelete">
      <q-tooltip>Suppression définitive du ticket</q-tooltip>
    </q-btn>
  </div>

  <div class="q-mt-md reply-box">
    <div class="info-block-title">Répondre</div>
    <q-input v-model="replyContent" dense outlined type="textarea" autogrow
      placeholder="Écrire une réponse ou une note…"
      :input-style="{ fontSize: '0.85rem', minHeight: '50px' }"
      @keydown.ctrl.enter="sendReply" @keydown.meta.enter="sendReply" />
    <div class="row justify-end q-gutter-sm q-mt-xs">
      <q-btn outline dense size="sm" label="Note interne" color="grey-8" icon="sticky_note_2"
        :disable="!replyContent?.trim()" :loading="sendingReply" @click="sendReply">
        <q-tooltip>Ajoute une note au fil — non envoyée au client</q-tooltip>
      </q-btn>
      <q-btn unelevated dense size="sm" label="Envoyer par courriel" color="primary" icon="mail" @click="openEmailDialog">
        <q-tooltip>Envoyer par courriel ; la réponse du destinataire reviendra dans ce ticket</q-tooltip>
      </q-btn>
    </div>
  </div>

  <!-- Aller-retour courriel : envoie le ticket, la réponse revient au fil du ticket -->
  <q-dialog v-model="showEmailDialog">
    <q-card style="min-width:min(92vw,460px)">
      <q-card-section class="row items-center q-pb-none">
        <q-icon name="mail" color="primary" class="q-mr-sm" />
        <div class="text-subtitle1 text-weight-bold">Envoyer par courriel</div>
        <q-space /><q-btn flat dense round icon="close" v-close-popup />
      </q-card-section>
      <q-card-section class="q-gutter-sm">
        <q-input v-model="emailTo" dense outlined label="À (courriel)" type="email" autofocus />
        <q-input v-model="emailCc" dense outlined label="Cc (optionnel)" />
        <q-input v-model="emailMessage" dense outlined type="textarea" autogrow label="Message" :input-style="{ minHeight: '90px' }" />
        <div class="text-caption text-grey-6"><q-icon name="info" size="13px" /> Un lien vers le ticket est ajouté. La réponse du destinataire s'ajoutera automatiquement à ce ticket.</div>
      </q-card-section>
      <q-card-actions align="right">
        <q-btn flat label="Annuler" v-close-popup />
        <q-btn unelevated color="primary" icon="send" label="Envoyer" :loading="sendingEmail"
          :disable="!emailTo || !/.+@.+\..+/.test(emailTo)" @click="sendTicketEmail" />
      </q-card-actions>
    </q-card>
  </q-dialog>

  <!-- Create single Dispatch Job dialog -->
  <UnifiedCreateModal
    v-model="showCreateDialog"
    mode="work-order"
    :context="dispatchContext"
    :existing-jobs="dispatchJobs"
    @created="onJobCreated"
  />

  <!-- Project Wizard (multi-step template) -->
  <ProjectWizard
    v-model="showProjectWizard"
    :issue="doc"
    :existing-jobs="dispatchJobs"
    @created="onJobCreated"
  />
</template>

<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { Notify, Dialog } from 'quasar'
import { richHtml } from 'src/utils/sanitize'
import { formatDate, formatDateTime, erpFileUrl } from 'src/composables/useFormatters'
import { createDoc, updateDoc, deleteDoc, listDocs } from 'src/api/erp'
import { authFetch } from 'src/api/auth'
import { useAuthStore } from 'src/stores/auth'
import { usePermissions } from 'src/composables/usePermissions'
import { BASE_URL } from 'src/config/erpnext'
import { HUB_URL } from 'src/config/hub'
import { fetchTags, updateTag, renameTag, deleteTag as deleteTagApi } from 'src/api/dispatch'
import InlineField from 'src/components/shared/InlineField.vue'
import UnifiedCreateModal from 'src/components/shared/UnifiedCreateModal.vue'
import FlowQuickButton from 'src/components/flow-editor/FlowQuickButton.vue'
import TicketStatusControl from 'src/components/shared/TicketStatusControl.vue'
import ProjectWizard from 'src/components/shared/ProjectWizard.vue'
import TaskNode from 'src/components/shared/TaskNode.vue'

const props = defineProps({
  doc: { type: Object, required: true },
  docName: String,
  title: String,
  comments: { type: Array, default: () => [] },
  comms: { type: Array, default: () => [] },
  files: { type: Array, default: () => [] },
  dispatchJobs: { type: Array, default: () => [] },
})
const emit = defineEmits(['navigate', 'reply-sent', 'dispatch-created', 'dispatch-deleted', 'dispatch-updated', 'deleted'])

const replyContent = ref('')
const authStore = useAuthStore()
const { userName } = usePermissions()
// Expéditeur = PRÉNOM de l'utilisateur connecté (note interne) / prénom+nom (sortie courriel au client) — jamais le courriel/login brut (« louis »).
const senderFirst = computed(() => { const n = (userName.value || '').trim(); return (n ? n.split(/\s+/)[0] : (authStore.user || '').split('@')[0]) || 'Agent' })
const senderFull = computed(() => ((userName.value || '').trim() || (authStore.user || '').split('@')[0] || 'Agent'))
const sendingReply = ref(false)
const showCreateDialog = ref(false)
const showProjectWizard = ref(false)

const dispatchContext = computed(() => ({
  subject: props.doc?.subject || '',
  customer: props.doc?.customer || '',
  source_issue: props.doc?.name || '',
  service_location: props.doc?.service_location || '',
  issue_type: props.doc?.issue_type || '',
  priority: props.doc?.priority || 'medium',
}))

// ── Client lié + fil unifié ──

const custOptions = ref([])
const loadingCust = ref(false)
const linkingCust = ref(false)
const customerLabel = ref('')

function initials (name) {
  if (!name) return '?'
  const p = String(name).replace(/@.*/, '').split(/[\s._-]+/).filter(Boolean)
  return ((p[0]?.[0] || '') + (p[1]?.[0] || '')).toUpperCase() || '?'
}

// Fusionne échanges (Communication) + notes (Comment) en UN fil chronologique
const thread = computed(() => {
  const items = []
  for (const c of props.comms || []) items.push({ id: 'e-' + c.name, who: c.sender_full_name || c.sender || c.owner || 'Système', when: c.creation, content: c.content, kind: 'comm' })
  for (const c of props.comments || []) items.push({ id: 'c-' + c.name, who: c.comment_by || 'Système', when: c.creation, content: c.content, kind: 'note' })
  return items.sort((a, b) => String(a.when || '').localeCompare(String(b.when || '')))
})

async function loadCustomerLabel () {
  customerLabel.value = ''
  if (!props.doc?.customer) return
  try {
    const rows = await listDocs('Customer', { filters: { name: props.doc.customer }, fields: ['customer_name'], limit: 1 })
    customerLabel.value = rows?.[0]?.customer_name || ''
  } catch { /* garde l'ID affiché */ }
}
watch(() => props.doc?.customer, loadCustomerLabel, { immediate: true })

function searchCustomers (val, update) {
  if (!val || val.length < 2) { update(() => { custOptions.value = [] }); return }
  update(async () => {
    loadingCust.value = true
    try {
      const rows = await listDocs('Customer', {
        filters: { customer_name: ['like', '%' + val + '%'] },
        fields: ['name', 'customer_name'], limit: 12, orderBy: 'customer_name asc',
      })
      custOptions.value = rows.map(r => ({ label: r.customer_name || r.name, value: r.name, sub: r.name }))
    } catch { custOptions.value = [] }
    loadingCust.value = false
  })
}

async function linkCustomer (name) {
  if (!name) return
  linkingCust.value = true
  try {
    await updateDoc('Issue', props.docName, { customer: name })
    props.doc.customer = name
    await loadCustomerLabel()
    Notify.create({ type: 'positive', message: 'Client lié', timeout: 1500 })
  } catch (e) { Notify.create({ type: 'negative', message: 'Erreur: ' + e.message, timeout: 3000 }) }
  finally { linkingCust.value = false }
}

async function unlinkCustomer () {
  linkingCust.value = true
  try {
    await updateDoc('Issue', props.docName, { customer: '' })
    props.doc.customer = ''
    customerLabel.value = ''
    Notify.create({ type: 'positive', message: 'Client délié', timeout: 1500 })
  } catch (e) { Notify.create({ type: 'negative', message: 'Erreur: ' + e.message, timeout: 3000 }) }
  finally { linkingCust.value = false }
}

// ── Assignees ──

const userOptions = ref([])
const loadingUsers = ref(false)
const assignees = ref([])

function parseAssign (raw) {
  if (!raw) return []
  try { return JSON.parse(raw).filter(Boolean) } catch { return [] }
}

watch(() => props.doc?._assign, (val) => {
  assignees.value = parseAssign(val)
}, { immediate: true })

async function loadUsers () {
  if (userOptions.value.length) return
  loadingUsers.value = true
  try {
    const users = await listDocs('User', {
      filters: { enabled: 1, user_type: 'System User' },
      fields: ['name', 'full_name'],
      limit: 100,
      orderBy: 'full_name asc',
    })
    userOptions.value = users.map(u => ({ label: u.full_name || u.name, value: u.name }))
  } catch { userOptions.value = [] }
  loadingUsers.value = false
}

async function saveAssignees (newList) {
  const old = parseAssign(props.doc._assign)
  const added = newList.filter(u => !old.includes(u))
  const removed = old.filter(u => !newList.includes(u))
  try {
    for (const user of added) {
      await authFetch(BASE_URL + '/api/method/frappe.desk.form.assign_to.add', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ doctype: 'Issue', name: props.docName, assign_to: [user] }),
      })
    }
    for (const user of removed) {
      await authFetch(BASE_URL + '/api/method/frappe.desk.form.assign_to.remove', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ doctype: 'Issue', name: props.docName, assign_to: user }),
      })
    }
    props.doc._assign = JSON.stringify(newList)
    Notify.create({ type: 'positive', message: 'Assignation mise à jour', timeout: 1500 })
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur assignation: ' + e.message, timeout: 3000 })
    assignees.value = old
  }
}

onMounted(() => { loadUsers(); loadDispatchTags() })

// ── Tags (Dispatch Tags shared list) ──

const allDispatchTags = ref([])
const loadingTags = ref(false)
const filteredTagOptions = ref([])

function parseTags (raw) {
  if (!raw) return []
  return raw.split(',').map(t => t.trim()).filter(Boolean)
}

const currentTags = computed(() => parseTags(props.doc?._user_tags))

function getTagColor (label) {
  const t = allDispatchTags.value.find(x => x.label === label || x.name === label)
  return t?.color || '#6b7280'
}

function filterTags (val, update) {
  update(() => {
    const current = parseTags(props.doc?._user_tags)
    const all = allDispatchTags.value
      .map(t => ({ label: t.label, value: t.label, color: t.color, category: t.category || '' }))
      .filter(o => !current.includes(o.value))
    if (!val) { filteredTagOptions.value = all; return }
    const q = val.toLowerCase()
    filteredTagOptions.value = all.filter(o => o.label.toLowerCase().includes(q))
  })
}

async function addTag (tag) {
  if (!tag) return
  const label = typeof tag === 'string' ? tag : tag.value || tag
  if (currentTags.value.includes(label)) return
  try {
    await authFetch(BASE_URL + '/api/method/frappe.desk.doctype.tag.tag.add_tag', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ tag: label, dt: 'Issue', dn: props.docName }),
    })
    const cur = parseTags(props.doc._user_tags)
    cur.push(label)
    props.doc._user_tags = cur.join(',')
    Notify.create({ type: 'positive', message: `Tag "${label}" ajouté`, timeout: 1500 })
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur: ' + e.message, timeout: 3000 })
  }
}

function addNewTag (val, done) {
  if (val.length > 1) {
    done(val, 'add-unique')
    addTag(val)
  }
}

async function removeTag (tag) {
  try {
    await authFetch(BASE_URL + '/api/method/frappe.desk.doctype.tag.tag.remove_tag', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ tag, dt: 'Issue', dn: props.docName }),
    })
    const cur = parseTags(props.doc._user_tags).filter(t => t !== tag)
    props.doc._user_tags = cur.join(',')
    Notify.create({ type: 'positive', message: `Tag "${tag}" retiré`, timeout: 1500 })
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur: ' + e.message, timeout: 3000 })
  }
}

async function loadDispatchTags () {
  loadingTags.value = true
  try {
    allDispatchTags.value = await fetchTags()
  } catch { allDispatchTags.value = [] }
  loadingTags.value = false
}

// ── Tag Manager ──

const showTagManager = ref(false)
const editingTagName = ref(null)
const editingTagValue = ref('')

const TAG_COLORS = [
  '#6366f1', '#3b82f6', '#06b6d4', '#10b981', '#14b8a6',
  '#f59e0b', '#f97316', '#f43f5e', '#d946ef', '#8b5cf6',
  '#78716c', '#64748b',
]

function startEditTag (t) {
  editingTagName.value = t.name
  editingTagValue.value = t.label
}

async function saveTagRename (t) {
  const newLabel = editingTagValue.value.trim()
  editingTagName.value = null
  if (!newLabel || newLabel === t.label) return
  try {
    await renameTag(t.name, newLabel)
    t.label = newLabel
    t.name = newLabel
    Notify.create({ type: 'positive', message: `Tag renommé → "${newLabel}"`, timeout: 1500 })
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur: ' + e.message, timeout: 3000 })
  }
}

async function cycleTagColor (t) {
  const idx = TAG_COLORS.indexOf(t.color)
  const next = TAG_COLORS[(idx + 1) % TAG_COLORS.length]
  try {
    await updateTag(t.name, { color: next })
    t.color = next
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur: ' + e.message, timeout: 3000 })
  }
}

async function deleteDispatchTag (t) {
  Dialog.create({
    title: 'Supprimer le tag ?',
    message: `Le tag "${t.label}" sera retiré de tous les éléments.`,
    cancel: { flat: true, label: 'Annuler' },
    ok: { color: 'red', label: 'Supprimer', unelevated: true },
  }).onOk(async () => {
    try {
      await deleteTagApi(t.name)
      allDispatchTags.value = allDispatchTags.value.filter(x => x.name !== t.name)
      Notify.create({ type: 'positive', message: `Tag "${t.label}" supprimé`, timeout: 1500 })
    } catch (e) {
      Notify.create({ type: 'negative', message: 'Erreur: ' + e.message, timeout: 3000 })
    }
  })
}

// ── Delete ──

function confirmDelete () {
  Dialog.create({
    title: 'Supprimer ce ticket ?',
    message: `Le ticket ${props.docName} sera supprimé définitivement.`,
    cancel: { flat: true, label: 'Annuler' },
    ok: { color: 'red', label: 'Supprimer', unelevated: true },
    persistent: true,
  }).onOk(async () => {
    try {
      await deleteDoc('Issue', props.docName)
      Notify.create({ type: 'positive', message: 'Ticket supprimé', timeout: 2000 })
      emit('deleted', props.docName)
    } catch (e) {
      Notify.create({ type: 'negative', message: 'Erreur: ' + e.message, timeout: 3000 })
    }
  })
}

// ── Nested task tree ──

const rootJobs = computed(() => {
  // Jobs with no parent_job, or whose parent_job is not in this ticket's jobs
  const jobNames = new Set(props.dispatchJobs.map(j => j.name))
  return props.dispatchJobs.filter(j => !j.parent_job || !jobNames.has(j.parent_job))
    .sort((a, b) => (a.step_order || 0) - (b.step_order || 0))
})

const orphanJobs = computed(() => {
  // Jobs whose parent is in the list but weren't caught as roots or children
  // This handles edge cases
  const rootAndChildNames = new Set()
  function collectNames (jobs) {
    for (const j of jobs) {
      rootAndChildNames.add(j.name)
    }
  }
  collectNames(rootJobs.value)
  // Recursively collect all children
  function collectChildren (parentName) {
    const children = props.dispatchJobs.filter(j => j.parent_job === parentName)
    for (const c of children) {
      rootAndChildNames.add(c.name)
      collectChildren(c.name)
    }
  }
  for (const r of rootJobs.value) collectChildren(r.name)
  return props.dispatchJobs.filter(j => !rootAndChildNames.has(j.name))
})

// ── n8n webhook firing ──

async function fireWebhook (url, job) {
  if (!url) return
  try {
    await authFetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        event: url.includes('close') ? 'job_closed' : 'job_opened',
        job_name: job.name,
        job_subject: job.subject,
        job_status: job.status,
        job_type: job.job_type,
        ticket: props.doc?.name,
        customer: props.doc?.customer,
        timestamp: new Date().toISOString(),
      }),
    })
  } catch (err) {
    console.warn('[n8n webhook] fire failed:', err)
  }
}

function onJobCreated (job) {
  emit('dispatch-created', job)
}

function onJobDeleted (jobName) {
  emit('dispatch-deleted', jobName)
}

function onJobUpdated (jobName, data) {
  emit('dispatch-updated', jobName, data)
}

// Fermer/Rouvrir/Reporter : gérés par le composant partagé TicketStatusControl.

// ── Reply ──

async function sendReply () {
  if (!replyContent.value?.trim() || sendingReply.value) return
  sendingReply.value = true
  try {
    await createDoc('Communication', {
      communication_type: 'Communication',
      communication_medium: 'Other',
      sent_or_received: 'Sent',
      subject: props.title || props.docName,
      content: replyContent.value.trim(),
      sender: authStore.user || '',
      sender_full_name: senderFirst.value,
      reference_doctype: 'Issue',
      reference_name: props.docName,
    })
    replyContent.value = ''
    emit('reply-sent', props.docName)
    Notify.create({ type: 'positive', message: 'Reponse envoyee', timeout: 2000 })
  } catch {
    Notify.create({ type: 'negative', message: 'Erreur: reponse non envoyee', timeout: 3000 })
  } finally {
    sendingReply.value = false
  }
}

// ── Envoi par courriel (aller-retour : la réponse revient au ticket) ──

const showEmailDialog = ref(false)
const emailTo = ref('')
const emailCc = ref('')
const emailMessage = ref('')
const sendingEmail = ref(false)

function openEmailDialog () {
  emailTo.value = (props.doc?.raised_by && /.+@.+\..+/.test(props.doc.raised_by)) ? props.doc.raised_by : ''
  emailCc.value = ''
  emailMessage.value = replyContent.value?.trim() || ''
  showEmailDialog.value = true
}

async function sendTicketEmail () {
  if (!emailTo.value || sendingEmail.value) return
  sendingEmail.value = true
  try {
    const res = await fetch(`${HUB_URL}/conversations/ticket-email`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        issue: props.docName, to: emailTo.value.trim(), cc: emailCc.value.trim(),
        message: emailMessage.value.trim(), link: window.location.href,
        agentName: senderFull.value, // sortie courriel → expéditeur = prénom+nom de l'agent (pas « louis »)
      }),
    })
    const d = await res.json()
    if (d.ok) {
      Notify.create({ type: 'positive', message: 'Courriel envoyé — la réponse reviendra à ce ticket', timeout: 2800 })
      showEmailDialog.value = false
      replyContent.value = ''
      emit('reply-sent', props.docName)
    } else {
      Notify.create({ type: 'negative', message: d.error || 'Échec de l’envoi', timeout: 3500 })
    }
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur: ' + e.message, timeout: 3500 })
  } finally {
    sendingEmail.value = false
  }
}
</script>

<style scoped>
.issue-actions {
  display: flex;
  gap: 8px;
}
.issue-field-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 16px;
}
.if-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 0;
  font-size: 0.875rem;
  border-bottom: 1px solid #f1f5f9;
  min-height: 32px;
}
.if-label {
  font-size: 0.75rem;
  font-weight: 600;
  color: #6b7280;
  width: 80px;
  flex-shrink: 0;
}
.if-value {
  font-size: 0.875rem;
  color: #1f2937;
}
.dispatch-section {
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 10px 12px;
}
.dispatch-jobs-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.tag-manager {
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 8px 10px;
  max-height: 240px;
  overflow-y: auto;
}
.tag-mgr-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 0;
  border-bottom: 1px solid #f1f5f9;
}
.tag-mgr-row:last-child { border-bottom: none; }
.tag-mgr-dot {
  width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; cursor: pointer;
  transition: transform 0.1s;
}
.tag-mgr-dot:hover { transform: scale(1.3); }
.tag-mgr-label {
  flex: 1; font-size: 0.8rem; cursor: pointer; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tag-mgr-label:hover { text-decoration: underline dotted; }
.tag-mgr-edit {
  flex: 1; font-size: 0.8rem; border: 1px solid #cbd5e1; border-radius: 4px;
  padding: 2px 6px; outline: none; min-width: 0;
}
.tag-mgr-edit:focus { border-color: #6366f1; }
.tag-mgr-cat { flex-shrink: 0; font-size: 0.65rem; }
.tag-mgr-del { opacity: 0.3; transition: opacity 0.15s; }
.tag-mgr-row:hover .tag-mgr-del { opacity: 1; }

/* Client lié */
.cust-banner {
  display: flex; align-items: center; gap: 8px;
  background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 8px;
  padding: 5px 12px; min-height: 40px;
}
.cust-name { font-weight: 600; font-size: 0.9rem; color: #166534; cursor: pointer; }
.cust-name:hover { text-decoration: underline; }

/* Fil unifié en bulles */
.thread-wrap { display: flex; flex-direction: column; gap: 10px; }
.msg-bubble { border: 1px solid #e5e7eb; border-radius: 10px; padding: 8px 12px; background: #fff; }
.msg-bubble.msg-note { background: #fffbeb; border-color: #fde68a; } /* notes internes = ambre */
.msg-head { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.msg-avatar {
  width: 22px; height: 22px; border-radius: 50%; background: #e0e7ff; color: #4338ca;
  font-size: 0.62rem; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.msg-who { font-size: 0.8rem; font-weight: 600; color: #374151; }
.msg-when { font-size: 0.7rem; color: #9ca3af; margin-left: auto; white-space: nowrap; }
.msg-body { font-size: 0.85rem; color: #1f2937; line-height: 1.5; word-break: break-word; }
.msg-body :deep(img) { max-width: 100%; height: auto; }
</style>
