<script setup lang="ts">
// <!-- ld-v2 --> Réglage collectif — décider au curseur. Faisceau anonyme,
// médiane basse nommée, carte « Pour moi » (jamais de chiffre inventé),
// sandbox Explorer, et la CRISTALLISATION : un geste humain daté du garant,
// solennel mais léger — jamais un automatisme.
import { computeMyImpact, detectBimodality, medianByElement, resolveDerived } from '~/engine'
import { useCollectiveStore } from '~/stores/collective'
import { useDecisionsStore } from '~/stores/decisions'
import {
  CRYSTALLIZE_ACTION, CRYSTALLIZE_CARD, CRYSTALLIZE_CHOICES, EXPLORE_DISCLAIMER,
  EXPLORE_LABEL, FROZEN_BANNER, IMPACT_CARD_TITLE, IMPACT_DISCLAIMER,
  MEDIAN_EXPLANATION, PARAM_CONSTRAINT_LABEL,
} from '~/lexicon'
import type { Decision, Id, ParamSpec, Protocol, VoteSession } from '~/types/domain'

const props = defineProps<{
  decision: Decision
  session: VoteSession
  protocol: Protocol
  secret?: boolean
  canAct?: boolean
  asPersonId?: Id
  isSteward?: boolean
}>()
const emit = defineEmits<{ (e: 'adopted'): void }>()

const col = useCollectiveStore()
const store = useDecisionsStore()

const spec = computed<ParamSpec>(() => props.decision.paramSpec!)
const votable = computed(() => spec.value.params.filter(p => p.derived !== true))
/** Index votable → index complet (ordre spec.params). */
const votableIndex = computed(() => {
  const map = new Map<string, number>()
  votable.value.forEach((p, i) => map.set(p.key, i))
  return map
})

// ── Les positions déposées (derniers votes actifs, toujours recalculées) ──
const active = computed(() => store.activeVotes(props.session.id))
const vectors = computed(() => active.value
  .map(v => v.values)
  .filter((v): v is number[] => v !== undefined && v.length === votable.value.length))
const fullVectors = computed(() => vectors.value.map(v => resolveDerived(spec.value, v)))
const medianVotable = computed(() => medianByElement(vectors.value))
const medianFull = computed(() =>
  medianVotable.value.length === votable.value.length && votable.value.length > 0
    ? resolveDerived(spec.value, medianVotable.value)
    : null)
const baselineFull = computed(() => spec.value.params.map(p => p.baseline ?? p.min))
const bimodalKeys = computed(() => votable.value
  .filter((_, i) => detectBimodality(vectors.value.map(v => v[i] ?? Number.NaN)))
  .map(p => p.key))

function columnFor(fullIndex: number): number[] {
  return fullVectors.value.map(v => v[fullIndex] ?? Number.NaN).filter(Number.isFinite)
}

// ── Ma position de travail ──
const voterId = computed(() => props.asPersonId ?? col.me?.id)
const myVote = computed(() => active.value.find(v => v.voterId === voterId.value))
const working = ref<number[]>(
  (myVote.value?.values && [...myVote.value.values])
  ?? votable.value.map(p => p.baseline ?? p.min),
)
const workingFull = computed(() => resolveDerived(spec.value, working.value))

const round6 = (v: number) => Math.round(v * 1e6) / 1e6
/** Nouvelle liste avec la valeur vi ajustée : bornes, pas, et part calculée gardée dans ses bornes. */
function adjusted(list: number[], vi: number, raw: number): number[] {
  const p = votable.value[vi]
  if (!p || !Number.isFinite(raw)) return list
  const snap = (v: number) => round6(p.min + Math.round((v - p.min) / p.step) * p.step)
  let v = snap(Math.min(p.max, Math.max(p.min, raw)))
  const next = [...list]
  const derived = spec.value.params.find(q => q.derived === true)
  if (spec.value.constraint === 'sum100' && p.kind === 'share' && derived) {
    const otherSum = votable.value.reduce(
      (sum, q, j) => sum + (q.kind === 'share' && j !== vi ? (next[j] ?? 0) : 0), 0)
    const lo = 100 - derived.max - otherSum
    const hi = 100 - derived.min - otherSum
    v = snap(Math.min(hi, Math.max(lo, v)))
    if (v > hi + 1e-6) v = round6(v - p.step)
    if (v < lo - 1e-6) v = round6(v + p.step)
    v = Math.min(p.max, Math.max(p.min, v))
  }
  next[vi] = v
  return next
}
function setWorking(key: string, v: number) {
  const vi = votableIndex.value.get(key)
  if (vi !== undefined) working.value = adjusted(working.value, vi, v)
}

