<template>
  <q-page class="q-pa-md">
    <div class="q-mb-md">
      <div class="row items-center no-wrap q-mb-sm">
        <q-icon name="account_tree" size="24px" color="primary" class="q-mr-sm" />
        <div class="col">
          <div class="text-h6 text-weight-bold">Orchestration<span class="gt-xs text-subtitle2 text-grey-6 q-ml-xs">— constructeur &amp; dépendances</span></div>
          <div class="text-caption text-grey-6 gt-xs"><b>Modèles</b> = construire le flow (boîtes + liens + actions) · <b>Instances</b> = un flow lancé (statut réel). 🚚 terrain · 🏢 bureau.</div>
        </div>
      </div>
      <div class="row items-center q-gutter-sm tg-controls">
        <q-btn-toggle v-model="source" no-caps dense unelevated toggle-color="primary" color="grey-3" text-color="grey-8"
          :options="[{ value: 'template', label: 'Modèles' }, { value: 'run', label: 'Instances' }]" @update:model-value="onSourceChange" />
        <q-btn-toggle v-model="view" no-caps dense unelevated toggle-color="primary" color="grey-3" text-color="grey-8"
          :options="[{ value: 'dag', icon: 'account_tree', label: 'Dépendances' }, { value: 'gantt', icon: 'view_timeline', label: 'Planning' }]" />
        <q-select v-if="source === 'template'" v-model="selectedName" :options="templateOptions" emit-value map-options dense outlined options-dense
          class="tg-select" label="Orchestration" @update:model-value="loadTemplate" />
        <q-select v-else v-model="selectedRun" :options="runOptions" emit-value map-options dense outlined options-dense
          class="tg-select" label="Instance (client)" @update:model-value="loadRun" />
        <q-space class="gt-xs" />
        <q-btn v-if="source === 'template'" dense unelevated no-caps color="green-7" icon="save" :label="dirty ? 'Enregistrer' : 'Enregistré'" :disable="!dirty || saving" :loading="saving" @click="save" />
        <q-btn v-if="source === 'template'" dense unelevated no-caps color="deep-purple-6" icon="play_arrow" label="Lancer" @click="openLaunch"><q-tooltip>Lancer cette orchestration sur un client</q-tooltip></q-btn>
      </div>
    </div>

    <div v-if="loading" class="flex flex-center q-pa-xl"><q-spinner color="primary" size="32px" /></div>
    <template v-else>
      <TaskDependencyGraph v-if="view === 'dag'" :tasks="tasks" :editable="source === 'template'"
        @add="onAdd" @connect="onConnect" @edit="onEdit" @delete="onDelete" @move="onMove" />
      <TaskGantt v-else :tasks="tasks" />
    </template>

    <div class="text-caption text-grey-5 q-mt-sm">
      Données réelles (Flow Template / Flow Run). En <b>Modèles</b> : tirez d'une boîte à l'autre pour créer une dépendance, cliquez une boîte pour lier son <b>action</b>, puis <b>Enregistrer</b>.
      En <b>Instances</b> : couleur = statut LIVE (vert=fait, bleu=en cours, rouge=échec, gris=en attente).<span v-if="source === 'run' && runStatus"> Instance <b>{{ runStatus }}</b><span v-if="runCustomer"> · {{ runCustomer }}</span>.</span>
    </div>

    <!-- Éditeur d'étape : libellé + ACTION branchable (kind exécuté par le runtime) -->
    <q-dialog v-model="stepDialog">
      <q-card style="min-width:min(380px, 90vw)">
        <q-card-section class="row items-center q-pb-none"><q-icon name="bolt" color="primary" class="q-mr-sm" /><span class="text-subtitle1 text-weight-bold">Étape</span></q-card-section>
        <q-card-section class="q-gutter-sm">
          <q-input v-model="editStep.label" dense outlined autofocus label="Libellé de l'étape" />
          <q-select v-model="editStep.kind" :options="KIND_OPTIONS" emit-value map-options dense outlined label="Action (exécutée par le flow)">
            <template #option="scope"><q-item v-bind="scope.itemProps"><q-item-section avatar>{{ scope.opt.icon }}</q-item-section><q-item-section>{{ scope.opt.label }}</q-item-section></q-item></template>
          </q-select>
          <div class="text-caption text-grey-6">🚚 « Intervention terrain » = déplacement d'un technicien. Les autres actions sont à distance / système.</div>
        </q-card-section>
        <q-card-actions align="right">
          <q-btn flat no-caps color="negative" label="Supprimer" @click="onDelete(editStep.id); stepDialog = false" />
          <q-space />
          <q-btn flat no-caps label="Fermer" v-close-popup />
          <q-btn unelevated no-caps color="primary" label="OK" @click="applyStepEdit" />
        </q-card-actions>
      </q-card>
    </q-dialog>

    <!-- Lancer une instance de l'orchestration sur un client réel -->
    <q-dialog v-model="launchDialog">
      <q-card style="min-width:min(380px, 90vw)">
        <q-card-section class="row items-center q-pb-none"><q-icon name="play_circle" color="deep-purple-6" class="q-mr-sm" /><span class="text-subtitle1 text-weight-bold">Lancer sur un client</span></q-card-section>
        <q-card-section>
          <q-select v-model="custPicked" :options="custOptions" use-input input-debounce="300" @filter="searchCust" emit-value map-options dense outlined options-dense label="Client" hint="Tapez un nom de client" />
          <div class="text-caption text-grey-6 q-mt-sm">Crée une instance de « {{ selectedLabel }} » et exécute les premières étapes. ⚠ Selon la config du modèle, des étapes peuvent échouer (visible dans Instances).</div>
        </q-card-section>
        <q-card-actions align="right">
          <q-btn flat no-caps label="Annuler" v-close-popup />
          <q-btn unelevated no-caps color="deep-purple-6" label="Lancer" :disable="!custPicked || launching" :loading="launching" @click="doLaunch" />
        </q-card-actions>
      </q-card>
    </q-dialog>
  </q-page>
