<template>
  <div class="mov-wrap q-pa-sm">
    <!-- Barre : navigation de mois + éditeur des besoins (heures requises par compétence) + génération des besoins solveur -->
    <div class="row items-center q-gutter-sm q-mb-sm">
      <q-btn flat round dense icon="chevron_left" @click="shiftMonth(-1)" />
      <div class="text-subtitle1 text-weight-bold" style="min-width:150px;text-align:center;text-transform:capitalize">{{ monthLabel }}</div>
      <q-btn flat round dense icon="chevron_right" @click="shiftMonth(1)" />
      <q-btn flat dense no-caps size="sm" icon="today" label="Ce mois" @click="goThisMonth" />
      <q-spinner v-if="loading" size="18px" color="primary" />
      <q-space />
      <q-btn dense flat no-caps size="sm" :color="showReq ? 'indigo' : 'grey-7'" icon="tune" :label="showReq ? 'Masquer les besoins' : 'Besoins par compétence'" @click="showReq = !showReq" />
    </div>

    <!-- Éditeur : heures REQUISES par compétence × type de jour (semaine / fin de sem. / férié) — remplace l'ancienne « Demande ». -->
    <q-card v-if="showReq" flat bordered class="q-mb-sm">
      <q-card-section class="q-py-sm">
        <div class="row items-center q-mb-xs">
          <div class="text-weight-medium">Besoins — heures requises par compétence</div>
          <q-space />
          <q-btn dense unelevated color="indigo" size="sm" no-caps icon="playlist_add_check" label="Générer les besoins du mois" :loading="generating" @click="generateRequirements">
            <q-tooltip class="bg-grey-9" style="max-width:280px">Traduit ces heures requises en <b>besoins de couverture</b> (Shift Requirements) pour le solveur « Générer », sur tout le mois affiché. Remplace les besoins existants du mois.</q-tooltip>
          </q-btn>
        </div>
        <table class="mov-req">
          <thead><tr><th style="text-align:left">Compétence</th><th>Semaine</th><th>Fin de sem.</th><th>Férié</th></tr></thead>
          <tbody>
            <tr v-for="sk in skills" :key="sk">
              <td style="text-align:left"><span class="mov-dot" :style="{ background: tagColor(sk) }"></span>{{ sk }}</td>
              <td><input type="number" min="0" step="1" class="mov-in" :value="reqOf(sk, 'weekday')" @change="setReq(sk, 'weekday', $event.target.value)" /></td>
              <td><input type="number" min="0" step="1" class="mov-in" :value="reqOf(sk, 'weekend')" @change="setReq(sk, 'weekend', $event.target.value)" /></td>
              <td><input type="number" min="0" step="1" class="mov-in" :value="reqOf(sk, 'holiday')" @change="setReq(sk, 'holiday', $event.target.value)" /></td>
            </tr>
            <tr v-if="!skills.length"><td colspan="4" class="text-grey-6 q-pa-sm">Aucune compétence — taggez des techniciens.</td></tr>
          </tbody>
        </table>
        <div class="text-caption text-grey-6 q-mt-xs">Heures/jour requises par compétence. La couverture = heures des techs <b>en quart</b> (hors garde, hors absents) qui ont la compétence. Case rouge = déficit.</div>
      </q-card-section>
    </q-card>

    <!-- Calendrier du mois : 1 case/jour, résumé en quart / absents + alertes de couverture -->
    <div class="mov-cal">
      <div v-for="d in DOW_LABELS" :key="'h' + d" class="mov-dowh">{{ d }}</div>
      <div v-for="(c, i) in monthCells" :key="i" class="mov-cell" :class="{ 'mov-empty': !c.inMonth, weekend: c.weekend, today: c.isToday, holiday: c.holiday, short: c.inMonth && c.deficits.length }">
        <template v-if="c.inMonth">
          <div class="mov-top"><span class="mov-daynum">{{ c.day }}</span><span v-if="c.holiday" class="mov-holi" title="Férié">★</span><q-space /><q-icon v-if="c.deficits.length" name="warning" color="negative" size="15px" /></div>
          <!-- Initiales des techs en quart (max 5) -->
          <div class="mov-inits">
            <span v-for="t in c.onShift.slice(0, 5)" :key="t.id" class="mov-init" :style="{ background: t.color }">{{ t.initials }}</span>
            <span v-if="c.onShift.length > 5" class="mov-more">+{{ c.onShift.length - 5 }}</span>
          </div>
          <!-- Compteurs + déficits -->
          <div class="mov-foot">
            <span class="mov-on" :class="{ zero: !c.onShift.length }">{{ c.onShift.length }} en quart</span>
            <span v-if="c.absent.length" class="mov-off">· {{ c.absent.length }} abs.</span>
          </div>
          <div v-if="c.deficits.length" class="mov-defs">
            <span v-for="df in c.deficits" :key="df.skill" class="mov-def">{{ df.skill }} −{{ df.deficit }}h</span>
          </div>
          <q-tooltip v-if="c.onShift.length || c.absent.length || c.coverage.length" anchor="center middle" self="center middle" class="bg-grey-9" style="font-size:11.5px;max-width:320px">
            <div class="text-weight-bold q-mb-xs">{{ c.iso }}<span v-if="c.holiday"> · férié</span></div>
            <div v-if="c.onShift.length"><b>{{ c.onShift.length }} en quart :</b> {{ c.onShift.map(t => t.name + ' (' + t.hours + 'h)').join(', ') }}</div>
            <div v-if="c.absent.length" class="q-mt-xs"><b>{{ c.absent.length }} absent(s) :</b> {{ c.absent.map(t => t.name + ' — ' + t.type).join(', ') }}</div>
            <div v-if="c.coverage.length" class="q-mt-xs"><b>Couverture :</b><br><span v-for="cov in c.coverage" :key="cov.skill" :class="{ 'text-negative': cov.deficit > 0 }">• {{ cov.skill }} : {{ cov.avail }}/{{ cov.req }}h<span v-if="cov.deficit > 0"> (manque {{ cov.deficit }}h)</span><br></span></div>
          </q-tooltip>
        </template>
      </div>
    </div>
    <div class="row items-center q-gutter-md q-mt-xs mov-legend">
      <span><i class="mov-lg mov-lg-on"></i>en quart</span>
      <span><i class="mov-lg mov-lg-short"></i>couverture insuffisante</span>
      <span><i class="mov-lg mov-lg-holi"></i>férié</span>
      <q-space />
      <span class="text-grey-6">{{ monthTotals.onDays }} jour(s) couvert(s) · {{ monthTotals.shortDays }} en déficit</span>
    </div>
  </div>