const error = ref('')
function deposit() {
  error.value = ''
  const result = store.castVote(props.session.id, {
    values: [...working.value],
    ...(props.asPersonId ? { asPersonId: props.asPersonId } : {}),
  })
  if ('ok' in result) error.value = result.reason
}

// ── Carte « Pour moi » — estimation sur données déclarées, sinon rien ──
const attrKey = computed(() => spec.value.impactAttrKey)
const myAttr = computed(() =>
  attrKey.value !== undefined ? col.me?.attributes?.[attrKey.value] : undefined)
const declaredAttrs = computed(() => {
  const key = attrKey.value
  if (key === undefined) return []
  return col.people
    .filter(p => props.session.corpusPersonIds.includes(p.id))
    .map(p => p.attributes?.[key])
    .filter((v): v is number => typeof v === 'number')
})
const resources = computed(() => props.decision.resources ?? { note: '' })
const impactMine = computed(() =>
  computeMyImpact(spec.value, resources.value, workingFull.value, myAttr.value, declaredAttrs.value))
const impactMedian = computed(() => medianFull.value
  ? computeMyImpact(spec.value, resources.value, medianFull.value, myAttr.value, declaredAttrs.value)
  : null)
const impactBase = computed(() =>
  computeMyImpact(spec.value, resources.value, baselineFull.value, myAttr.value, declaredAttrs.value))
const unit = computed(() => props.decision.resources?.unit ?? '')
const fmtAmount = (v?: number) =>
  v === undefined ? '—' : `${v.toLocaleString('fr-FR', { maximumFractionDigits: 2 })} ${unit.value}`

// ── Explorer — exploration, ne compte pas ──
const exploring = ref(false)
const sandbox = ref<number[]>([])
function toggleExplore() {
  if (exploring.value) { exploring.value = false; return }
  sandbox.value = [...working.value]
  exploring.value = true
}
function setSandbox(key: string, v: number) {
  const vi = votableIndex.value.get(key)
  if (vi !== undefined) sandbox.value = adjusted(sandbox.value, vi, v)
}
const sandboxFull = computed(() =>
  sandbox.value.length === votable.value.length ? resolveDerived(spec.value, sandbox.value) : [])
const seedMedian = () => { if (medianVotable.value.length) sandbox.value = [...medianVotable.value] }
const seedBase = () => { sandbox.value = votable.value.map(p => p.baseline ?? p.min) }
const seedMine = () => { sandbox.value = [...working.value] }
function makeMine() { working.value = [...sandbox.value]; exploring.value = false }

// ── Cristallisation — le geste du garant ──
const frozen = computed(() => props.session.status === 'frozen')
const locked = computed(() => props.session.status !== 'open' || !props.canAct)
const showCrystal = ref(false)
const quorum = computed(() => props.protocol.formula.parametricMinParticipants)
const quorumShort = computed(() =>
  quorum.value !== undefined && vectors.value.length < quorum.value)
function doCrystallize() {
  error.value = ''
  const result = store.crystallize(props.session.id)
  if ('ok' in result) { error.value = result.reason; return }
  showCrystal.value = false
  if (result.outcome === 'adopted') emit('adopted')
}
function split() {
  navigateTo({ path: '/decider', query: { parent: props.decision.id, chain: 'element' } })
}
function reframe() {
  error.value = ''
  const result = store.transition(props.decision.id, 'framing')
  if (!result.ok) { error.value = result.reason; return }
  showCrystal.value = false
}
const crystallizer = computed(() => props.session.crystallizedById
  ? col.people.find(p => p.id === props.session.crystallizedById)?.displayName ?? '—'
  : null)