</template>

<script setup>
import { ref, reactive, computed, onMounted, defineAsyncComponent } from 'vue'
import { useQuasar } from 'quasar'
import { listFlowTemplates, getFlowTemplate, updateFlowTemplate } from 'src/api/flow-templates'
import { HUB_URL } from 'src/config/hub'
import TaskDependencyGraph from 'src/components/flow-editor/TaskDependencyGraph.vue'
// Vue Gantt (hy-vue-gantt, lourd) rendu seulement dans la vue « gantt » → chargé à la demande.
const TaskGantt = defineAsyncComponent(() => import('src/components/flow-editor/TaskGantt.vue'))

const $q = useQuasar()
const view = ref('dag')
const source = ref('template')
const templateOptions = ref([])
const selectedName = ref(null)
const runOptions = ref([])
const selectedRun = ref(null)
const runStatus = ref('')
const runCustomer = ref('')
const tasks = ref([])
const loading = ref(true)
const saving = ref(false)
const dirty = ref(false)

// État de modèle en cours d'édition (steps bruts + le reste du flow_definition).
const templateSteps = ref([])
const loadedDef = ref({ version: 1, trigger: { event: 'manual' }, variables: {}, steps: [] })

const stepDialog = ref(false)
const editStep = reactive({ id: '', label: '', kind: 'notify' })

// Actions branchables = les `kind` que le runtime exécute.
const KIND = {
  dispatch_job: { label: 'Intervention terrain', icon: '🚚', dispatch: true },
  notify: { label: 'Notification (SMS/courriel)', icon: '📨' },
  issue: { label: 'Ticket', icon: '🎫' },
  erp_update: { label: 'Mise à jour ERP', icon: '🗄️' },
  subscription_activate: { label: 'Activer l\'abonnement', icon: '⚡' },
  wait: { label: 'Attente', icon: '⏳' },
  condition: { label: 'Condition', icon: '❓' },
  webhook: { label: 'Webhook', icon: '🔗' },
}
const KIND_OPTIONS = Object.entries(KIND).map(([value, m]) => ({ value, label: m.label, icon: m.icon }))
const RUN_STATUS = { done: 'Completed', running: 'Working', failed: 'Cancelled', scheduled: 'Pending Review', pending: 'Open', skipped: 'Open' }