</template>

<script setup>
// Vue MOIS (tous techs) : résumé par jour = qui est EN QUART / ABSENT + alertes de COUVERTURE par compétence
// (heures requises vs heures en quart des techs qualifiés). Remplace l'ancien module « Demande » (besoins par template).
// Auto-contenu : charge ses propres données du mois (assignations, absences, fériés) ; besoins par compétence en localStorage.
import { ref, reactive, computed, watch, onMounted } from 'vue'
import { useQuasar } from 'quasar'
import * as roster from 'src/api/roster'

const props = defineProps({
  techs: { type: Array, default: () => [] },       // { id, name, skills:[], color_hex? }
  templates: { type: Array, default: () => [] },    // Shift Template (nom, hours, on_call) — pour l'unité de couverture + génération
  anchor: { type: String, default: '' },            // date ISO dans le mois à afficher (sinon aujourd'hui)
  refreshKey: { type: Number, default: 0 },
})
const emit = defineEmits(['generated'])
const $q = useQuasar()
const err = (e) => $q.notify({ type: 'negative', message: '' + (e.message || e) })

const DOW_LABELS = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim']
const MO = ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre']
const LS_SKILL_DEMAND = 'roster-skill-demand-v1'

const month = ref('') // 'YYYY-MM'
const loading = ref(false)
const generating = ref(false)
const showReq = ref(false)
const asgByDate = ref({})  // iso → [{ tech, hours }] (quarts réguliers, hors garde)
const absByKey = ref({})   // 'tech|iso' → type
const holiSet = ref(new Set())
const skillDemand = reactive(load())

function load () { try { return JSON.parse(localStorage.getItem(LS_SKILL_DEMAND) || '{}') } catch { return {} } }
function saveReq () { try { localStorage.setItem(LS_SKILL_DEMAND, JSON.stringify(skillDemand)) } catch (e) {} }
function reqOf (sk, slot) { return (skillDemand[sk] && skillDemand[sk][slot]) || 0 }
function setReq (sk, slot, v) { const n = Math.max(0, Number(v) || 0); if (!skillDemand[sk]) skillDemand[sk] = { weekday: 0, weekend: 0, holiday: 0 }; skillDemand[sk][slot] = n; saveReq() }

const skills = computed(() => [...new Set((props.techs || []).flatMap(t => t.skills || []))].sort())
const techById = computed(() => { const m = {}; for (const t of (props.techs || [])) m[t.id] = t; return m })
const tplByName = computed(() => Object.fromEntries((props.templates || []).map(t => [t.name, t])))
function tagColor (sk) { let h = 0; for (let i = 0; i < sk.length; i++) h = (h * 31 + sk.charCodeAt(i)) & 0xffffff; return 'hsl(' + (h % 360) + ',55%,55%)' }
function initials (name) { const p = String(name || '').trim().split(/\s+/); return ((p[0] || '')[0] || '').toUpperCase() + ((p[1] || '')[0] || '').toUpperCase() }
function techColor (t) { return (t && t.color_hex) || tagColor(t ? (t.name || t.id) : '?') }