const shareParams = computed(() => spec.value.constraint === 'sum100'
  ? spec.value.params.filter(p => p.kind === 'share')
  : [])
const plainParams = computed(() => spec.value.constraint === 'sum100'
  ? spec.value.params.filter(p => p.kind !== 'share')
  : spec.value.params)
function fullIndexOf(key: string): number {
  return spec.value.params.findIndex(p => p.key === key)
}
</script>

<template>
  <!-- ld-v2 -->
  <section class="ld-card vp">
    <p class="vp__explain">{{ MEDIAN_EXPLANATION }}</p>

    <p v-if="frozen" class="vp__frozen">
      <UIcon name="i-lucide-lock" />
      <span>{{ FROZEN_BANNER }}</span>
    </p>

    <!-- Le cadre contraint (parts à 100) puis les curseurs libres -->
    <div v-if="shareParams.length" class="vp__frame">
      <span class="vp__frame-tag">{{ PARAM_CONSTRAINT_LABEL }} — les parts totalisent 100</span>
      <VoteParamSlider
        v-for="p in shareParams"
        :key="p.key"
        :param="p"
        :value="workingFull[fullIndexOf(p.key)] ?? p.min"
        :dots="columnFor(fullIndexOf(p.key))"
        :median="medianFull?.[fullIndexOf(p.key)]"
        :locked="locked"
        :bimodal="bimodalKeys.includes(p.key)"
        @update:value="setWorking(p.key, $event)"
      />
    </div>
    <VoteParamSlider
      v-for="p in plainParams"
      :key="p.key"
      :param="p"
      :value="workingFull[fullIndexOf(p.key)] ?? p.min"
      :dots="columnFor(fullIndexOf(p.key))"
      :median="medianFull?.[fullIndexOf(p.key)]"
      :locked="locked"
      :bimodal="bimodalKeys.includes(p.key)"
      @update:value="setWorking(p.key, $event)"
    />

    <div v-if="session.status === 'open'" class="vp__actions">
      <button class="ld-btn" type="button" :disabled="!canAct" @click="deposit()">
        {{ myVote ? 'Remplacer ma position' : 'Déposer ma position' }}
      </button>
      <button class="ld-btn ld-btn--ghost" type="button" @click="toggleExplore()">
        <UIcon name="i-lucide-flask-conical" />
        <span>{{ EXPLORE_LABEL }}</span>
      </button>
    </div>
    <p v-if="error" class="vp__error">{{ error }}</p>

    <!-- Sandbox Explorer -->
    <div v-if="exploring" class="vp__sandbox">
      <p class="vp__sandbox-banner">
        <UIcon name="i-lucide-flask-conical" />
        <span>{{ EXPLORE_DISCLAIMER }}</span>
      </p>
      <div class="vp__sandbox-seeds">
        <button class="ld-btn ld-btn--quiet" type="button" :disabled="!medianVotable.length" @click="seedMedian()">Partir de la médiane</button>
        <button class="ld-btn ld-btn--quiet" type="button" @click="seedBase()">Partir du statu quo</button>
        <button class="ld-btn ld-btn--quiet" type="button" @click="seedMine()">Partir de ma position</button>
      </div>
      <VoteParamSlider
        v-for="p in spec.params"
        :key="p.key"
        :param="p"
        :value="sandboxFull[fullIndexOf(p.key)] ?? p.min"
        neutral
        @update:value="setSandbox(p.key, $event)"
      />
      <button class="ld-btn" type="button" :disabled="session.status !== 'open'" @click="makeMine()">
        En faire ma position
      </button>
    </div>

    <!-- Carte « Pour moi » — masquée si rien de déclaré, jamais de chiffre inventé -->
    <div v-if="impactMine && impactBase" class="vp__impact">
      <h3 class="vp__impact-title">{{ IMPACT_CARD_TITLE }}</h3>
      <table class="vp__impact-table">
        <thead>
          <tr><th /><th>ma position</th><th>médiane</th><th>statu quo</th></tr>
        </thead>
        <tbody>
          <tr v-for="(line, i) in impactMine.perParam" :key="line.key">
            <th>{{ line.label }}</th>
            <td>{{ fmtAmount(line.amount) }}</td>
            <td>{{ fmtAmount(impactMedian?.perParam[i]?.amount) }}</td>
            <td>{{ fmtAmount(impactBase.perParam[i]?.amount) }}</td>
          </tr>
          <tr class="vp__impact-total">
            <th>total</th>
            <td>{{ fmtAmount(impactMine.total) }}</td>
            <td>{{ fmtAmount(impactMedian?.total) }}</td>
            <td>{{ fmtAmount(impactBase.total) }}</td>
          </tr>
        </tbody>
      </table>
      <p class="vp__impact-note">{{ IMPACT_DISCLAIMER }}</p>
    </div>

    <p class="vp__count">
      {{ vectors.length }} position{{ vectors.length > 1 ? 's' : '' }} déposée{{ vectors.length > 1 ? 's' : '' }}
      <template v-if="quorum !== undefined"> · quorum : {{ quorum }}</template>
    </p>

    <!-- Le geste du garant -->
    <div v-if="frozen && isSteward" class="vp__crystal">
      <p class="vp__crystal-card">{{ CRYSTALLIZE_CARD }}</p>
      <button class="vp__stamp-btn" type="button" @click="showCrystal = true">
        <UIcon name="i-lucide-stamp" />
        <span>{{ CRYSTALLIZE_ACTION }}</span>
      </button>
    </div>
    <p v-if="crystallizer && session.crystallizedAt" class="vp__crystallized">
      Cristallisée par {{ crystallizer }} le {{ new Date(session.crystallizedAt).toLocaleDateString('fr-FR') }}.
    </p>

    <UModal v-model:open="showCrystal" :title="CRYSTALLIZE_ACTION">
      <template #body>
        <div class="vp__modal">
          <p v-if="medianFull" class="vp__modal-median">
            <template v-for="(p, i) in spec.params" :key="p.key">
              <span>{{ p.label }} : <strong>{{ (medianFull[i] ?? 0).toLocaleString('fr-FR', { maximumFractionDigits: 2 }) }}</strong>{{ p.unit ? ` ${p.unit}` : '' }}</span>
            </template>
          </p>
          <p v-if="quorumShort" class="vp__modal-warn">
            Le quorum n'est pas atteint ({{ vectors.length }} sur {{ quorum }}) — le geste constatera le rejet.
          </p>
          <p v-for="key in bimodalKeys" :key="key" class="vp__modal-warn">
            <UIcon name="i-lucide-split" />
            <span>{{ votable.find(p => p.key === key)?.label }} : deux positions distinctes se dessinent.</span>
          </p>
          <div class="vp__modal-choices">
            <template v-if="bimodalKeys.length">
              <button class="ld-btn" type="button" @click="doCrystallize()">{{ CRYSTALLIZE_CHOICES[0] }}</button>
              <button class="ld-btn ld-btn--ghost" type="button" @click="split()">{{ CRYSTALLIZE_CHOICES[1] }}</button>
              <button class="ld-btn ld-btn--ghost" type="button" @click="reframe()">{{ CRYSTALLIZE_CHOICES[2] }}</button>
            </template>
            <template v-else>
              <button class="ld-btn" type="button" @click="doCrystallize()">{{ CRYSTALLIZE_ACTION }}</button>
              <button class="ld-btn ld-btn--quiet" type="button" @click="showCrystal = false">Annuler</button>
            </template>
          </div>
          <p v-if="error" class="vp__error">{{ error }}</p>
        </div>
      </template>
    </UModal>
  </section>
