/**
 * Shared pure helpers for the texts screens — library (/textes), living
 * document (/textes/[slug]) and formula atelier (/textes/formules).
 * No I/O, no store access: pages assemble, these functions compute.
 */

import type {
  Clause,
  ClauseVersion,
  CollectiveSettings,
  Decision,
  DecisionStatus,
  Id,
  InertiaPreset,
  ISODate,
  Json,
  Person,
  Protocol,
  Vote,
  VoteSession,
} from '~/types/domain'
import { INERTIA_LABELS } from '~/lexicon'

// ── Shared view-model shapes (SFC scripts cannot export types) ──
/** A proposed clause version, content first — author folded behind it. */
export interface ProposedEntry {
  version: ClauseVersion
  authorName: string
  decisionId?: Id
  decisionTitle?: string
}

/** One link of a clause's decision chain (founding, then amendments). */
export interface ChainEntry {
  id: Id
  title: string
  status: DecisionStatus
  decidedAt?: string
}

/** Query-preset state of the formula atelier (?W=&T=&M=&B=&G=&C=&S=). */
export interface AtelierInit {
  W?: number
  T?: number
  M?: number
  B?: number
  G?: number
  C?: number
  S?: number
}

// ── Inertia presets — REAL wiring (types/domain.ts) ──────────
export const INERTIA_PARAMS: Record<InertiaPreset, { majorityPct: number; gradientExponent: number }> = {
  low: { majorityPct: 50, gradientExponent: 0.1 },
  standard: { majorityPct: 50, gradientExponent: 0.2 },
  high: { majorityPct: 60, gradientExponent: 0.4 },
  max: { majorityPct: 66, gradientExponent: 0.6 },
}

/** Tint per preset — derived from the mood custom properties. */
export const INERTIA_COLORS: Record<InertiaPreset, string> = {
  low: 'var(--mood-success)',
  standard: 'var(--mood-accent)',
  high: 'var(--mood-warning)',
  max: 'var(--mood-error)',
}

export const INERTIA_ORDER: readonly InertiaPreset[] = ['low', 'standard', 'high', 'max']

// ── Sections — labels + icons for known tags, graceful fallback ──
const SECTION_META: Record<string, { label: string; icon: string }> = {
  preambule: { label: 'Préambule', icon: 'i-lucide-compass' },
  introduction: { label: 'Introduction', icon: 'i-lucide-scroll-text' },
  mission: { label: 'Mission', icon: 'i-lucide-target' },
  composition: { label: 'Composition', icon: 'i-lucide-users' },
  engagements: { label: 'Engagements', icon: 'i-lucide-heart-handshake' },
  fondamental: { label: 'Engagements fondamentaux', icon: 'i-lucide-shield-check' },
  technique: { label: 'Engagements techniques', icon: 'i-lucide-wrench' },
  qualification: { label: 'Qualification', icon: 'i-lucide-graduation-cap' },
  aspirant: { label: 'Aspirant forgeron', icon: 'i-lucide-user-plus' },
  certificateur: { label: 'Certificateur forgeron', icon: 'i-lucide-stamp' },
  conclusion: { label: 'Conclusion', icon: 'i-lucide-bookmark' },
  annexe: { label: 'Annexes', icon: 'i-lucide-paperclip' },
  formule: { label: 'Formule de vote', icon: 'i-lucide-calculator' },
  inertie: { label: 'Réglage de l\'inertie', icon: 'i-lucide-sliders-horizontal' },
  ordonnancement: { label: 'Ordonnancement', icon: 'i-lucide-list-ordered' },
  // the raw tag never reaches the UI — this label replaces it
  triage: { label: 'Seuils et fenêtres', icon: 'i-lucide-route' },
  protocoles: { label: 'Protocoles de vote', icon: 'i-lucide-vote' },
}

export function sectionMeta(tag: string): { label: string; icon: string } {
  const known = SECTION_META[tag]
  if (known) return known
  const label = tag.charAt(0).toUpperCase() + tag.slice(1)
  return { label, icon: 'i-lucide-file-text' }
}

// ── Formatting ───────────────────────────────────────────────
export function formatDateFr(iso: ISODate | undefined): string {
  if (!iso) return ''
  return new Date(iso).toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' })
}

export function formatInt(n: number): string {
  return n.toLocaleString('fr-FR')
}

