<script setup lang="ts">
// <!-- ld-v2 --> /decisions/observatoire — L'Observatoire (Δ12) : comment nous
// décidons, dans les faits. Tout est dérivé des stores en computed purs ; des
// faits comptés, des barres sobres aux couleurs du mood — jamais un score.
import type { DecisionRoute, Id } from '~/types/domain'
import { useCollectiveStore } from '~/stores/collective'
import { useDecisionsStore } from '~/stores/decisions'
import {
  MATURATION_CARD, OBSERVATORY_SUBTITLE, OBSERVATORY_TITLE,
  REVIEW_TITLE, REVIEW_VERDICTS, REVOKED_SECTION, ROUTE_SHORT,
} from '~/lexicon'
import { formatDay } from '~/components/mandates/mandateUi'

const col = useCollectiveStore()
const decisionsStore = useDecisionsStore()
const reviewLabels = REVIEW_VERDICTS

// ── Filtres : personne / cercle / tag / période ──
const fPerson = ref(''); const fCircle = ref(''); const fTag = ref(''); const fPeriod = ref('')
const tagOptions = computed(() => [...new Set(col.decisions.flatMap(d => d.tags))].sort())
const since = computed(() =>
  fPeriod.value === '' ? '' : new Date(Date.now() - Number(fPeriod.value) * 86_400_000).toISOString(),
)
const filtered = computed(() => col.decisions.filter(d =>
  (fPerson.value === '' || d.authorId === fPerson.value)
  && (fCircle.value === '' || d.scope.circleIds.includes(fCircle.value))
  && (fTag.value === '' || d.tags.includes(fTag.value))
  && (since.value === '' || (d.decidedAt ?? d.createdAt) >= since.value),
))
const engaged = computed(() => filtered.value.filter(d => d.status !== 'draft'))

// ── Autonomie : ratio par route, maturité, temps médians, participation ──
const ROUTES: DecisionRoute[] = ['solo', 'mandate', 'advice', 'collective', 'record']
const routeBars = computed(() => {
  const total = Math.max(1, engaged.value.length)
  return ROUTES.map((r) => {
    const n = engaged.value.filter(d => d.route === r).length
    return { label: ROUTE_SHORT[r], value: n, display: `${n} · ${Math.round((n / total) * 100)} %` }
  })
})
const recordedCount = computed(() => engaged.value.filter(d => d.route === 'record').length)
const tooledCount = computed(() => engaged.value.length - recordedCount.value)

function median(xs: number[]): number {
  if (xs.length === 0) return 0
  const s = [...xs].sort((a, b) => a - b)
  const mid = Math.floor(s.length / 2)
  return s.length % 2 ? s[mid]! : (s[mid - 1]! + s[mid]!) / 2
}
const medianBars = computed(() => ROUTES.map((r) => {
  const days = filtered.value
    .filter(d => d.route === r && d.decidedAt !== undefined)
    .map(d => (new Date(d.decidedAt!).getTime() - new Date(d.createdAt).getTime()) / 86_400_000)
  const m = median(days)
  return { label: ROUTE_SHORT[r], value: m, display: m === 0 ? '—' : m < 1 ? `${Math.max(1, Math.round(m * 24))} h` : `${Math.round(m)} j` }
}))

const participation = computed(() => {
  const ids = new Set(filtered.value.map(d => d.id))
  const closed = col.sessions.filter(s => ids.has(s.decisionId) && s.status !== 'open' && s.corpusSize > 0)
  if (closed.length === 0) return { value: '—', hint: 'aucune session close' }
  const avg = closed.reduce((sum, s) => sum + decisionsStore.activeVotes(s.id).length / s.corpusSize, 0) / closed.length
  return { value: `${Math.round(avg * 100)} %`, hint: `sur ${closed.length} session${closed.length > 1 ? 's' : ''} close${closed.length > 1 ? 's' : ''}` }
})

const heavier = computed(() => engaged.value.filter(d => d.routeOverridden && d.overrideNote === undefined).length)
const lighter = computed(() => engaged.value.filter(d => d.overrideNote !== undefined).length)
const scopeKept = computed(() => engaged.value.filter(d => d.scopeKeptNote !== undefined).length)

// ── Ressources PAR UNITÉ — l'huile et l'eau, jamais additionnées ──
const resourceLines = computed(() => {
  const byUnit = new Map<string, { total: number; count: number }>()
  for (const d of engaged.value) {
    if (!d.resources?.amount) continue
    const unit = d.resources.unit ?? 'sans unité'
    const line = byUnit.get(unit) ?? { total: 0, count: 0 }
    line.total += d.resources.amount
    line.count += 1
    byUnit.set(unit, line)
  }
  return [...byUnit.entries()].map(([unit, l]) => ({
    unit, total: l.total.toLocaleString('fr-FR'), count: l.count,
  }))
})

