<template>
  <q-page class="q-pa-md" style="background:#f7f8fa">
    <div class="row items-center q-mb-md">
      <div>
        <div class="text-h6 text-weight-bold">Sync F ↔ ERPNext <span class="text-grey-6 text-subtitle2">— tableau de bord</span></div>
        <div class="text-caption text-grey-7">Moteurs modulaires (un par domaine), orchestration + vue unifiées. F autoritaire ; ERPNext reflète F.</div>
      </div>
      <q-space />
      <q-btn outline color="grey-7" icon="confirmation_number" label="Rafraîchir tickets" :disable="syncing" no-caps class="q-mr-sm" @click="startSync('tickets')"><q-tooltip>Tirer les tickets legacy → Dispatch (lourd ~1 min ; sinon auto aux 15 min + sync d'assignation live en Planification)</q-tooltip></q-btn>
      <q-btn unelevated color="positive" icon="sync" label="Synchroniser maintenant" :loading="syncing" no-caps class="q-mr-sm" @click="startSync('core')"><q-tooltip>Comptes + paiements + soldes, tout de suite (~30 s). Factures/services : cron horaire.</q-tooltip></q-btn>
      <q-btn flat color="primary" icon="refresh" label="Recalculer" :loading="loading || sLoading" @click="reload" no-caps />
    </div>

    <!-- Progression de la synchronisation manuelle -->
    <q-card v-if="syncRun" flat bordered class="q-mb-md" :class="syncRun.running ? 'bg-blue-1' : 'bg-grey-2'">
      <q-card-section class="q-py-sm">
        <div class="row items-center q-mb-xs">
          <q-icon :name="syncRun.running ? 'sync' : 'check_circle'" :color="syncRun.running ? 'blue-7' : 'green-7'" :class="{ spin: syncRun.running }" class="q-mr-sm" />
          <span class="text-weight-medium">{{ syncRun.running ? 'Synchronisation en cours…' : 'Synchronisation terminée' }}</span>
          <span class="text-caption text-grey-7 q-ml-sm">{{ syncRun.elapsed_s }} s · {{ syncRun.done }}/{{ syncRun.total }}</span>
          <q-space />
          <q-btn v-if="!syncRun.running" flat dense round size="sm" icon="close" @click="syncRun = null" />
        </div>
        <q-linear-progress :value="syncRun.total ? syncRun.done / syncRun.total : 0" :indeterminate="syncRun.running && !syncRun.done" color="teal" rounded size="7px" class="q-mb-sm" />
        <div class="row q-col-gutter-md">
          <div v-for="s in syncRun.steps" :key="s.key" class="col-auto row items-center">
            <q-icon :name="stepIcon(s.status)" :color="stepColor(s.status)" size="16px" :class="{ spin: s.status === 'running' }" class="q-mr-xs" />
            <span class="text-caption">{{ s.label }}<span v-if="s.detail" class="text-grey-7"> — {{ s.detail }}</span></span>
          </div>
        </div>
        <div v-if="syncRun.note" class="text-caption text-grey-6 q-mt-xs"><q-icon name="info" size="13px" /> {{ syncRun.note }}</div>
      </q-card-section>
    </q-card>

    <!-- ── TABLEAU DE BORD DE RÉCONCILIATION : comptes bruts F vs ERPNext, par module ── -->
    <q-card flat bordered class="q-mb-md">
      <q-card-section class="row items-center q-py-sm">
        <q-icon name="balance" size="20px" color="grey-7" class="q-mr-sm" />
        <div>
          <div class="text-weight-bold">Réconciliation par module — F vs ERPNext</div>
          <div class="text-caption text-grey-6">Comptes bruts de chaque côté. Écart = F − ERPNext (positif = manque dans ERPNext).</div>
        </div>
        <q-space />
        <q-btn flat dense round size="sm" icon="refresh" :loading="sbLoading" @click="loadScoreboard"><q-tooltip>Recalculer les écarts</q-tooltip></q-btn>
      </q-card-section>
      <q-markup-table v-if="scoreboard" flat dense wrap-cells separator="horizontal">
        <thead>
          <tr class="text-grey-7">
            <th class="text-left">Module</th>
            <th class="text-right">F (legacy)</th>
            <th class="text-right">ERPNext</th>
            <th class="text-right">Écart</th>
            <th class="text-left" style="min-width:180px">Note</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="e in scoreboard.entities" :key="e.key">
            <td class="text-left text-weight-medium">{{ e.label }}</td>
            <td class="text-right">{{ fmtN(e.f) }}</td>
            <td class="text-right">{{ fmtN(e.erp) }}</td>
            <td class="text-right text-weight-bold" :class="deltaClass(e.delta)">
              <q-icon v-if="e.delta === 0" name="check_circle" size="14px" class="q-mr-xs" />{{ e.delta == null ? 'n/a' : (e.delta > 0 ? '+' : '') + fmtN(e.delta) }}
            </td>
            <td class="text-left text-caption text-grey-7">{{ e.note }}</td>
          </tr>
        </tbody>
      </q-markup-table>
      <q-card-section v-if="scoreboard" class="q-pt-sm text-caption text-grey-6 row items-center q-gutter-x-md">
        <span>{{ fmtN(scoreboard.resiliated_f_accounts) }} comptes F résiliés</span>
        <span>·</span>
        <span :class="scoreboard.ghost_active_subscriptions ? 'text-orange-9 text-weight-bold' : 'text-green-8'">
          <q-icon :name="scoreboard.ghost_active_subscriptions ? 'rate_review' : 'check_circle'" size="14px" /> {{ fmtN(scoreboard.ghost_active_subscriptions) }} actifs sur compte résilié — à revoir
        </span>
        <q-space />
        <span v-if="scoreboard.generated_at">{{ new Date(scoreboard.generated_at).toLocaleString('fr-CA') }}</span>
      </q-card-section>
      <q-card-section v-else class="text-caption text-grey-6"><q-spinner size="16px" class="q-mr-xs" /> Calcul des écarts par module…</q-card-section>
    </q-card>

    <!-- ── BANDE UNIFIÉE : tous les domaines en ordre de dépendance (DAG) ── -->
    <q-card flat bordered class="q-mb-md">
      <q-card-section class="row items-center q-py-sm">
        <div class="text-weight-bold">État par domaine</div>
        <span class="text-caption text-grey-6 q-ml-sm">— ordre d'exécution = dépendances (un module ne tourne jamais avant ses prérequis)</span>
        <q-space />
        <template v-if="health.healthy != null">
          <q-badge v-if="health.healthy" color="green-2" text-color="green-9"><q-icon name="check_circle" size="13px" class="q-mr-xs" />facturation à jour</q-badge>
          <q-badge v-else color="amber-1" text-color="amber-9"><q-icon name="warning" size="13px" class="q-mr-xs" />facturation à vérifier</q-badge>
          <span v-if="health.last_cycle_min_ago != null" class="text-caption text-grey-6 q-ml-sm">dernier cycle il y a {{ health.last_cycle_min_ago }} min</span>
        </template>
      </q-card-section>
      <q-separator />
      <div class="row q-pa-sm items-stretch">
        <div class="col-6 col-md-2 q-pa-xs" v-for="d in domains" :key="d.key">
          <q-card flat bordered class="dcard fit">
            <div class="row items-center no-wrap q-mb-xs">
              <q-badge rounded color="green-1" text-color="green-9" class="q-mr-xs">{{ d.order }}</q-badge>
              <span class="text-weight-medium">{{ d.label }}</span>
              <q-space />
              <q-icon :name="d.icon" size="15px" :color="d.iconColor" />
            </div>
            <div class="num" :class="d.colorClass">{{ d.value }}</div>
            <div class="lbl">{{ d.metric }}</div>
            <div class="text-caption text-grey-6 ellipsis" :title="d.sub">{{ d.sub }}</div>
          </q-card>
        </div>
      </div>
      <q-card-section class="text-caption text-grey-7 q-pt-none">
        <q-icon name="account_tree" size="14px" /> clients → adresses · services · tickets → factures → paiements.
        <code>disabled</code>/<code>is_suspended</code> = dérivé des services (jamais synchronisé) — hors « à réviser ».
      </q-card-section>
    </q-card>

    <q-banner dense rounded class="bg-green-1 text-green-9 q-mb-md">
      <template #avatar><q-icon name="lock" color="green-7" /></template>
      <b>Lecture seule.</b> Politique : <b>ADD</b> = remplir un champ vide (sûr) · <b>CHANGE</b> = valeur divergente (révision) ·
      <b>NEVER-CLOBBER</b> = F vide → jamais écrasé · statut client (<code>disabled</code>) = dérivé, jamais synchronisé.
    </q-banner>

    <div class="text-subtitle2 text-weight-bold text-grey-8 q-mb-sm"><q-icon name="group" size="18px" /> Détail clients <span class="text-caption text-grey-6">— aperçu legacy-sync</span></div>

    <div v-if="loading" class="column items-center q-pa-xl text-grey-6">
      <q-spinner-dots size="40px" color="primary" /><div class="q-mt-sm">Calcul de l'aperçu clients… (~10 s)</div>
    </div>
    <div v-else-if="err" class="text-negative q-pa-md">{{ err }}</div>

    <template v-else-if="rep">
      <div class="row q-col-gutter-md q-mb-md">
        <div class="col-6 col-md-3"><q-card flat bordered class="stat"><div class="num text-info">{{ cs.would_create }}</div><div class="lbl">Comptes à créer <HelpHint title="Créer tout (F → OPS)">Crée dans ERPNext <b>tous</b> les comptes F qui n'ont pas encore de fiche client — pas seulement ceux facturés récemment. Politique <b>F autoritaire</b> : le nom n'est jamais écrasé, le statut est dérivé des services (jamais imposé), et l'opération est <b>idempotente</b> (relançable sans doublon).</HelpHint></div><q-btn v-if="cs.would_create" dense unelevated color="info" size="sm" no-caps icon="group_add" label="Créer tout" class="q-mt-xs" :disable="syncing" @click="confirmCreateAll"><q-tooltip>Créer dans ERPNext tous les comptes F qui n'existent pas encore (F→OPS), pas seulement ceux facturés récemment.</q-tooltip></q-btn></q-card></div>
        <div class="col-6 col-md-3"><q-card flat bordered class="stat"><div class="num text-green-8">{{ cs.safe_add }}</div><div class="lbl">À enrichir (sûr)</div></q-card></div>
        <div class="col-6 col-md-3"><q-card flat bordered class="stat"><div class="num text-warning">{{ cs.needs_review }}</div><div class="lbl">À réviser (actionnable)</div></q-card></div>
        <div class="col-6 col-md-3"><q-card flat bordered class="stat"><div class="num text-grey-7">{{ cs.unchanged }}</div><div class="lbl">Inchangés</div></q-card></div>
      </div>

      <div class="row q-col-gutter-md">
        <div class="col-12 col-md-4">
          <q-card flat bordered>
            <q-card-section class="text-weight-bold bg-green-1 text-green-9">Enrichissements sûrs (ADD) — {{ cs.safe_add }} clients</q-card-section>
            <q-list dense separator>
              <q-item v-for="(n,f) in rep.customers.add_by_field" :key="f"><q-item-section>{{ f }}</q-item-section><q-item-section side class="text-weight-bold">{{ n }}</q-item-section></q-item>
              <q-item v-if="!Object.keys(rep.customers.add_by_field||{}).length"><q-item-section class="text-grey-6">Aucun</q-item-section></q-item>
            </q-list>
            <q-card-section class="text-caption text-grey-7">Remplit des champs ERPNext vides (email/tél…). Réversible. <code>applySafeAdds(confirm:'SAFE-ADD')</code> — non exécuté.</q-card-section>
          </q-card>
        </div>
        <div class="col-12 col-md-4">
          <q-card flat bordered>
            <q-card-section class="text-weight-bold bg-orange-1 text-warning row items-center q-py-sm">
              <span>À réviser (CHANGE) — {{ cs.needs_review }}</span>
              <q-space />
              <q-btn dense unelevated color="warning" size="sm" no-caps icon="done_all" label="Tout appliquer" :loading="batchApplying === 'tous'" :disable="!!batchApplying || !cs.needs_review" @click="confirmApplyAll" />
            </q-card-section>
            <q-list dense separator>
              <q-item v-for="(n,f) in rep.customers.review_by_field" :key="f">
                <q-item-section>{{ f }}</q-item-section>
                <q-item-section side>
                  <div class="row items-center no-wrap q-gutter-xs">
                    <q-badge color="amber-1" text-color="amber-9">{{ n }}</q-badge>
                    <q-btn dense flat size="sm" icon="visibility" color="grey-7" @click="showDetail(f)"><q-tooltip>Voir le détail</q-tooltip></q-btn>
                    <q-btn dense flat color="positive" size="sm" no-caps label="Appliquer" :loading="batchApplying === f" :disable="!!batchApplying || f === 'customer_name'" @click="applyBatch(f)"><q-tooltip v-if="f === 'customer_name'">Nom protégé — jamais écrasé</q-tooltip></q-btn>
                  </div>
                </q-item-section>
              </q-item>
              <q-item v-if="!Object.keys(rep.customers.review_by_field||{}).length"><q-item-section class="text-grey-6">Aucun</q-item-section></q-item>
            </q-list>
            <q-card-section v-if="reviewDetail" class="bg-grey-1 q-py-sm">
              <div class="row items-center q-mb-xs"><span class="text-caption text-weight-medium">{{ reviewDetail.field }} — {{ reviewDetail.total }} changement(s)</span><q-space /><q-btn flat dense round size="xs" icon="close" @click="reviewDetail = null" /></div>
              <div v-for="(it,i) in reviewDetail.items" :key="i" class="text-caption ellipsis">{{ it.customer_name }} <span class="text-grey-6">#{{ it.legacy_account_id }}</span> : <span v-for="d in it.change" :key="d.field"><s>{{ d.from }}</s> → <b>{{ d.to }}</b></span></div>
              <div v-if="reviewDetail.total > reviewDetail.items.length" class="text-caption text-grey-6">… +{{ reviewDetail.total - reviewDetail.items.length }} autres</div>
            </q-card-section>
            <q-card-section class="text-caption text-grey-7">F gagne, sur décision. Le nom n'est jamais écrasé. Applique par champ ou tout d'un coup.</q-card-section>
          </q-card>
        </div>
        <div class="col-12 col-md-4">
          <q-card flat bordered class="q-mb-md">
            <q-card-section class="text-weight-bold bg-blue-grey-1 text-blue-grey-9">Statut — dérivé (non-actionnable)</q-card-section>
            <q-list dense separator>
              <q-item><q-item-section>F actif / ERPNext désactivé</q-item-section><q-item-section side class="text-weight-bold">{{ sd.f_active_erp_disabled }}</q-item-section></q-item>
              <q-item><q-item-section>F inactif / ERPNext actif</q-item-section><q-item-section side class="text-weight-bold">{{ sd.f_inactive_erp_enabled }}</q-item-section></q-item>
            </q-list>
            <q-card-section class="text-caption text-grey-7">Statut dérivé des abonnements — <b>jamais synchronisé</b>. Un client hors zone reste vendable (cross-sell), pas « churn ».</q-card-section>
          </q-card>
          <q-card flat bordered>
            <q-card-section class="text-weight-bold">Never-clobber (protégés)</q-card-section>
            <q-list dense separator>
              <q-item v-for="(n,f) in rep.customers.never_clobber_by_field" :key="f"><q-item-section>{{ f }}</q-item-section><q-item-section side class="text-weight-bold">{{ n }}</q-item-section></q-item>
              <q-item v-if="!Object.keys(rep.customers.never_clobber_by_field||{}).length"><q-item-section class="text-grey-6">Aucun</q-item-section></q-item>
            </q-list>
          </q-card>
        </div>
      </div>

      <div class="row q-col-gutter-md q-mt-xs">
        <div class="col-12 col-md-6">
          <q-card flat bordered>
            <q-card-section class="text-weight-bold">Nouveaux comptes (échantillon)</q-card-section>
            <q-markup-table flat dense>
              <thead><tr><th class="text-left">legacy_account_id</th><th class="text-left">Nom</th><th class="text-left">Statut F</th></tr></thead>
              <tbody><tr v-for="c in rep.customers.creates_sample" :key="c.legacy_account_id"><td>{{ c.legacy_account_id }}</td><td>{{ c.customer_name }}</td><td>{{ c.status }}</td></tr></tbody>
            </q-markup-table>
          </q-card>
        </div>
        <div class="col-12 col-md-6">
          <q-card flat bordered>
            <q-card-section class="text-weight-bold">À réviser (échantillon)</q-card-section>
            <q-list dense separator>
              <q-item v-for="r in rep.customers.needs_review_sample" :key="r.legacy_account_id">
                <q-item-section>
                  <q-item-label>{{ r.customer_name }} <span class="text-grey-6">#{{ r.legacy_account_id }}</span></q-item-label>
                  <q-item-label caption><span v-for="d in r.change" :key="d.field" class="chg">{{ d.field }}: <s>{{ d.from }}</s> → <b>{{ d.to }}</b></span></q-item-label>
                </q-item-section>
                <q-item-section side>
                  <q-btn dense flat color="positive" icon="check" label="Appliquer" size="sm" no-caps :loading="applyingId === r.legacy_account_id" @click="applyChange(r)">
                    <q-tooltip>Accepter la valeur de F (écrit dans ERPNext). Réversible. Le nom n'est jamais écrasé.</q-tooltip>
                  </q-btn>
                </q-item-section>
              </q-item>
              <q-item v-if="!(rep.customers.needs_review_sample||[]).length"><q-item-section class="text-grey-6">Aucun</q-item-section></q-item>
            </q-list>
          </q-card>
        </div>
      </div>

      <div class="text-caption text-grey-6 q-mt-md">Aperçu clients calculé {{ rep.started_at }} · F {{ cs.f_total }} comptes / ERPNext {{ cs.erp_total }}.</div>
    </template>
  </q-page>
</template>

<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import { useQuasar } from 'quasar'
import { HUB_URL } from 'src/config/hub'
import HelpHint from 'src/components/shared/HelpHint.vue'

const $q = useQuasar()

const loading = ref(false)
const err = ref('')
const rep = ref(null)
const cs = computed(() => (rep.value && rep.value.customers.summary) || {})
const sd = computed(() => (rep.value && rep.value.customers.status_divergence) || {})

// ── Bande unifiée (rapide, /sync/status) ──
const sLoading = ref(false)
const status = ref(null)
// ── Tableau de bord de réconciliation (comptes bruts F vs ERPNext, /legacy-sync/scoreboard) ──
const scoreboard = ref(null)
const sbLoading = ref(false)
const health = computed(() => (status.value && status.value.billing_health) || {})
const fmtN = (v) => v == null ? '—' : Number(v).toLocaleString('fr-CA', { maximumFractionDigits: 0 })
const fmtCur = (v) => v == null ? '—' : '$' + Number(v).toLocaleString('fr-CA', { maximumFractionDigits: 0 })
const lagCls = (l) => (l == null ? 'text-grey-6' : l > 0 ? 'text-warning' : 'text-green-8')
const createCls = (n) => (n == null ? 'text-grey-6' : n > 0 ? 'text-info' : 'text-green-8')
const domains = computed(() => {
  const m = (status.value && status.value.modules) || {}
  const okIcon = (cond) => cond ? 'check_circle' : 'pending'
  return [
    { key: 'clients', label: 'Clients', order: 1, value: fmtN(m.clients?.to_create), metric: 'à créer', sub: `ERPNext ${fmtN(m.clients?.erp)}`, colorClass: createCls(m.clients?.to_create), icon: okIcon(!m.clients?.to_create), iconColor: m.clients?.to_create ? 'blue-5' : 'green-5' },
    { key: 'adresses', label: 'Adresses', order: 2, value: fmtN(m.adresses?.to_create), metric: 'à créer', sub: `ERPNext ${fmtN(m.adresses?.erp)}`, colorClass: createCls(m.adresses?.to_create), icon: okIcon(!m.adresses?.to_create), iconColor: m.adresses?.to_create ? 'blue-5' : 'green-5' },
    { key: 'services', label: 'Services', order: 2, value: fmtN(m.services?.to_create), metric: 'à créer', sub: `ERPNext ${fmtN(m.services?.erp)}`, colorClass: createCls(m.services?.to_create), icon: okIcon(!m.services?.to_create), iconColor: m.services?.to_create ? 'blue-5' : 'green-5' },
    { key: 'factures', label: 'Factures', order: 3, value: fmtN(m.factures?.lag), metric: 'en retard', sub: `${fmtN(m.factures?.synced)} vivantes · ${fmtCur(m.factures?.ar_open)} dû`, colorClass: lagCls(m.factures?.lag), icon: okIcon(!m.factures?.lag), iconColor: m.factures?.lag ? 'orange-5' : 'green-5' },
    { key: 'paiements', label: 'Paiements', order: 4, value: fmtN(m.paiements?.lag), metric: 'en retard', sub: `${fmtN(m.paiements?.total)} au total`, colorClass: lagCls(m.paiements?.lag), icon: okIcon(!m.paiements?.lag), iconColor: m.paiements?.lag ? 'orange-5' : 'green-5' },
    { key: 'tickets', label: 'Tickets', order: 2, value: '·', metric: 'moteur séparé', sub: 'osTicket → Dispatch', colorClass: 'text-grey-6', icon: 'open_in_new', iconColor: 'grey-5' },
  ]
})

async function loadStatus () {
  sLoading.value = true
  try { const r = await fetch(`${HUB_URL}/sync/status`); if (r.ok) status.value = await r.json() } catch {}
  sLoading.value = false
}

async function load () {
  loading.value = true; err.value = ''
  try {
    const res = await fetch(`${HUB_URL}/legacy-sync/preview`)
    if (!res.ok) throw new Error('Échec du calcul (' + res.status + ')')
    rep.value = await res.json()
  } catch (e) { err.value = e.message }
  loading.value = false
}
async function loadScoreboard () {
  sbLoading.value = true
  try { const r = await fetch(`${HUB_URL}/legacy-sync/scoreboard`); if (r.ok) scoreboard.value = await r.json() } catch {} finally { sbLoading.value = false }
}
function deltaClass (d) { if (d == null) return 'text-grey-5'; if (d === 0) return 'text-green-7'; if (Math.abs(d) < 100) return 'text-orange-8'; return 'text-red-7' }
function reload () { loadStatus(); load(); loadScoreboard() }

// ── Synchronisation manuelle + progression ──
const syncing = ref(false)
const syncRun = ref(null)
let _pollTimer = null
const stepIcon = (s) => ({ pending: 'schedule', running: 'sync', done: 'check_circle', error: 'error' }[s] || 'schedule')
const stepColor = (s) => ({ pending: 'grey-5', running: 'blue-6', done: 'green-6', error: 'red-6' }[s] || 'grey-5')
async function startSync (scope) {
  if (syncing.value) return
  syncing.value = true; syncRun.value = null
  try {
    const r = await fetch(`${HUB_URL}/sync/run`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ confirm: 'F-WINS', scope }) })
    const d = await r.json()
    if (d.already_running) { $q.notify({ type: 'warning', message: 'Une synchronisation est déjà en cours' }); poll(); return }
    if (!d.started) { $q.notify({ type: 'negative', message: d.error || 'Échec' }); syncing.value = false; return }
    poll()
  } catch (e) { $q.notify({ type: 'negative', message: e.message }); syncing.value = false }
}
function poll () {
  clearTimeout(_pollTimer)
  fetch(`${HUB_URL}/sync/run-status`).then(r => r.json()).then(d => {
    syncRun.value = d
    if (d.running) { _pollTimer = setTimeout(poll, 1500) }
    else { syncing.value = false; const ok = (d.steps || []).filter(s => s.status === 'error').length === 0; $q.notify({ type: ok ? 'positive' : 'warning', message: ok ? 'Synchronisation terminée' : 'Terminée avec des erreurs' }); reload() }
  }).catch(() => { syncing.value = false })
}
// Appliquer un changement révisé (accepter F) — par compte
const applyingId = ref(null)
async function applyChange (r) {
  applyingId.value = r.legacy_account_id
  try {
    const res = await fetch(`${HUB_URL}/legacy-sync/apply-change`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ legacy_account_id: r.legacy_account_id, confirm: 'F-WINS' }) })
    const d = await res.json()
    if (d.applied) {
      $q.notify({ type: 'positive', message: `Appliqué à ${r.customer_name} : ${Object.keys(d.patch).join(', ')}` })
      rep.value.customers.needs_review_sample = rep.value.customers.needs_review_sample.filter(x => x.legacy_account_id !== r.legacy_account_id)
      loadStatus()
    } else $q.notify({ type: 'warning', message: d.error || d.note || 'Rien à appliquer' })
  } catch (e) { $q.notify({ type: 'negative', message: e.message }) } finally { applyingId.value = null }
}
// Appliquer en LOT (par champ ou tout) + détail
const batchApplying = ref(null)
const reviewDetail = ref(null)
async function applyBatch (field) {
  const label = field || 'tous'
  if (batchApplying.value) return
  batchApplying.value = label
  try {
    const r = await fetch(`${HUB_URL}/legacy-sync/apply-review-batch`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ field, confirm: 'F-WINS' }) })
    const d = await r.json()
    if (d.applied != null) { $q.notify({ type: 'positive', message: `${d.applied} changement(s) appliqué(s)${field ? ' · ' + field : ''}` }); reviewDetail.value = null; load() }
    else $q.notify({ type: 'negative', message: d.error || 'Échec' })
  } catch (e) { $q.notify({ type: 'negative', message: e.message }) } finally { batchApplying.value = null }
}
async function showDetail (field) {
  if (reviewDetail.value && reviewDetail.value.field === field) { reviewDetail.value = null; return }
  try {
    const r = await fetch(`${HUB_URL}/legacy-sync/apply-review-batch`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ field }) }) // dry-run
    const d = await r.json()
    reviewDetail.value = { field, items: d.sample || [], total: d.would_apply || 0 }
  } catch (e) { $q.notify({ type: 'negative', message: e.message }) }
}
// Créer TOUS les comptes F manquants (F→OPS complet) — passe par le run ASYNC de l'orchestrateur (scope 'customers') → progression + polling, pas de requête bloquante.
function confirmCreateAll () {
  $q.dialog({ title: 'Créer tous les comptes manquants', message: `Créer dans ERPNext les <b>${cs.value.would_create}</b> compte(s) F sans Customer (incl. résiliés) ?<br><span class="text-caption">Politique : créé visible, statut dérivé des services. Idempotent — relançable sans doublon.</span>`, html: true, cancel: true, ok: { label: 'Créer tout', color: 'info' } }).onOk(() => startSync('customers'))
}
function confirmApplyAll () {
  const bf = rep.value.customers.review_by_field || {}
  const detail = Object.entries(bf).filter(([f]) => f !== 'customer_name').map(([f, n]) => `${f}: ${n}`).join(' · ')
  $q.dialog({ title: 'Tout appliquer (F gagne)', message: `Accepter F sur ${cs.value.needs_review} client(s) ?<br><span class="text-caption">${detail}</span><br><br>Le nom est protégé. Réversible.`, html: true, cancel: true, ok: { label: 'Appliquer', color: 'orange-8' } }).onOk(() => applyBatch(null))
}
onBeforeUnmount(() => clearTimeout(_pollTimer))
onMounted(reload)
</script>

<style scoped>
.stat { padding: 12px; text-align: center; }
.stat .num { font-size: 1.7rem; font-weight: 700; line-height: 1; }
.stat .lbl { font-size: 0.72rem; color: #64748b; margin-top: 4px; }
.dcard { padding: 10px; }
.dcard .num { font-size: 1.5rem; font-weight: 700; line-height: 1.1; }
.dcard .lbl { font-size: 0.7rem; color: #64748b; text-transform: uppercase; letter-spacing: .02em; }
.chg { display: inline-block; margin-right: 10px; }
code { background: #eef2ff; padding: 1px 4px; border-radius: 3px; }
.spin { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>
