// KueSync — Companion variables
// © 2026 Krometis Analytics. All rights reserved.

export function getVariableDefinitions() {
  const defs = [
    { variableId: 'connected', name: 'Any agent connected (true/false)' },
    { variableId: 'connected_count', name: 'Agents connected (socket open)' },
    { variableId: 'healthy_count', name: 'Agents healthy (reporting, not stale)' },
    { variableId: 'target_count', name: 'Agents configured' },
    { variableId: 'app', name: 'Active app on primary machine' },
    { variableId: 'current_slide', name: 'Current slide — real Keynote number (primary)' },
    { variableId: 'total_slides', name: 'Total slides — real count incl. hidden (primary)' },
    { variableId: 'current_slide_shown', name: 'Current slide — excluding hidden/skipped (primary)' },
    { variableId: 'total_slides_shown', name: 'Total slides — excluding hidden/skipped (primary)' },
    { variableId: 'playing', name: 'Slideshow playing (primary, true/false)' },
    { variableId: 'fleet_in_sync', name: 'All machines on the same slide (in sync / DRIFT)' },
    { variableId: 'show_running', name: 'A slideshow is running on the primary (true/false)' },
    { variableId: 'slide_top', name: 'Slide button top line (Slide / No show)' },
    { variableId: 'slide_bot', name: 'Slide button bottom line (5/10 / stopped)' },
    { variableId: 'fleet_phase', name: 'Fleet phase (paused when no show / in sync / DRIFT)' },
    { variableId: 'fleet_spread', name: 'Slide spread across machines (0 = locked)' },
    { variableId: 'fleet_slides', name: 'Per-machine slides, e.g. 5·5·4 (✕ = unhealthy)' },
    { variableId: 'cue_running', name: 'Cue running (true/false)' },
    // Cue light (hardware-style): lamp state mirroring the fleet's last move direction.
    { variableId: 'cue_signal', name: 'Cue light signal (go / back / idle)' },
    { variableId: 'cue_go', name: 'Cue light = GO / forward (true/false)' },
    { variableId: 'cue_back', name: 'Cue light = BACK / reverse (true/false)' },
    { variableId: 'build_step', name: 'Build step on current slide (inferred; Nexts since slide changed)' },
    { variableId: 'build_index', name: 'Build position on current slide (real; PowerPoint, — if N/A)' },
    { variableId: 'build_count', name: 'Total builds on current slide (real; PowerPoint, — if N/A)' },
    { variableId: 'build_readout', name: 'Build position e.g. 2/5 (— when not reported / no builds)' },
    { variableId: 'slide_notes', name: 'Presenter notes for the current slide (primary)' },
    { variableId: 'slide_name', name: 'Current slide name/title (primary)' },
    { variableId: 'presentation_name', name: 'Active presentation/document name (primary)' },
    { variableId: 'cue_dwell_remaining', name: 'Seconds left on current cue slide' },
    // v6.0.8 video clock — TRT / remaining / elapsed for the current slide's media.
    { variableId: 'media_trt', name: 'Video TRT — total running time, M:SS (— if none)' },
    { variableId: 'media_remaining', name: 'Video time remaining, M:SS (— if none)' },
    { variableId: 'media_elapsed', name: 'Video elapsed, M:SS (— if none)' },
    { variableId: 'media_remaining_secs', name: 'Video time remaining, whole seconds (for feedbacks)' },
    { variableId: 'media_playing', name: 'Video clock running (true/false)' },
    { variableId: 'media_has', name: 'Current slide has a timed video (true/false)' },
    { variableId: 'media_armed', name: 'Video cued, waiting for its play trigger (true/false)' },
    { variableId: 'media_time_source', name: 'Where the time came from: measured / planned (cue sheet) / not available' },
    { variableId: 'media_source_short', name: 'Video time source, short for a button: measured / planned / n/a' },
    { variableId: 'license_state', name: 'License state (trial/licensed/expired)' },
    { variableId: 'trial_days_left', name: 'Trial days remaining' },
    { variableId: 'licensed_to', name: 'Licensed customer name' },
    { variableId: 'license_provider', name: 'Machine providing the license (Companion or an agent IP)' },
    // v6 Leader/Follower — who is driving the fleet right now (machine number AND IP).
    { variableId: 'leader_present', name: 'A Leader is driving the fleet (true/false)' },
    { variableId: 'leader_machine', name: 'Current Leader — machine number (e.g. M2, — = none)' },
    { variableId: 'leader_ip', name: 'Current Leader — IP/host (— = none)' },
    { variableId: 'leader_label', name: 'Current Leader — machine + IP (e.g. M2 · 192.168.1.51)' },
    { variableId: 'leader_name', name: 'Current Leader — friendly name (— = none)' },
    { variableId: 'fleet_versions', name: 'Agent versions across the fleet (compact)' },
    { variableId: 'version_status', name: 'Fleet version status (OK / MIXED / UPDATE)' },
    { variableId: 'update_version', name: 'Newest KueSync version available (blank = up to date)' },
    { variableId: 'late_ms', name: 'Worst command lateness across the fleet, ms (0 = on time)' },
    { variableId: 'clock_source', name: 'Fleet timebase: ptp (grandmaster-locked) / ptp-holdover / peer' },
    // 0.10.2 KueSync Timer (fleet show timer). Values are updated once a second from the leader's authoritative timer.
    { variableId: 'kuetimer_time', name: 'KueSync Timer display (M:SS / +overtime / time of day)' },
    { variableId: 'kuetimer_state', name: 'KueSync Timer state (idle/set/running/warning/overtime/countup/tod)' },
    { variableId: 'kuetimer_mode', name: 'KueSync Timer mode (down/up/tod)' },
    { variableId: 'kuetimer_running', name: 'KueSync Timer running (true/false)' },
    { variableId: 'kuetimer_loaded', name: 'KueSync Timer LOADED — armed with a limit but not started (true/false)' },
    { variableId: 'kuetimer_remaining_s', name: 'KueSync Timer remaining seconds (negative = overtime; elapsed in count-up)' },
    { variableId: 'kuetimer_zerocolor', name: 'KueSync Timer at-zero colour (red/amber/white/green/blue or #rrggbb)' },
    { variableId: 'kuetimer_message', name: 'KueSync Timer on-screen message (empty = none)' },
    { variableId: 'kuetimer_limit', name: 'KueSync Timer set limit, M:SS' },
    { variableId: 'kuetimer_warn', name: 'KueSync Timer warn threshold, M:SS (or "off")' },
    { variableId: 'kuetimer_mode_label', name: 'KueSync Timer mode, readable (Countdown / Count up / Time of day)' },
    { variableId: 'kuetimer_atzero_label', name: 'KueSync Timer at-zero behaviour, readable (Blink / Hold 0:00 / Count overtime)' },
    { variableId: 'kuetimer_summary', name: 'KueSync Timer full setup summary (limit · warn · at-zero · state) — for an at-a-glance status button' },

    // ── 0.12.15 TIMER CUES ────────────────────────────────────────────────────────────────────────────────
    // Companion is an OPERATOR surface, so it may show the truth behind a paced presenter clock. The naming keeps
    // the two apart on purpose: kuetimer_time / kuetimer_remaining_s are what the PRESENTER sees, and every variable
    // below that says "real" is what is actually happening. Never build a presenter-facing display (a stage
    // monitor fed from Companion) off the real_* variables.
    { variableId: 'pgm_cue', name: 'Cue ON AIR (program) — name, empty if none' },
    { variableId: 'pvw_cue', name: 'Cue ARMED (preview) — name, empty if none' },
    { variableId: 'cue_index', name: 'Cue index ON AIR, 1-based (0 = none on air)' },
    { variableId: 'cue_pvw_index', name: 'Cue index ARMED, 1-based (0 = none armed)' },
    { variableId: 'cue_count', name: 'Number of timer cues in the list' },
    { variableId: 'cue_position', name: 'Cue position, readable ("3 / 12", or "—" when nothing is on air)' },
    { variableId: 'cue_advance_seq', name: 'Monotonic counter — ticks by 1 on every TAKE (drives a trigger)' },
    { variableId: 'kuetimer_endmode', name: 'Timer end mode (clock / hard out / paced-to-out)' },
    { variableId: 'kuetimer_warp', name: 'Presenter pacing active (true/false) — presenter clock differs from real time' },
    { variableId: 'kuetimer_warp_factor', name: 'Pacing factor, e.g. 1.091 (1 = no pacing, real time)' },
    { variableId: 'kuetimer_realremaining_s', name: 'REAL seconds remaining (operator truth; differs from kuetimer_remaining_s when paced)' },
    { variableId: 'kuetimer_real_time', name: 'REAL remaining, M:SS (operator truth)' },
    { variableId: 'kuetimer_out_tod', name: 'Actual out time of day, e.g. "2:00 pm" (empty when there is no hard / paced out)' },
    { variableId: 'cue_delta_s', name: 'Show over/under in SECONDS (positive = behind plan) — for trigger conditions' },
    { variableId: 'cue_delta', name: 'Show over/under, readable (+8:12 / \u22122:04 / "on plan")' },
    // 0.12.15 hardware edges: monotonic counters that tick ONCE per crossing, so a trigger watching them for a
    // change never misses an edge however briefly the level held. Pair with "Trigger on variable change".
    { variableId: 'cue_zero_seq', name: 'Monotonic counter \u2014 ticks by 1 each time the timer crosses INTO overtime (a hard-out edge for GPI)' },
    { variableId: 'cue_warn_seq', name: 'Monotonic counter \u2014 ticks by 1 each time the timer crosses INTO the warn window' },
    { variableId: 'cue_id', name: 'Stable id of the cue currently on air (empty when nothing is running)' },
    { variableId: 'cue_take_at', name: 'Wall-clock the current cue went on air, HH:MM:SS (empty when nothing is running)' },
    // 0.12.16 VIDEO COUNT-OUT (agent-fanned, honesty-gated: blank/false unless a source is live + accurate).
    { variableId: 'video_remaining', name: 'Video count-out remaining, M:SS (— when no clip is live)' },
    { variableId: 'video_remaining_secs', name: 'Video count-out remaining in SECONDS (0 when none) — for trigger conditions' },
    { variableId: 'video_source', name: 'Where the count-out comes from: Embedded (measured/entered), Mitti, PlaybackPro (— when none)' },
    { variableId: 'video_active', name: 'A clip is counting out (true/false)' },
    { variableId: 'video_state', name: 'Video count-out state: running / warn / over / paused / idle' },
    { variableId: 'video_out_seq', name: 'Monotonic counter — ticks by 1 each time a video reaches 0:00 (a video-out edge for GPI)' },

    // ── 0.12.49.1 NOTES SOURCE + LOOK (read from the leader's config) ────────────────────────────────────────
    { variableId: 'notes_source', name: 'KueSync Notes source — the machine feeding notes (AUTO / Leader / name·IP)' },
    { variableId: 'notes_backup', name: 'KueSync Notes backup source (none = no failover set)' },
    { variableId: 'clock_bg', name: 'Timer/Clock background colour (black/white/green/blue)' },
    { variableId: 'notes_bg', name: 'Notes background colour (follow clock / black/white/green/blue)' },
    { variableId: 'clock_chroma_text', name: 'Timer/Clock chroma text colour (white/black)' },
    { variableId: 'notes_chroma_text', name: 'Notes chroma text colour (white/black)' },
    { variableId: 'clock_pip', name: 'Kue-Light pip on the Timer (on/off)' },
    { variableId: 'notes_pip', name: 'Kue-Light pips on the Notes screen (on/off)' },
    { variableId: 'notes_pip_pos', name: 'Notes timer PIP position (tl/tc/tr/bl/bc/br)' },

    // ── 0.12.49.2 TIMER CUES + BACK-TIMING (read from the leader's cue list) ─────────────────────────────────
    { variableId: 'cue_list', name: 'Timer cue list — names with ▶ on air / ▷ armed (multi-line, for a display key)' },
    { variableId: 'cues_total_time', name: 'Timer cues — total real runtime of the list, M:SS' },
    { variableId: 'show_end_tod', name: 'Projected show END time-of-day (live, accounts for over/under)' },
    { variableId: 'show_end_planned_tod', name: 'Planned show end time-of-day (as programmed)' },
  ]
  // Per-machine slide readouts (so a confidence button can show which one drifted): slide_m1..m8.
  for (let i = 1; i <= 8; i++) defs.push({ variableId: `slide_m${i}`, name: `Slide on machine ${i} (✕ = unhealthy, — = none)` })
  // Per-machine friendly name + agent version.
  for (let i = 1; i <= 8; i++) defs.push({ variableId: `name_m${i}`, name: `Friendly name of machine ${i}` })
  for (let i = 1; i <= 8; i++) defs.push({ variableId: `agent_version_m${i}`, name: `Agent version on machine ${i}` })
  // 0.9.6: per-machine follower Active state (for button text / triggers; the feedback handles colour).
  for (let i = 1; i <= 8; i++) defs.push({ variableId: `active_m${i}`, name: `Follower ${i} — Active / Inactive` })
  // 0.12.53: per-machine SLIDE-DRIVER state (Yes/No) — for button text / triggers; the feedback handles colour.
  for (let i = 1; i <= 8; i++) defs.push({ variableId: `driver_m${i}`, name: `Follower ${i} — Slide driver (Yes / No)` })
  // 0.12.55: fleet subnet / auto-rebind awareness (from the Leader's canonical fleet view).
  defs.push({ variableId: 'followers_offsubnet', name: 'Fleet — followers on a different subnet (count)' })
  defs.push({ variableId: 'followers_roamed', name: 'Fleet — followers auto-reconnected on a new IP (count)' })
  defs.push({ variableId: 'fleet_subnet_note', name: 'Fleet — subnet status note' })
  return defs
}