</template>

<style scoped>
.vp { padding: 1.25rem; display: flex; flex-direction: column; gap: 1rem; }
.vp__explain { margin: 0; font-size: 0.875rem; color: var(--mood-text-muted); line-height: 1.5; }
.vp__frozen {
  display: flex; align-items: center; gap: 0.5rem; margin: 0;
  padding: 0.6rem 0.9rem; border-radius: var(--r-input);
  background: var(--mood-status-fige-bg); color: var(--mood-status-fige);
  font-weight: 700; font-size: 0.9375rem;
}
.vp__frame {
  display: flex; flex-direction: column; gap: 0.9rem;
  padding: 0.9rem; border-radius: var(--r-input);
  background: var(--mood-bg);
}
.vp__frame-tag {
  align-self: flex-start; font-size: 0.7rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--mood-text-muted); background: var(--mood-surface);
  padding: 2px 10px; border-radius: var(--r-pill);
}
.vp__actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.vp__error { margin: 0; font-size: 0.875rem; font-weight: 600; color: var(--mood-error); }
.vp__sandbox {
  display: flex; flex-direction: column; gap: 0.9rem;
  padding: 0.9rem; border-radius: var(--r-input);
  background: color-mix(in srgb, var(--mood-text-muted) 7%, var(--mood-bg));
}
.vp__sandbox-banner {
  display: flex; align-items: center; gap: 0.45rem; margin: 0;
  font-size: 0.8125rem; font-weight: 700; color: var(--mood-text-muted);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.vp__sandbox-seeds { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.vp__sandbox > .ld-btn { align-self: flex-start; }
.vp__impact {
  background: var(--mood-accent-soft); border-radius: var(--r-input);
  padding: 0.9rem 1rem; display: flex; flex-direction: column; gap: 0.5rem;
}
.vp__impact-title { margin: 0; font-size: 0.9375rem; font-weight: 800; color: var(--mood-accent); }
.vp__impact-table { border-collapse: collapse; font-size: 0.875rem; width: 100%; }
.vp__impact-table th, .vp__impact-table td { text-align: right; padding: 0.2rem 0.45rem; }
.vp__impact-table tbody th { text-align: left; font-weight: 600; }
.vp__impact-table thead th { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--mood-text-muted); }
.vp__impact-total { font-weight: 800; }
.vp__impact-note { margin: 0; font-size: 0.75rem; font-style: italic; color: var(--mood-text-muted); }
.vp__count { margin: 0; font-size: 0.8125rem; color: var(--mood-text-muted); }
.vp__crystal {
  display: flex; flex-direction: column; align-items: center; gap: 0.6rem;
  padding: 1rem; border-radius: var(--r-input); background: var(--mood-status-fige-bg);
}
.vp__crystal-card { margin: 0; font-weight: 600; font-size: 0.9375rem; text-align: center; }
.vp__stamp-btn {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.65rem 1.5rem; border-radius: 10px; cursor: pointer;
  transform: rotate(-2deg);
  font-weight: 800; font-size: 1rem; letter-spacing: 0.03em;
  color: var(--mood-accent); background: var(--mood-surface);
  box-shadow: inset 0 0 0 2.5px var(--mood-accent), 0 2px 8px var(--mood-shadow);
  transition: transform 0.12s ease;
}
.vp__stamp-btn:hover { transform: rotate(-2deg) translateY(-1px); }
.vp__stamp-btn:active { transform: rotate(-2deg) translateY(0); }
.vp__crystallized { margin: 0; font-size: 0.875rem; font-weight: 600; color: var(--mood-status-vigueur); }
.vp__modal { display: flex; flex-direction: column; gap: 0.75rem; }
.vp__modal-median { margin: 0; display: flex; flex-wrap: wrap; gap: 0.35rem 1rem; font-size: 0.9375rem; }
.vp__modal-warn {
  display: flex; align-items: baseline; gap: 0.4rem; margin: 0;
  font-size: 0.875rem; font-weight: 600; color: var(--mood-warning);
}
.vp__modal-choices { display: flex; flex-wrap: wrap; gap: 0.5rem; }
</style>