// ── Live sessions on clause amendments — the mini-gauges ─────
export interface ClauseSessionGauge {
  clauseId: Id
  clauseCode: string
  decisionId: Id
  decisionTitle: string
  positions: number // last active votes cast
  listSize: number // arrested voter list size
  closesAt: ISODate
  frozen: boolean
}

/** Last-active-vote count of a session (supersedes chains resolved). */
function activeVoteCount(sessionId: Id, votes: Vote[]): number {
  const sessionVotes = votes.filter(v => v.sessionId === sessionId)
  const superseded = new Set(
    sessionVotes.map(v => v.supersedesVoteId).filter((id): id is Id => id !== undefined),
  )
  return sessionVotes.filter(v => !superseded.has(v.id)).length
}

/**
 * Real running sessions (open or frozen) over decisions that amend one of
 * `clauses` — one gauge per amending decision, latest session wins.
 */
export function liveClauseGauges(
  clauses: Clause[],
  decisions: Decision[],
  sessions: VoteSession[],
  votes: Vote[],
): ClauseSessionGauge[] {
  const byId = new Map(clauses.map(c => [c.id, c]))
  const gauges: ClauseSessionGauge[] = []
  for (const decision of decisions) {
    if (!decision.amendsClauseId) continue
    const clause = byId.get(decision.amendsClauseId)
    if (!clause) continue
    const latest = sessions
      .filter(s => s.decisionId === decision.id)
      .sort((a, b) => (a.opensAt < b.opensAt ? 1 : -1))[0]
    if (!latest || (latest.status !== 'open' && latest.status !== 'frozen')) continue
    gauges.push({
      clauseId: clause.id,
      clauseCode: clause.code,
      decisionId: decision.id,
      decisionTitle: decision.title,
      positions: activeVoteCount(latest.id, votes),
      listSize: latest.corpusSize,
      closesAt: latest.closesAt,
      frozen: latest.status === 'frozen',
    })
  }
  return gauges
}

/** Engraved decisions linked to these clauses (井 filter of the library). */
export function engravedCount(clauses: Clause[], decisions: Decision[]): number {
  const ids = new Set(clauses.map(c => c.id))
  return decisions.filter(d => d.engraving && d.amendsClauseId && ids.has(d.amendsClauseId)).length
}

// ── Versions per clause ──────────────────────────────────────
/** The 'current' version of a clause, preferring clause.currentVersionId. */
export function currentVersionOf(clause: Clause, versions: ClauseVersion[]): ClauseVersion | undefined {
  const mine = versions.filter(v => v.clauseId === clause.id)
  if (clause.currentVersionId) {
    const pinned = mine.find(v => v.id === clause.currentVersionId)
    if (pinned) return pinned
  }
  return mine.find(v => v.status === 'current')
}

export function proposedVersionsOf(clause: Clause, versions: ClauseVersion[]): ClauseVersion[] {
  return versions
    .filter(v => v.clauseId === clause.id && v.status === 'proposed')
    .sort((a, b) => (a.createdAt < b.createdAt ? -1 : 1))
}

// ── Pact setting clauses — the VALUE IN CLEAR ────────────────
const EFFECTS_LABELS: Record<string, string> = {
  none: 'aucune exigence',
  structural: 'décisions structurantes',
  binding: 'décisions engageantes et structurantes',
}

/**
 * One French sentence stating the current value of a Pact setting clause
 * (« Petit groupe : jusqu'à 5 personnes »). `protocolName` resolves the
 * protocols.* keys; returns null when nothing readable can be said.
 */
export function settingSentence(
  key: string,
  value: Json | undefined,
  protocolName?: string,
): string | null {
  if (value === undefined && !key.startsWith('protocols.')) return null
  switch (key) {
    case 'triage.smallGroupMax': return `Petit groupe : jusqu'à ${value} personnes`
    case 'triage.collectiveMin': return `Grand corps : à partir de ${value} personnes`
    case 'triage.consentMax': return `Tour de consentement : jusqu'à ${value} personnes`
    case 'triage.objectionWindowHours': return `Fenêtre d'objection : ${value} heures`
    case 'triage.adviceWindowHours': return `Fenêtre d'avis : ${value} heures`
    case 'triage.framingDays': return `Temps de formulation : ${value} jours`
    case 'triage.concernEscalateRatio':
      return `Affluence : traitement obligatoire à partir de ${Math.round(Number(value) * 100)} % de concernés`
    case 'triage.recurrenceThreshold':
      return `Récurrence : ${value} décisions semblables suggèrent une règle`
    case 'triage.reviewDelayDays': return `Épreuve du réel : ${value} jours après adoption`
    case 'triage.requireEffects':
      return `Matière exigée : ${EFFECTS_LABELS[String(value)] ?? String(value)}`
  }
  if (key.startsWith('protocols.clauseByInertia.')) {
    const preset = key.slice('protocols.clauseByInertia.'.length) as InertiaPreset
    const label = INERTIA_LABELS[preset]
    return protocolName && label ? `Amender une clause en ${label} : ${protocolName}` : null
  }
  if (key.startsWith('protocols.')) {
    return protocolName ? `Protocole retenu : ${protocolName}` : null
  }
  return null
}