function pad (n) { return String(n).padStart(2, '0') }
const monthLabel = computed(() => { if (!month.value) return ''; const [y, m] = month.value.split('-').map(Number); return MO[m - 1] + ' ' + y })
const monthDays = computed(() => { if (!month.value) return 0; const [y, m] = month.value.split('-').map(Number); return new Date(Date.UTC(y, m, 0)).getUTCDate() })
const monthStart = computed(() => month.value ? month.value + '-01' : '')

// dayType d'un jour → clé de besoin (semaine / fin de sem. / férié)
function dayType (iso, dow) { if (holiSet.value.has(iso)) return 'holiday'; return (dow === 0 || dow === 6) ? 'weekend' : 'weekday' }

const monthCells = computed(() => {
  if (!month.value) return []
  const [y, m] = month.value.split('-').map(Number)
  const first = new Date(Date.UTC(y, m - 1, 1))
  const lead = (first.getUTCDay() + 6) % 7 // lundi = 0
  const cells = []
  for (let i = 0; i < lead; i++) cells.push({ inMonth: false })
  const today = new Date().toLocaleDateString('en-CA', { timeZone: 'America/Toronto' })
  for (let day = 1; day <= monthDays.value; day++) {
    const iso = y + '-' + pad(m) + '-' + pad(day)
    const dow = new Date(iso + 'T12:00:00').getUTCDay()
    const type = dayType(iso, dow)
    // Techs en quart ce jour (hors garde), non absents.
    const onShift = []
    for (const a of (asgByDate.value[iso] || [])) {
      if (absByKey.value[a.tech + '|' + iso]) continue
      const t = techById.value[a.tech]; if (!t) continue
      onShift.push({ id: t.id, name: t.name, initials: initials(t.name), color: techColor(t), hours: a.hours, skills: t.skills || [] })
    }
    // Absents ce jour.
    const absent = []
    for (const t of (props.techs || [])) { const type2 = absByKey.value[t.id + '|' + iso]; if (type2) absent.push({ id: t.id, name: t.name, type: type2 }) }
    // Couverture par compétence : heures dispo (techs en quart ayant la compétence) vs requis.
    const coverage = []; const deficits = []
    for (const sk of skills.value) {
      const req = reqOf(sk, type); if (!req) continue
      let avail = 0; for (const t of onShift) if ((t.skills || []).includes(sk)) avail += Number(t.hours) || 0
      avail = Math.round(avail * 10) / 10
      const deficit = Math.max(0, Math.round((req - avail) * 10) / 10)
      coverage.push({ skill: sk, avail, req, deficit })
      if (deficit > 0) deficits.push({ skill: sk, deficit })
    }
    cells.push({ inMonth: true, day, iso, dow, weekend: dow === 0 || dow === 6, holiday: holiSet.value.has(iso), isToday: iso === today, onShift, absent, coverage, deficits })
  }
  while (cells.length % 7) cells.push({ inMonth: false })
  return cells
})
const monthTotals = computed(() => { let onDays = 0, shortDays = 0; for (const c of monthCells.value) { if (!c.inMonth) continue; if (c.onShift && c.onShift.length) onDays++; if (c.deficits && c.deficits.length) shortDays++ } return { onDays, shortDays } })

function initMonth () { const a = props.anchor || new Date().toLocaleDateString('en-CA', { timeZone: 'America/Toronto' }); month.value = a.slice(0, 7) }
function shiftMonth (n) { const [y, m] = month.value.split('-').map(Number); const d = new Date(Date.UTC(y, m - 1 + n, 1)); month.value = d.getUTCFullYear() + '-' + pad(d.getUTCMonth() + 1); loadMonth() }
function goThisMonth () { month.value = new Date().toLocaleDateString('en-CA', { timeZone: 'America/Toronto' }).slice(0, 7); loadMonth() }