// ── Épreuves du réel ──
const now = new Date().toISOString()
const reviewsDue = computed(() => filtered.value
  .filter(d => d.review !== undefined && d.review.verdict === undefined && d.review.dueAt <= now)
  .map(d => ({ id: d.id, title: d.title, dueAt: d.review!.dueAt })))
const reviewCount = (v: 'confirmed' | 'revise' | 'revoke') =>
  filtered.value.filter(d => d.review?.verdict === v).length

// ── Révoquées, consignations, maturation ──
const revoked = computed(() => filtered.value
  .filter(d => d.status === 'revoked')
  .map(d => ({
    decision: d,
    revocation: col.decisions.find(c => c.parentDecisionId === d.id && c.chainKind === 'revocation'),
  })))
const records = computed(() => filtered.value
  .filter(d => d.route === 'record')
  .sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1)))

const maturations = computed(() => {
  const pairs = new Map<string, { tags: [string, string]; ids: Set<Id> }>()
  for (const r of records.value) {
    const tags = [...new Set(r.tags)].sort()
    for (let i = 0; i < tags.length; i++) {
      for (let j = i + 1; j < tags.length; j++) {
        const key = `${tags[i]}|${tags[j]}`
        const entry = pairs.get(key) ?? { tags: [tags[i]!, tags[j]!], ids: new Set<Id>() }
        entry.ids.add(r.id)
        pairs.set(key, entry)
      }
    }
  }
  const ripe = [...pairs.values()].filter(p => p.ids.size >= 3)
    .sort((a, b) => b.ids.size - a.ids.size)
  const seen = new Set<string>()
  return ripe.filter((p) => {
    const sig = [...p.ids].sort().join(',')
    if (seen.has(sig)) return false
    seen.add(sig)
    return true
  }).slice(0, 4)
})

// ── Élagage & décisions jamais revues ──
const sixMonthsAgo = new Date(Date.now() - 180 * 86_400_000).toISOString()
const unusedProtocols = computed(() => col.protocols.filter(p =>
  !col.decisions.some(d => d.protocolId === p.id) && !col.sessions.some(s => s.protocolId === p.id)))
const idleMandates = computed(() => col.mandates.filter(m =>
  m.status === 'active' && m.startsAt <= sixMonthsAgo
  && !col.decisions.some(d => d.underMandateId === m.id && d.createdAt >= sixMonthsAgo)))
const reviewDelay = computed(() => col.settings?.triage.reviewDelayDays ?? 90)
const neverReviewed = computed(() => {
  const limit = new Date(Date.now() - reviewDelay.value * 86_400_000).toISOString()
  return filtered.value.filter(d =>
    d.status === 'adopted' && d.route !== 'record' && d.review === undefined
    && d.decidedAt !== undefined && d.decidedAt <= limit)
})
</script>