// ── Full clause view assembly (page /textes/[slug]) ──────────
export interface ClauseViewCtx {
  versions: ClauseVersion[]
  decisions: Decision[]
  people: Person[]
  protocols: Protocol[]
  settings: CollectiveSettings | null
  gauges: Map<Id, ClauseSessionGauge>
  isPact: boolean
  memberCount: number
}

export interface ClauseView {
  clause: Clause
  current?: ClauseVersion
  proposed: ProposedEntry[]
  founding?: ChainEntry
  amendments: ChainEntry[]
  settingText: string | null
  gauge: ClauseSessionGauge | null
  /** « Vote WoT standard — 30 jours » — le protocole d'amendement résolu. */
  amendProtocol?: string
  atelierLink: string | null
  protectedClause: boolean
  status: { label: string; css: string }
}

/** The amendment protocol a clause resolves to, via the Pact settings. */
function amendProtocolOf(preset: InertiaPreset, ctx: ClauseViewCtx): Protocol | undefined {
  const id = ctx.settings?.protocolByRange.clauseByInertia?.[preset]
    ?? ctx.settings?.protocolByRange.consent
  return ctx.protocols.find(p => p.id === id)
}

/** Everything a clause row + detail needs, computed once per clause. */
export function buildClauseView(clause: Clause, ctx: ClauseViewCtx): ClauseView {
  const current = currentVersionOf(clause, ctx.versions)
  const proposedVersions = proposedVersionsOf(clause, ctx.versions)

  const proposed: ProposedEntry[] = proposedVersions.map((version) => {
    const decision = ctx.decisions.find(d => d.id === version.decisionId)
    const author = ctx.people.find(p => p.id === decision?.authorId)
    return {
      version,
      authorName: author?.displayName ?? 'quelqu\'un du collectif',
      ...(decision !== undefined
        ? { decisionId: decision.id, decisionTitle: decision.title }
        : {}),
    }
  })

  const chain: ChainEntry[] = ctx.decisions
    .filter(d => d.amendsClauseId === clause.id)
    .sort((a, b) => ((a.decidedAt ?? a.createdAt) < (b.decidedAt ?? b.createdAt) ? -1 : 1))
    .map(d => ({
      id: d.id,
      title: d.title,
      status: d.status,
      ...(d.decidedAt !== undefined ? { decidedAt: d.decidedAt } : {}),
    }))
  const [founding, ...amendments] = chain

  const protocolNameForValue = typeof current?.settingValue === 'string'
    ? ctx.protocols.find(p => p.id === current.settingValue)?.name
    : undefined
  const settingText = ctx.isPact && clause.settingKey
    ? settingSentence(clause.settingKey, current?.settingValue, protocolNameForValue)
    : null

  const protocol = amendProtocolOf(clause.inertia, ctx)
  const atelierLink = protocol?.method === 'binary'
    ? `/textes/formules?W=${ctx.memberCount}&M=${protocol.formula.majorityPct}`
      + `&B=${protocol.formula.baseExponent}&G=${protocol.formula.gradientExponent}`
      + `&C=${protocol.formula.constantBase}`
    : null

  const gauge = ctx.gauges.get(clause.id) ?? null
  const status = gauge
    ? { label: 'en vote', css: 'status-voting' }
    : proposed.length > 0
      ? { label: 'version proposée', css: 'status-framing' }
      : { label: 'en vigueur', css: 'status-adopted' }

  return {
    clause,
    ...(current !== undefined ? { current } : {}),
    proposed,
    ...(founding !== undefined ? { founding } : {}),
    amendments,
    settingText,
    gauge,
    ...(protocol !== undefined
      ? { amendProtocol: `${protocol.name} — ${protocol.durationDays} jours` }
      : {}),
    atelierLink,
    protectedClause: clause.settingKey?.startsWith('protocols.clauseByInertia.') === true,
    status,
  }
}