async function loadMonth () {
  if (!month.value) return
  loading.value = true
  try {
    const start = monthStart.value; const days = monthDays.value
    const [asgRes, absRes, holiRes] = await Promise.all([
      roster.listAssignments(start, days).catch(() => null),
      roster.getAbsences(start, days).catch(() => null),
      roster.holidays(start, month.value + '-' + pad(days)).catch(() => null),
    ])
    const abd = {}
    for (const a of ((asgRes && asgRes.assignments) || [])) {
      const tp = tplByName.value[a.shift]; if (tp && tp.on_call) continue // garde exclue de la couverture
      ;(abd[a.date] || (abd[a.date] = [])).push({ tech: a.tech, hours: Number(a.hours) || (tp ? tp.hours : 0) || 0 })
    }
    asgByDate.value = abd
    absByKey.value = (absRes && absRes.absences) || {}
    holiSet.value = new Set(((holiRes && (holiRes.holidays || [])) || []).map(h => h.date || h))
  } catch (e) { err(e) } finally { loading.value = false }
}

// Génère les Shift Requirements du MOIS à partir des heures requises par compétence (remplace les besoins existants du mois).
// Traduction : effectif = ⌈heures requises ÷ heures du modèle de jour⌉, compétence requise = la compétence.
async function generateRequirements () {
  const dayTpl = (props.templates || []).find(t => !t.on_call && Number(t.hours) >= 7 && Number(t.hours) <= 9) || (props.templates || []).find(t => !t.on_call)
  if (!dayTpl) { $q.notify({ type: 'warning', message: 'Aucun modèle de shift de jour — créez-en un (Types de shift).' }); return }
  const tplH = Number(dayTpl.hours) || 8
  generating.value = true
  try {
    await roster.clearRequirements(monthStart.value, monthDays.value)
    const reqs = []
    for (const c of monthCells.value) {
      if (!c.inMonth) continue
      for (const sk of skills.value) {
        const req = reqOf(sk, dayType(c.iso, c.dow)); if (!req) continue
        reqs.push({ requirement_date: c.iso, shift_template: dayTpl.name, zone: '', required_count: Math.max(1, Math.ceil(req / tplH)), required_skills: sk })
      }
    }
    if (reqs.length) await roster.bulkRequirements(reqs)
    $q.notify({ type: 'positive', message: reqs.length + ' besoin(s) généré(s) pour ' + monthLabel.value + ' — le solveur « Générer » les utilise' })
    emit('generated')
  } catch (e) { err(e) } finally { generating.value = false }
}

watch(() => props.anchor, () => { const wasMonth = month.value; initMonth(); if (month.value !== wasMonth) loadMonth() })
watch(() => props.refreshKey, () => loadMonth())
onMounted(() => { initMonth(); loadMonth() })
</script>

<style scoped>
.mov-wrap { max-width: 1100px; }
.mov-req { border-collapse: collapse; }
.mov-req th { font-size: 11px; color: #64748b; font-weight: 600; padding: 2px 10px; }
.mov-req td { padding: 2px 10px; font-size: 12.5px; }
.mov-in { width: 64px; padding: 3px 6px; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 12px; text-align: center; }
.mov-dot { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 6px; vertical-align: middle; }
.mov-cal { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.mov-dowh { text-align: center; font-size: 10.5px; color: #64748b; font-weight: 700; padding-bottom: 2px; }
.mov-cell { position: relative; min-height: 92px; border: 1px solid #e2e8f0; border-radius: 8px; padding: 5px 6px; background: #fff; display: flex; flex-direction: column; gap: 3px; }
.mov-empty { border: none; background: transparent; }
.mov-cell.weekend { background: #f8fafc; }
.mov-cell.today { border-color: #2563eb; box-shadow: inset 0 0 0 1px #2563eb; }
.mov-cell.holiday { background: #fffbeb; }
.mov-cell.short { border-color: #ef4444; }
.mov-top { display: flex; align-items: center; gap: 4px; }
.mov-daynum { font-size: 12px; color: #334155; font-weight: 700; }
.mov-holi { color: #b45309; font-size: 11px; }
.mov-inits { display: flex; flex-wrap: wrap; gap: 2px; }
.mov-init { width: 19px; height: 19px; border-radius: 50%; color: #fff; font-size: 9.5px; font-weight: 700; display: inline-flex; align-items: center; justify-content: center; }
.mov-more { font-size: 10px; color: #64748b; align-self: center; }
.mov-foot { font-size: 10.5px; color: #475569; margin-top: auto; }
.mov-on.zero { color: #94a3b8; }
.mov-off { color: #64748b; }
.mov-defs { display: flex; flex-wrap: wrap; gap: 2px; }
.mov-def { font-size: 9.5px; font-weight: 700; color: #b91c1c; background: #fee2e2; border-radius: 4px; padding: 0 4px; }
.mov-legend { font-size: 10.5px; color: #475569; }
.mov-lg { display: inline-block; width: 10px; height: 10px; border-radius: 2px; vertical-align: middle; margin-right: 4px; }
.mov-lg-on { background: #2563eb; }
.mov-lg-short { background: #ef4444; }
.mov-lg-holi { background: #fbbf24; }
</style>