<template>
  <!-- ld-v2 -->
  <div class="obs">
    <header class="obs__header">
      <h1 class="obs__title">{{ OBSERVATORY_TITLE }}</h1>
      <p class="obs__sub">{{ OBSERVATORY_SUBTITLE }}</p>
    </header>

    <!-- Filtres -->
    <div class="obs__filters">
      <select v-model="fPerson" class="obs__select" aria-label="Filtrer par personne">
        <option value="">Toutes les personnes</option>
        <option v-for="p in col.people" :key="p.id" :value="p.id">{{ p.displayName }}</option>
      </select>
      <select v-model="fCircle" class="obs__select" aria-label="Filtrer par cercle">
        <option value="">Tous les cercles</option>
        <option v-for="c in col.circles" :key="c.id" :value="c.id">{{ c.name }}</option>
      </select>
      <select v-model="fTag" class="obs__select" aria-label="Filtrer par tag">
        <option value="">Tous les tags</option>
        <option v-for="t in tagOptions" :key="t" :value="t">#{{ t }}</option>
      </select>
      <select v-model="fPeriod" class="obs__select" aria-label="Filtrer par période">
        <option value="">Depuis toujours</option>
        <option value="30">30 derniers jours</option>
        <option value="90">90 derniers jours</option>
        <option value="365">Cette année</option>
      </select>
    </div>

    <!-- Autonomie -->
    <ObservatorySection title="Stats d'autonomie" icon="i-lucide-compass" sub="qui décide comment — le miroir, pas le juge">
      <ObservatoryBars :items="routeBars" />
      <div class="obs__tiles">
        <ObservatoryStat
          label="Maturité"
          :value="`${recordedCount} / ${tooledCount}`"
          hint="consignées / outillées — le fil de l'eau se protocolise quand il est mûr"
        />
        <ObservatoryStat label="Participation moyenne" :value="participation.value" :hint="participation.hint" />
      </div>
      <h3 class="obs__h3">Temps médians par route</h3>
      <ObservatoryBars :items="medianBars" />
      <h3 class="obs__h3">Dérogations au chemin</h3>
      <div class="obs__tiles">
        <ObservatoryStat label="Alourdies" :value="String(heavier)" hint="un chemin plus exigeant que suggéré" />
        <ObservatoryStat label="Allégées" :value="String(lighter)" hint="toujours motivées publiquement" />
        <ObservatoryStat label="Périmètres maintenus" :value="String(scopeKept)" hint="affluence atteinte, maintien motivé" />
      </div>
    </ObservatorySection>

    <!-- Ressources par unité -->
    <ObservatorySection title="Ce que ça engage" icon="i-lucide-droplets" sub="par unité, lignes séparées — l'huile et l'eau ne s'additionnent pas">
      <ul v-if="resourceLines.length" class="obs__list">
        <li v-for="line in resourceLines" :key="line.unit" class="obs__resource">
          <span class="obs__resource-total">{{ line.total }} {{ line.unit }}</span>
          <span class="obs__muted">sur {{ line.count }} décision{{ line.count > 1 ? 's' : '' }}</span>
        </li>
      </ul>
      <p v-else class="obs__muted">Aucune ressource engagée sur la période.</p>
    </ObservatorySection>

    <!-- Épreuves du réel -->
    <ObservatorySection :title="REVIEW_TITLE" icon="i-lucide-telescope" sub="le réel a-t-il suivi ?">
      <div class="obs__tiles">
        <ObservatoryStat label="Dues" :value="String(reviewsDue.length)" />
        <ObservatoryStat :label="reviewLabels.confirmed" :value="String(reviewCount('confirmed'))" />
        <ObservatoryStat :label="reviewLabels.revise" :value="String(reviewCount('revise'))" />
        <ObservatoryStat :label="reviewLabels.revoke" :value="String(reviewCount('revoke'))" />
      </div>
      <ul v-if="reviewsDue.length" class="obs__list">
        <li v-for="r in reviewsDue" :key="r.id">
          <NuxtLink :to="`/decisions/${r.id}`" class="obs__row">
            <span class="obs__row-title">{{ r.title }}</span>
            <span class="obs__due">due le {{ formatDay(r.dueAt) }}</span>
          </NuxtLink>
        </li>
      </ul>
    </ObservatorySection>

    <!-- Révoquées — ce qu'on en a appris -->
    <ObservatorySection :title="REVOKED_SECTION" icon="i-lucide-flask-conical" sub="le droit à l'erreur, regardé avec la curiosité du chercheur">
      <ul v-if="revoked.length" class="obs__list">
        <li v-for="entry in revoked" :key="entry.decision.id" class="obs__revoked">
          <NuxtLink :to="`/decisions/${entry.decision.id}`" class="obs__row">
            <span class="obs__row-title">{{ entry.decision.title }}</span>
            <span class="obs__muted">{{ formatDay(entry.decision.decidedAt ?? entry.decision.createdAt) }}</span>
          </NuxtLink>
          <p v-if="entry.decision.review?.note" class="obs__learned">{{ entry.decision.review.note }}</p>
          <NuxtLink v-if="entry.revocation" :to="`/decisions/${entry.revocation.id}`" class="obs__chain">
            <UIcon name="i-lucide-link" />
            la décision qui l'a révoquée
          </NuxtLink>
        </li>
      </ul>
      <p v-else class="obs__muted">Rien à apprendre ici pour l'instant — aucune révoquée sur la période.</p>
    </ObservatorySection>

    <!-- Consignations -->
    <ObservatorySection title="Consignations" icon="i-lucide-notebook-pen" sub="déjà tranché ailleurs — consigné ici, tel quel">
      <ul v-if="records.length" class="obs__list">
        <li v-for="d in records" :key="d.id">
          <NuxtLink :to="`/decisions/${d.id}`" class="obs__row obs__row--col">
            <span class="obs__row-title">{{ d.title }}</span>
            <span v-if="d.decidedHow" class="obs__how">{{ d.decidedHow }}</span>
          </NuxtLink>
        </li>
      </ul>
      <p v-else class="obs__muted">Aucune consignation sur la période.</p>

      <div v-for="m in maturations" :key="m.tags.join('|')" class="obs__card obs__card--ripe">
        <p class="obs__card-title">
          <UIcon name="i-lucide-sprout" />
          {{ MATURATION_CARD }}
        </p>
        <p class="obs__muted">
          {{ m.ids.size }} consignations partagent {{ m.tags.map(t => `#${t}`).join(' et ') }}.
        </p>
        <NuxtLink :to="`/decider?clause-nouvelle&tags=${m.tags.join(',')}`" class="ld-btn ld-btn--ghost obs__card-btn">
          Protocoliser
        </NuxtLink>
      </div>
    </ObservatorySection>

    <!-- Élagage & relances -->
    <ObservatorySection title="Élagage" icon="i-lucide-scissors" sub="ce qui ne sert plus mérite une revue — pas un enterrement silencieux">
      <div v-for="p in unusedProtocols" :key="p.id" class="obs__card">
        <p class="obs__card-title">Le protocole « {{ p.name }} » n'a jamais servi.</p>
        <p class="obs__muted">Le relire, l'amender — ou l'élaguer par une décision.</p>
      </div>
      <div v-for="m in idleMandates" :key="m.id" class="obs__card">
        <p class="obs__card-title">Le mandat « {{ m.title }} » est sans trace depuis 6 mois.</p>
        <NuxtLink :to="`/mandats/${m.id}`" class="obs__chain">
          <UIcon name="i-lucide-key-round" />
          voir la fiche — une revue s'impose peut-être
        </NuxtLink>
      </div>
      <template v-if="neverReviewed.length">
        <h3 class="obs__h3">Jamais revues depuis {{ reviewDelay }} jours</h3>
        <ul class="obs__list">
          <li v-for="d in neverReviewed" :key="d.id">
            <NuxtLink :to="`/decisions/${d.id}`" class="obs__row">
              <span class="obs__row-title">{{ d.title }}</span>
              <span class="obs__muted">décidée le {{ formatDay(d.decidedAt) }}</span>
            </NuxtLink>
          </li>
        </ul>
      </template>
      <p v-if="!unusedProtocols.length && !idleMandates.length && !neverReviewed.length" class="obs__muted">
        Rien à élaguer — tout ce qui existe sert encore.
      </p>
    </ObservatorySection>
  </div>