function datesFor (steps) {
  const byId = {}; for (const s of steps) byId[s.id] = s
  const memo = {}
  const depth = (id, seen = new Set()) => { if (memo[id] != null) return memo[id]; if (seen.has(id)) return 0; seen.add(id); const deps = (byId[id]?.depends_on || []).filter(d => byId[d]); const d = deps.length ? 1 + Math.max(...deps.map(x => depth(x, seen))) : 0; memo[id] = d; return d }
  const base = new Date('2026-06-22T00:00:00')
  const iso = (n) => { const d = new Date(base); d.setDate(d.getDate() + n); return d.toISOString().slice(0, 10) }
  return (s) => { const dp = depth(s.id); return { start: iso(dp), end: iso(dp + 1) } }
}
function mapSteps (steps, stepState) {
  const dates = datesFor(steps)
  return steps.map(s => {
    const k = KIND[s.kind] || { label: s.kind }
    const live = stepState && stepState[s.id]
    const status = live ? (RUN_STATUS[live.status] || 'Open') : 'Open'
    const { start, end } = dates(s)
    return { id: s.id, subject: s.label || s.id, dependsOn: s.depends_on || [], requiresDispatch: !!k.dispatch, assignee: live && live.error ? '⚠ ' + k.label : k.label, status, start, end, pos: s._pos }
  })
}
function remap () { tasks.value = mapSteps(templateSteps.value) }

async function loadTemplate (name) {
  if (!name) { tasks.value = []; loading.value = false; return }
  loading.value = true; dirty.value = false
  try {
    const tpl = await getFlowTemplate(name)
    loadedDef.value = tpl?.flow_definition || { version: 1, trigger: { event: 'manual' }, variables: {}, steps: [] }
    templateSteps.value = JSON.parse(JSON.stringify(loadedDef.value.steps || []))
    remap()
  } catch (e) { tasks.value = [] } finally { loading.value = false }
}

// ── Builder : ajouter / relier / éditer / supprimer / déplacer (mute templateSteps + remap + dirty) ──
function findStep (id) { return templateSteps.value.find(s => String(s.id) === String(id)) }
function dependsTransitively (a, b, seen = new Set()) { // a dépend-il (transitivement) de b ?
  if (a === b) return true
  if (seen.has(a)) return false
  seen.add(a)
  const s = findStep(a); return (s?.depends_on || []).some(d => dependsTransitively(d, b, seen))
}
function onAdd () {
  const id = 's' + Math.random().toString(36).slice(2, 7)
  templateSteps.value.push({ id, kind: 'notify', label: 'Nouvelle étape', depends_on: [] })
  dirty.value = true; remap()
  editStep.id = id; editStep.label = 'Nouvelle étape'; editStep.kind = 'notify'; stepDialog.value = true
}
function onConnect ({ source: src, target }) {
  if (src === target) return
  const t = findStep(target); if (!t) return
  t.depends_on = t.depends_on || []
  if (t.depends_on.includes(src)) return
  if (dependsTransitively(src, target)) { $q.notify({ type: 'warning', message: 'Lien refusé : créerait une boucle.' }); return }
  t.depends_on.push(src); dirty.value = true; remap()
}
function onEdit (id) { const s = findStep(id); if (!s) return; editStep.id = id; editStep.label = s.label || ''; editStep.kind = s.kind || 'notify'; stepDialog.value = true }
function applyStepEdit () { const s = findStep(editStep.id); if (s) { s.label = editStep.label; s.kind = editStep.kind; dirty.value = true; remap() } stepDialog.value = false }
function onDelete (id) {
  templateSteps.value = templateSteps.value.filter(s => String(s.id) !== String(id))
  for (const s of templateSteps.value) if (s.depends_on) s.depends_on = s.depends_on.filter(d => String(d) !== String(id))
  dirty.value = true; remap()
}
function onMove ({ id, position }) { const s = findStep(id); if (s) { s._pos = { x: position.x, y: position.y }; dirty.value = true } }