export function updateVariables(self) {
  const p = self.primary && self.primary()
  const s = p ? p.state : { app: '', playing: false, slide: 0, total: 0 }
  const connectedCount = self.connectedCount ? self.connectedCount() : 0
  const healthyCount = self.healthyCount ? self.healthyCount() : connectedCount
  const ent = self.fleetEntitlement ? self.fleetEntitlement() : self.license.evaluate()
  const fleet = self.fleetSync ? self.fleetSync() : { inSync: true, spread: 0 }

  // Per-machine slides + a compact fleet string ("5·5·4", ✕ = configured-but-unhealthy).
  const targets = self.targets || []
  const vals = {}
  const parts = []
  const verSet = new Set()
  for (let i = 0; i < 8; i++) {
    const t = targets[i]
    let cell = '—'
    if (t) cell = self.healthy && self.healthy(t) ? String(t.state.slide || 0) : '✕'
    vals[`slide_m${i + 1}`] = cell
    vals[`name_m${i + 1}`] = t ? (t.state.label || '') : ''
    vals[`agent_version_m${i + 1}`] = t ? (t.state.agentVersion || '') : ''
    vals[`active_m${i + 1}`] = t ? (t.state.followerActive !== false ? 'Active' : 'Inactive') : '—'
    vals[`driver_m${i + 1}`] = t ? (t.state.slideDriver === true ? 'Yes' : 'No') : '—'
    if (t && t.state.agentVersion) verSet.add(t.state.agentVersion)
    if (t) parts.push(cell)
  }
  // 0.12.55: fleet subnet / auto-rebind awareness, from the Leader's canonical fleet. offSubnet = a follower
  // connected across subnets (works fine on the best path; the operator may want it local); roamed = it was
  // auto-reconnected on a new IP after a network change.
  { const lead = self.leaderTarget ? self.leaderTarget() : null
    const ff = lead && lead.state && Array.isArray(lead.state.fleetFollowers) ? lead.state.fleetFollowers : []
    const offN = ff.filter((f) => f && f.offSubnet).length
    const roamN = ff.filter((f) => f && f.roamed).length
    vals.followers_offsubnet = String(offN)
    vals.followers_roamed = String(roamN)
    vals.fleet_subnet_note = offN ? `${offN} follower${offN > 1 ? 's' : ''} on another subnet` : 'all on Leader subnet' }

  // Fleet version awareness: MIXED if connected agents disagree, UPDATE if a newer build is published.
  const updVer = targets.map((t) => t && t.state.updateVersion).find((v) => v) || ''
  const versionStatus = updVer ? 'UPDATE' : (verSet.size > 1 ? 'MIXED' : 'OK')

  // v6 Leader/Follower — resolve the active Leader (machine number + IP) for the companion button.
  const ld = self.currentLeader ? self.currentLeader() : { present: false, machine: null, host: null }
  const ldMachine = ld.present && ld.machine ? `M${ld.machine}` : (ld.present ? 'M?' : '—')
  const ldIp = ld.present && ld.host ? ld.host : '—'
  const ldLabel = ld.present ? `${ldMachine} · ${ldIp}`.trim() : '— none —'

  // Show-running awareness: when the primary is NOT in slideshow, the readouts should say so rather
  // than showing a stale slide number or a sync verdict.
  const playing = !!s.playing
  const shownCur = (s.slideShown != null ? s.slideShown : s.slide) || 0
  const shownTot = (s.totalShown != null ? s.totalShown : s.total) || 0
  const bi = s.buildIndex != null ? s.buildIndex : -1
  const bc = s.buildCount != null ? s.buildCount : -1
  const buildReadout = (playing && bi >= 0 && bc >= 0) ? `${bi}/${bc}` : '—'
  const pmeta = (p && p.meta) || { presentationName: '', slideName: '', notes: '' }

  self.setVariableValues({
    ...vals,
    show_running: String(playing),
    slide_top: playing ? 'Slide' : 'No show',
    slide_bot: playing ? `${shownCur}/${shownTot}` : 'stopped',
    fleet_phase: !playing ? 'paused' : (fleet.inSync ? 'in sync' : 'DRIFT'),
    build_index: bi >= 0 ? bi : '—',
    build_count: bc >= 0 ? bc : '—',
    build_readout: buildReadout,
    slide_notes: pmeta.notes || '',
    slide_name: pmeta.slideName || '',
    presentation_name: pmeta.presentationName || '',
    leader_present: String(!!ld.present),
    leader_machine: ldMachine,
    leader_ip: ldIp,
    leader_label: ldLabel,
    leader_name: ld.present ? (ld.name || '—') : '—',
    fleet_versions: [...verSet].join(' · ') || '—',
    version_status: versionStatus,
    update_version: updVer,
    late_ms: Math.max(0, ...targets.map((t) => (t && t.state && t.state.firedLateMs) || 0)),
    clock_source: (targets.find((t) => t && t.state && t.state.clockSource) || {}).state?.clockSource || 'peer',
    connected: String(connectedCount > 0),
    connected_count: connectedCount,
    healthy_count: healthyCount,
    target_count: targets.length,
    app: s.app || '',
    current_slide: s.slide || 0,
    total_slides: s.total || 0,
    current_slide_shown: (s.slideShown != null ? s.slideShown : s.slide) || 0,
    total_slides_shown: (s.totalShown != null ? s.totalShown : s.total) || 0,
    playing: String(s.playing),
    fleet_in_sync: fleet.inSync ? 'in sync' : 'DRIFT',
    fleet_spread: fleet.spread || 0,
    fleet_slides: parts.join('·') || '—',
    cue_running: String(self.cue.running),
    cue_signal: self.cueState ? self.cueState() : 'idle',
    cue_go: String((self.cueState ? self.cueState() : 'idle') === 'go'),
    cue_back: String((self.cueState ? self.cueState() : 'idle') === 'back'),
    cue_dwell_remaining: self.cue.dwellRemaining || 0,
    license_state: ent.state,
    trial_days_left: ent.trialDaysLeft || 0,
    licensed_to: ent.licensedTo || '',
    license_provider: ent.provider || '',
  })
}
