<!--
  DispatchJobDetail — vue NATIVE d'un Dispatch Job dans DetailModal (SECTION_MAP['Dispatch Job']).
  AVANT : ouvrir un job (ex. timeline fiche client) tombait sur la grille générique + bouton desk ERPNext.
  MAINTENANT : même chrome moderne que le ticket, en assemblant les MÊMES modules partagés que le volet
  Planification et l'onglet « Intervention sur site » d'IssueDetail — zéro duplication.
-->
<template>
  <div>
    <!-- Client + intitulé -->
    <div v-if="!hideCustomerLink && doc.customer" class="dj-cust">
      <q-icon name="person" size="16px" class="q-mr-xs" />
      <router-link :to="'/clients/' + encodeURIComponent(doc.customer)" class="erp-link">{{ doc.customer_name || doc.customer }}</router-link>
    </div>

    <!-- Statut + priorité + planif -->
    <div class="dj-meta">
      <q-select dense options-dense outlined v-model="statusModel" :options="STATUS_OPTS" emit-value map-options
        label="Statut" style="min-width:150px" @update:model-value="onStatus" :loading="savingStatus" />
      <span v-if="doc.priority" class="dj-chip" :class="'dj-prio-' + String(doc.priority).toLowerCase()">{{ prioLabel }}</span>
      <span v-if="scheduledLabel" class="dj-when"><q-icon name="event" size="15px" /> {{ scheduledLabel }}</span>
      <span v-if="doc.job_type" class="dj-chip dj-type">{{ doc.job_type }}</span>
    </div>
    <div v-if="doc.address" class="dj-addr"><q-icon name="place" size="15px" /> {{ doc.address }}</div>
    <div v-if="doc.legacy_ticket_id" class="dj-legacy">Ticket F #{{ doc.legacy_ticket_id }}</div>

    <q-separator class="q-my-sm" />

    <!-- Carte + tracé GPS -->
    <JobMapModule :name="doc.name || docName" :lat="doc.latitude" :lon="doc.longitude"
      :tech-id="doc.assigned_tech || ''" :iso="iso" :can-track="canTrack" :today-iso="todayIso" />
    <!-- Suivi terrain (géofencing) -->
    <GeofenceTimeline :name="doc.name || docName" />
    <!-- Photos du tech (preuve d'adresse géo-vérifiée + appareil) + journal terrain -->
    <JobMediaModule :name="doc.name || docName" />
    <!-- Durée estimée -->
    <DurationField :name="doc.name || docName" :dur-h="Number(doc.duration_h) || 1" @updated="p => { doc.duration_h = p.duration_h }" />
    <!-- Compétences requises -->
    <RequiredSkillsField :name="doc.name || docName" :skills="skills" :all-tags="allTags" :get-color="getTagColor"
      @updated="arr => { doc.required_skill = (arr && arr[0]) || '' }" @create="onCreateTag" />
    <!-- Équipe / assignation (lead + assistants) — chemin roster simple (avec garde-fou 409 F) -->
    <div class="q-mt-sm">
      <JobTeamField :name="doc.name || docName" :tech-id="doc.assigned_tech || ''" :tech-name="techName"
        :tech-options="techOptions" :can-onsite="true"
        @assign-lead="onAssignLead" @unassign-lead="onUnassignLead" @updated="() => $emit('dispatch-updated', doc.name || docName)" />
    </div>
    <!-- Fil du billet + réponse (si lié à un ticket F) -->
    <template v-if="doc.legacy_ticket_id">
      <JobThread ref="threadRef" :lid="doc.legacy_ticket_id" />
      <JobReplyBox :name="doc.name || docName" :lid="doc.legacy_ticket_id" @sent="() => { threadRef && threadRef.reload(); $emit('reply-sent', doc.name || docName) }" />
    </template>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'
import { Notify } from 'quasar'
import { updateDoc } from 'src/api/erp'
import { useTagCatalog } from 'src/composables/useTagCatalog'
import * as roster from 'src/api/roster'
import JobMapModule from './modules/JobMapModule.vue'
import GeofenceTimeline from './modules/GeofenceTimeline.vue'
import JobMediaModule from './modules/JobMediaModule.vue'
import DurationField from './modules/DurationField.vue'
import RequiredSkillsField from './modules/RequiredSkillsField.vue'
import JobTeamField from './modules/JobTeamField.vue'
import JobThread from './modules/JobThread.vue'
import JobReplyBox from './modules/JobReplyBox.vue'

const props = defineProps({
  doc: { type: Object, required: true },
  docName: { type: String, default: '' },
  title: { type: String, default: '' },
  hideCustomerLink: { type: Boolean, default: false },
})
const emit = defineEmits(['navigate', 'dispatch-updated', 'reply-sent'])

const STATUS_OPTS = [
  { label: 'À planifier', value: 'open' },
  { label: 'Assigné', value: 'assigned' },
  { label: 'En cours', value: 'In Progress' },
  { label: 'En attente', value: 'On Hold' },
  { label: 'Complété', value: 'Completed' },
  { label: 'Annulé', value: 'Cancelled' },
]
const statusModel = ref(props.doc.status || 'open')
const savingStatus = ref(false)
async function onStatus (v) {
  const prev = props.doc.status
  savingStatus.value = true
  try {
    await updateDoc('Dispatch Job', props.doc.name || props.docName, { status: v })
    props.doc.status = v
    Notify.create({ type: 'positive', message: 'Statut : ' + (STATUS_OPTS.find(o => o.value === v) || {}).label, timeout: 1400 })
    emit('dispatch-updated', props.doc.name || props.docName, { status: v })
  } catch (e) { statusModel.value = prev; Notify.create({ type: 'negative', message: 'Statut : ' + (e.message || e), timeout: 3000 }) } finally { savingStatus.value = false }
}

const prioLabel = computed(() => ({ high: 'Urgent', medium: 'Moyenne', low: 'Basse' })[String(props.doc.priority || '').toLowerCase()] || props.doc.priority)
const scheduledLabel = computed(() => {
  const d = props.doc.scheduled_date; if (!d) return ''
  const t = props.doc.start_time ? String(props.doc.start_time).slice(0, 5) : ''
  return d + (t ? ' · ' + t : '')
})
const iso = computed(() => String(props.doc.scheduled_date || '').slice(0, 10))
const todayIso = computed(() => new Date().toLocaleDateString('en-CA', { timeZone: 'America/Toronto' }))
const canTrack = computed(() => {
  const i = iso.value; if (!i) return false
  const n = todayIso.value
  const yest = new Date(Date.parse(n + 'T12:00:00Z') - 86400000).toISOString().slice(0, 10)
  return i === n || i === yest
})
const skills = computed(() => { const s = props.doc.required_skill; return s ? [s] : [] })

// Tag catalog (source unique) pour les compétences requises.
const { allTags, getColor: getTagColor, load: loadTagCatalog, createTag: createCatTag } = useTagCatalog()
async function onCreateTag ({ label, color }) { try { await createCatTag({ label, color }) } catch (e) { /* best-effort */ } }

// Techs (chargés à l'ouverture) pour l'assignation lead/équipe.
const jobTechs = ref([])
const techOptions = computed(() => (jobTechs.value || []).map(t => ({ label: t.name + ((t.skills || []).length ? ' · ' + t.skills.slice(0, 4).join(', ') : ''), value: t.id })))
const techName = computed(() => { const id = props.doc.assigned_tech; if (!id) return ''; const t = (jobTechs.value || []).find(x => x.id === id); return (t && t.name) || String(id) })
const threadRef = ref(null)

async function onAssignLead (techId) {
  const name = props.doc.name || props.docName; if (!techId || !name) return
  try {
    await roster.assignJob(name, techId, iso.value || undefined) // 409-aware (garde F) via api/roster
    props.doc.assigned_tech = techId
    const t = (jobTechs.value || []).find(x => x.id === techId)
    Notify.create({ type: 'positive', message: 'Assigné à ' + ((t && t.name) || techId), timeout: 1800 })
    emit('dispatch-updated', name, { assigned_tech: techId })
  } catch (e) { Notify.create({ type: 'negative', message: e.message || String(e), timeout: 3000 }) }
}
async function onUnassignLead () {
  const name = props.doc.name || props.docName; if (!name) return
  try {
    await roster.unassignJobRoster(name); props.doc.assigned_tech = ''
    Notify.create({ type: 'info', message: 'Renvoyé au pool', timeout: 1600 })
    emit('dispatch-updated', name, { assigned_tech: '' })
  } catch (e) { Notify.create({ type: 'negative', message: e.message || String(e), timeout: 3000 }) }
}

onMounted(async () => {
  loadTagCatalog()
  try { const r = await roster.listTechnicians(); jobTechs.value = Array.isArray(r) ? r : ((r && r.technicians) || []) } catch { jobTechs.value = [] }
})
</script>

<style scoped>
.dj-cust { font-size: 13.5px; margin-bottom: 8px; }
.dj-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.dj-when { font-size: 12.5px; color: #455a64; }
.dj-addr { font-size: 12.5px; color: #546e7a; margin-bottom: 4px; }
.dj-legacy { font-size: 11px; color: #90a4ae; }
.dj-chip { font-size: 11px; font-weight: 700; border-radius: 999px; padding: 2px 9px; background: #eceff1; color: #455a64; }
.dj-type { background: #e8eaf6; color: #3949ab; }
.dj-prio-high { background: #fbe7e7; color: #b83232; }
.dj-prio-medium { background: #fbf0dc; color: #996515; }
.dj-prio-low { background: #eceff1; color: #607d8b; }
</style>