async function save () {
  if (!selectedName.value) return
  saving.value = true
  try {
    await updateFlowTemplate(selectedName.value, { flow_definition: { ...loadedDef.value, steps: templateSteps.value } })
    dirty.value = false
    $q.notify({ type: 'positive', message: 'Orchestration enregistrée', timeout: 1500 })
  } catch (e) { $q.notify({ type: 'negative', message: 'Échec : ' + (e.message || e) }) } finally { saving.value = false }
}

// ── Lancer une instance sur un client (wrapper proxy-authed /flow/instantiate) ──
const launchDialog = ref(false)
const custOptions = ref([])
const custPicked = ref(null)
const launching = ref(false)
const selectedLabel = computed(() => (templateOptions.value.find(o => o.value === selectedName.value) || {}).label || '')
function openLaunch () { custPicked.value = null; custOptions.value = []; launchDialog.value = true }
async function searchCust (query, done) {
  const q = String(query || '').trim()
  if (q.length < 2) { done(() => {}); return }
  try {
    const r = await fetch(`${HUB_URL}/collab/customer-search?q=${encodeURIComponent(q)}`).then(x => x.json())
    const opts = (r.matches || []).map(m => ({ label: (m.customer_name || m.name) + (m.territory ? ' · ' + m.territory : ''), value: m.name }))
    done(() => { custOptions.value = opts })
  } catch { done(() => {}) }
}
async function doLaunch () {
  if (!custPicked.value || !selectedName.value) return
  launching.value = true
  try {
    const r = await fetch(`${HUB_URL}/flow/instantiate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ template: selectedName.value, customer: custPicked.value }) }).then(x => x.json())
    launchDialog.value = false
    $q.notify({ type: 'positive', message: 'Instance lancée' + (r.name ? ' (' + r.name + ')' : ''), timeout: 2500 })
    source.value = 'run'; await loadRuns()
  } catch (e) { $q.notify({ type: 'negative', message: 'Échec du lancement : ' + (e.message || e) }) } finally { launching.value = false }
}

// ── Instances (lecture seule, statut live) ──
async function loadRun (name) {
  if (!name) { tasks.value = []; loading.value = false; return }
  loading.value = true
  try {
    const r = await fetch(`${HUB_URL}/flow/runs/${encodeURIComponent(name)}`).then(x => x.json())
    const run = r?.run || r
    runStatus.value = run?.status || ''; runCustomer.value = run?.customer || ''
    let ss = run?.step_state; if (typeof ss === 'string') { try { ss = JSON.parse(ss) } catch { ss = {} } }
    const tpl = await getFlowTemplate(run?.flow_template)
    tasks.value = mapSteps(tpl?.flow_definition?.steps || [], ss || {})
  } catch (e) { tasks.value = [] } finally { loading.value = false }
}
async function loadRuns () {
  loading.value = true
  try {
    const r = await fetch(`${HUB_URL}/flow/runs`).then(x => x.json())
    const runs = r.runs || []
    runOptions.value = runs.map(x => ({ label: x.name + ' · ' + (x.customer || '?') + ' · ' + (x.status || ''), value: x.name }))
    if (runOptions.value.length) { selectedRun.value = runOptions.value[0].value; await loadRun(selectedRun.value) } else { tasks.value = []; loading.value = false }
  } catch (e) { loading.value = false }
}
function onSourceChange (v) { if (v === 'run') loadRuns(); else loadTemplate(selectedName.value) }

onMounted(async () => {
  try {
    const tpls = await listFlowTemplates()
    templateOptions.value = tpls.map(t => ({ label: t.template_name || t.name, value: t.name }))
    if (templateOptions.value.length) { selectedName.value = templateOptions.value[0].value; await loadTemplate(selectedName.value) } else loading.value = false
  } catch (e) { loading.value = false }
})
</script>

<style scoped>
.tg-select { min-width: 210px; }
/* Mobile (xs) : les sélecteurs prennent toute la largeur et les contrôles s'empilent proprement. */
@media (max-width: 599px) {
  .tg-select { flex: 1 1 100%; min-width: 0; }
  .tg-controls .q-btn-toggle { flex: 1 1 auto; }
}
</style>