</template>

<style scoped>
.obs { max-width: 52rem; margin: 0 auto; width: 100%; display: flex; flex-direction: column; gap: 1.25rem; }
.obs__title { margin: 0; font-size: clamp(1.375rem, 3.5vw, 1.75rem); font-weight: 800; letter-spacing: -0.01em; }
.obs__sub { margin: 0.25rem 0 0; font-size: 0.9375rem; font-style: italic; color: var(--mood-text-muted); }
.obs__filters { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.obs__select {
  flex: 1 1 10rem;
  padding: 0.5rem 0.75rem;
  font: inherit;
  font-size: 0.875rem;
  color: var(--mood-text);
  background: var(--mood-input-bg);
  border: none;
  border-radius: var(--r-input);
  box-shadow: inset 0 0 0 1px var(--mood-input-border);
}
.obs__select:focus { outline: none; box-shadow: inset 0 0 0 2px var(--mood-input-focus); }
.obs__h3 {
  margin: 0.375rem 0 0;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--mood-text-muted);
}
.obs__tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr)); gap: 0.625rem; }
.obs__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.375rem; }
.obs__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--r-input);
  background: var(--mood-accent-soft);
  text-decoration: none;
  color: var(--mood-text);
  transition: transform 0.1s ease;
  flex-wrap: wrap;
}
.obs__row:hover { transform: translateY(-1px); }
.obs__row--col { flex-direction: column; align-items: flex-start; gap: 0.125rem; }
.obs__row-title { font-size: 0.875rem; font-weight: 600; min-width: 0; }
.obs__due { font-size: 0.75rem; font-weight: 700; color: var(--mood-status-fenetre); white-space: nowrap; }
.obs__muted { margin: 0; font-size: 0.8125rem; color: var(--mood-text-muted); }
.obs__how { font-size: 0.8125rem; font-style: italic; color: var(--mood-text-muted); }
.obs__resource { display: flex; align-items: baseline; gap: 0.625rem; flex-wrap: wrap; }
.obs__resource-total { font-size: 1.125rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.obs__revoked { display: flex; flex-direction: column; gap: 0.25rem; }
.obs__learned {
  margin: 0;
  padding-left: 0.75rem;
  font-size: 0.8125rem;
  font-style: italic;
  color: var(--mood-text-muted);
}
.obs__chain {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--mood-accent);
  text-decoration: none;
  padding-left: 0.75rem;
}
.obs__chain:hover { text-decoration: underline; }
.obs__card {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.875rem 1rem;
  border-radius: var(--r-icon);
  background: var(--mood-accent-soft);
}
.obs__card--ripe { background: var(--mood-status-vigueur-bg); }
.obs__card-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 700;
}
.obs__card-btn { align-self: flex-start; margin-top: 0.375rem; }
</style>
