// KueSync — ready-made button presets ("a ton of simple options")
// © 2026 Krometis Analytics. All rights reserved.

import { combineRgb } from '@companion-module/base'

const WHITE = combineRgb(255, 255, 255)
const BLACK = combineRgb(0, 0, 0)
const DARK = combineRgb(30, 30, 30)
const GREEN = combineRgb(0, 120, 0)
const RED = combineRgb(150, 30, 30)

// size:'auto' lets Companion scale each button's text to fit the key — so a long device name or a
// two-line label (e.g. "Stage Right · Inactive") shrinks to fit instead of wrapping/clipping. Callers
// that need a fixed size (big arrows, tiny notes) still override via the style arg.
function button(category, text, actionId, options = {}, feedbacks = [], style = {}) {
  return {
    type: 'button',
    category,
    name: text.replace(/\n/g, ' '),
    style: { text, size: '14', color: WHITE, bgcolor: DARK, ...style },
    steps: [{ down: [{ actionId, options }], up: [] }],
    feedbacks,
  }
}

export function getPresets(self) {
  const v = (name) => `$(${self.label}:${name})`
  const presets = {}
  const TRANSPORT = 'KueSync · Transport'
  const CUE = 'KueSync · Cue / Loop'
  const GOTO = 'KueSync · Go to slide'
  const STATUS = 'KueSync · Status'
  const MEDIA = 'KueSync · Media'
  const MONITOR = 'KueSync · Monitor'

  // ---- transport (all machines) ----
  // DEFAULT NEXT/PREV play in-slide builds & animations, synced across the fleet by timing.
  presets.prev = button(TRANSPORT, 'PREV', 'prev', { mode: 'relative' })
  presets.next = button(TRANSPORT, 'NEXT', 'next', { mode: 'relative' }, [{ feedbackId: 'playing', options: {} }])
  // STATIC variants — absolute slide, self-correcting, for build-free decks / video walls (skip builds).
  presets.prev_static = button(TRANSPORT, 'PREV\nSTATIC', 'prev', { mode: 'synced' })
  presets.next_static = button(TRANSPORT, 'NEXT\nSTATIC', 'next', { mode: 'synced' })
  presets.first = button(TRANSPORT, 'FIRST', 'first')
  presets.last = button(TRANSPORT, 'LAST', 'last')
  presets.start = button(TRANSPORT, 'START', 'start')
  presets.stop = button(TRANSPORT, 'STOP', 'stop')
  presets.jump_fwd = button(TRANSPORT, '+5', 'jump', { dir: 'forward', count: 5 })
  presets.jump_back = button(TRANSPORT, '-5', 'jump', { dir: 'back', count: 5 })
  presets.sync_now = button(TRANSPORT, 'SYNC\nNOW', 'sync_now', {}, [], { bgcolor: GREEN })
  presets.resync = button(TRANSPORT, 'RESYNC\nALL', 'resync', { slide: '' }, [], { bgcolor: GREEN })

  // ---- followers: activate / deactivate (0.9.6 / 0.9.8 auto-populated) ----
  // A TOGGLE button per machine that both parks/un-parks the follower AND shows its state — green Active /
  // red Inactive. These now AUTO-POPULATE from the machines this connection is talking to: each is keyed by
  // the machine's IP and labelled with its DEVICE NAME (matching the leader's labeling), so a button always
  // hits the right physical machine regardless of IP-box order. Re-published when the fleet / a name changes.
  const FOLLOWERS = 'KueSync · Followers'
  const targets = self.targets || []
  targets.forEach((t, i) => {
    const host = String(t.baseHost || t.host)
    const nm = (t.state && t.state.label) ? t.state.label : `Machine ${i + 1}`
    presets[`follower_toggle_${i + 1}`] = button(
      FOLLOWERS, `${nm}\n${v('active_m' + (i + 1))}`, 'follower_toggle_active',
      { machine: host, resync: true },
      [{ feedbackId: 'follower_active', options: { machine: host, activeColor: combineRgb(0, 120, 0), inactiveColor: combineRgb(150, 30, 30) } }])
  })
  // Generic numbered fallbacks (work even before machines connect) + dedicated Activate / Deactivate examples.
  for (let m = 1; m <= 4; m++) {
    presets[`follower_toggle_num_m${m}`] = button(
      FOLLOWERS, `FOLL ${m}\n${v('active_m' + m)}`, 'follower_toggle_active',
      { machine: String(m), resync: true },
      [{ feedbackId: 'follower_active', options: { machine: String(m) } }])
  }
  presets.follower_activate_m1 = button(FOLLOWERS, 'ARM\nFOLL 1', 'follower_activate', { machine: '1', resync: true },
    [{ feedbackId: 'follower_active', options: { machine: '1' } }])
  presets.follower_deactivate_m1 = button(FOLLOWERS, 'PARK\nFOLL 1', 'follower_deactivate', { machine: '1' },
    [{ feedbackId: 'follower_active', options: { machine: '1' } }])

  // ---- followers: SLIDE DRIVER toggle (0.12.53) — one lit button per machine that both designates it as a
  // driver (its USB advancer / Perfect Cue / keystrokes drive the whole fleet via the Leader) AND shows its
  // state. Auto-populated from the connected machines, plus numbered fallbacks. ----
  targets.forEach((t, i) => {
    const host = String(t.baseHost || t.host)
    const nm = (t.state && t.state.label) ? t.state.label : `Machine ${i + 1}`
    presets[`follower_driver_${i + 1}`] = button(
      FOLLOWERS, `DRIVE\n${nm}\n${v('driver_m' + (i + 1))}`, 'follower_slidedriver_toggle',
      { machine: host },
      [{ feedbackId: 'follower_slidedriver', options: { machine: host, driverColor: combineRgb(0, 120, 0), offColor: combineRgb(27, 39, 51) } }])
  })
  for (let m = 1; m <= 4; m++) {
    presets[`follower_driver_num_m${m}`] = button(
      FOLLOWERS, `DRIVE\nFOLL ${m}\n${v('driver_m' + m)}`, 'follower_slidedriver_toggle',
      { machine: String(m) },
      [{ feedbackId: 'follower_slidedriver', options: { machine: String(m) } }])
  }

  // ---- launch numbered shows (0.10.0) — auto-populated from KueSync Link, one button per numbered deck ----
  // A press opens that deck on every machine at slide 1 (edit the button's "Machines" to target a subset).
  const LAUNCH = 'KueSync · Launch'
  ;(self.allShows ? self.allShows() : []).forEach((s) => {
    presets[`launch_${s.num}`] = button(LAUNCH, `LAUNCH\n${s.n}${s.title ? '\n' + String(s.title).slice(0, 10) : ''}`, 'launch_show', { show: String(s.num), machines: '' })
  })
  // A generic, editable example (works even before any numbered decks exist — set the number + machines).
  presets.launch_example = button(LAUNCH, 'LAUNCH\nSHOW#', 'launch_show', { show: '', machines: '' })

  // ---- deck control / cue / loop ----
  // GO DECK = arm: bring the show up FULL SCREEN at slide 1 on every machine and HOLD (no advance).
  // ── 0.12.15 TIMER CUES transport ──────────────────────────────────────────────────────────────────────
  // TAKE is amber, not green, and deliberately so: it is the one button here that puts something on air, and
  // it must not read like the harmless transport keys beside it. This matches KueSync Control, where the same
  // reasoning moved TAKE off green.
  presets.cue_take = button(CUE, 'TAKE', 'cue_take', { cue: 0 }, [], { bgcolor: combineRgb(46, 36, 19), color: combineRgb(240, 207, 138) })
  presets.cue_next = button(CUE, 'ARM\nNEXT', 'cue_preview_next')
  presets.cue_prev = button(CUE, 'ARM\nPREV', 'cue_preview_prev')
  presets.cue_stop = button(CUE, 'STOP\nCUE', 'cue_stop', {}, [], { bgcolor: RED })
  // An at-a-glance status key: what is on air, what is armed, and how far off plan the show is running.
  presets.cue_status = {
    type: 'button', category: CUE, name: 'Cue status (on air / armed / position)',
    style: { text: 'PGM\\n$(kuesync:pgm_cue)\\n$(kuesync:cue_position)', size: '7', color: WHITE, bgcolor: combineRgb(28, 16, 19) },
    steps: [], feedbacks: [],
  }
  presets.go_deck = button(CUE, 'GO\nDECK', 'go_deck', {},
    [{ feedbackId: 'playing', options: {} }])
  // (GO LIVE preset removed in 6.2.5 — it was GO DECK + SYNC ROLL fused into one press, which read
  // as redundant/confusing next to SYNC ROLL. The run_cue action it used is unchanged, so any
  // existing GO LIVE buttons keep working; we just stop offering it.)
  // SYNC ROLL = start the synced roll from the CURRENT slide (use after GO DECK). Continuously
  // advances all machines in lockstep, playing builds & animations; at the end Keynote's own "Loop
  // slideshow" wraps to slide 1 (enable it on the deck). Edit "Seconds per slide" for the interval.
  presets.sync_roll = button(CUE, 'SYNC\nROLL', 'run_cue',
    { defaultDwell: 8, perSlide: '', fromStart: false, loop: true, builds: true },
    [{ feedbackId: 'cue_running', options: {} }])
  // END SHOW = stop the roll AND exit the slideshow on every machine (take the fleet down).
  presets.end_show = button(CUE, 'END\nSHOW', 'end_show', {}, [], { bgcolor: RED })
  presets.run_cue = button(CUE, 'RUN\nCUE', 'run_cue',
    { defaultDwell: 8, perSlide: '', fromStart: false, loop: false, builds: true },
    [{ feedbackId: 'cue_running', options: {} }])
  // STATIC variants — absolute per-slide cue sheet (e.g. hold slide 7 for a 120s video), self-correcting,
  // SKIPS in-slide builds. Use on build-free decks / video walls where exact per-slide dwell matters.
  presets.sync_roll_static = button(CUE, 'SYNC\nROLL\nSTATIC', 'run_cue',
    { defaultDwell: 10, perSlide: '', fromStart: true, loop: true, builds: false },
    [{ feedbackId: 'cue_running', options: {} }])
  presets.run_cue_static = button(CUE, 'RUN\nCUE\nSTATIC', 'run_cue',
    { defaultDwell: 10, perSlide: '7=120', fromStart: false, loop: false, builds: false },
    [{ feedbackId: 'cue_running', options: {} }])
  // Panic stop: disengage ANY auto-advance (GO / LOOP / RUN, builds or static) and HOLD on the
  // current slide/build — sends no slide command, so nothing jumps. Always solid red so it's
  // unmistakable on the page.
  presets.stop_auto = button(CUE, 'STOP\nAUTO', 'cancel_cue', {}, [], { bgcolor: RED })
  presets.pause_cue = button(CUE, 'PAUSE\nCUE', 'pause_cue')
  presets.resume_cue = button(CUE, 'RESUME\nCUE', 'resume_cue')
  presets.cancel_cue = button(CUE, 'CANCEL\nCUE', 'cancel_cue')

  // ---- "Go to any slide" — editable: drop it on a button and set the slide number to anything ----
  presets.goto_any = button(GOTO, 'GOTO\nSLIDE#', 'goto', { slide: '1' })
  // ---- quick one-touch buttons for slides 1..40 (highlight the active slide) ----
  for (let n = 1; n <= 40; n++) {
    presets[`goto_${n}`] = button(GOTO, `${n}`, 'goto', { slide: String(n) },
      [{ feedbackId: 'on_slide', options: { slide: n } }])
  }

  // ---- status ----
  // Slide readout — counts EXCLUDE hidden/skipped slides (use current_slide / total_slides for the
  // real Keynote numbers).
  presets.status_slide = {
    type: 'button', category: STATUS, name: 'Current slide (excl. hidden)',
    style: { text: `${v('slide_top')}\n${v('slide_bot')}`, size: 'auto', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [{ feedbackId: 'connected', options: {} }],
  }
  // Machines HEALTHY (reporting, not just socket-open) / configured. Green only when all healthy.
  presets.status_agents = {
    type: 'button', category: STATUS, name: 'Machines healthy',
    style: { text: `Healthy\n${v('healthy_count')}/${v('target_count')}`, size: 'auto', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [{ feedbackId: 'all_healthy', options: {} }],
  }
  // Live confidence indicator: green when every healthy machine is on the same slide, RED the
  // moment any one slips. Press it to RESYNC ALL (snap the fleet back to the primary's slide).
  presets.status_fleet = {
    type: 'button', category: STATUS, name: 'Fleet in sync',
    style: { text: `FLEET\n${v('fleet_phase')}`, size: 'auto', color: WHITE, bgcolor: GREEN },
    steps: [{ down: [{ actionId: 'resync', options: { slide: '' } }], up: [] }],
    feedbacks: [{ feedbackId: 'fleet_in_sync', options: {} }],
  }
  // Per-machine slide readout (which one drifted, at a glance): e.g. 5·5·4  (✕ = unhealthy).
  presets.status_fleet_slides = {
    type: 'button', category: STATUS, name: 'Per-machine slides',
    style: { text: `${v('fleet_slides')}`, size: '18', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [{ actionId: 'resync', options: { slide: '' } }], up: [] }],
    feedbacks: [{ feedbackId: 'fleet_in_sync', options: {} }],
  }
  // Current Leader (v6 Leader/Follower): who is driving the fleet, by machine number AND IP.
  // Green when that Leader is healthy, amber if it's reported but stale (mid-failover), neutral when
  // there's no Leader (Companion-only operation). Reads "M2 · 192.168.1.51" / "— none —".
  presets.status_leader = {
    type: 'button', category: STATUS, name: 'Current Leader (machine + IP)',
    style: { text: `LEADER\n${v('leader_label')}`, size: 'auto', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }],
    feedbacks: [{ feedbackId: 'leader_known', options: {} }],
  }
  // Shows the status (trial / licensed) AND which machine is providing the authorization (Companion,
  // or the leader agent IP that holds the key).
  // Build position on the current slide (PowerPoint reports it; shows — for Keynote/Mac or no builds).
  // Greens with 'playing'; the FLEET button already turns red on build-level drift across the fleet.
  presets.status_build = {
    type: 'button', category: STATUS, name: 'Build position (PowerPoint)',
    style: { text: `BUILD\n${v('build_readout')}`, size: '18', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [{ feedbackId: 'playing', options: {} }],
  }
  presets.status_license = {
    type: 'button', category: STATUS, name: 'License (status + provider)',
    style: { text: `${v('license_state')}\n${v('license_provider')}`, size: 'auto', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [{ actionId: 'revalidate', options: {} }], up: [] }],
    feedbacks: [{ feedbackId: 'license_state', options: {} }],
  }

  // ---- media transport (PowerPoint) ----
  presets.media_play = button(MEDIA, 'PLAY /\nPAUSE', 'media_playpause', { machines: '' }, [{ feedbackId: 'playing', options: {} }])
  presets.media_end = button(MEDIA, 'VIDEO\nEND', 'media_end', { machines: '' })
  presets.media_stop = button(MEDIA, 'VIDEO\nSTOP', 'media_stop', { machines: '' }, [], { bgcolor: RED })

  // ---- video time: TRT / remaining / elapsed (v6.0.8) ----
  // Display buttons (no action). size 'auto' so the time value always fits (even "1:02:33"); short
  // labels so they don't wrap. "Remaining" + the combined button turn amber then red near the end.
  presets.media_remaining = {
    type: 'button', category: MEDIA, name: 'Video — time remaining',
    style: { text: `LEFT\n${v('media_remaining')}`, size: 'auto', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [{ feedbackId: 'media_ending', options: { amberAt: 30, redAt: 10 } }],
  }
  presets.media_trt = {
    type: 'button', category: MEDIA, name: 'Video — total running time (TRT)',
    style: { text: `TRT\n${v('media_trt')}`, size: 'auto', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [],
  }
  presets.media_elapsed = {
    type: 'button', category: MEDIA, name: 'Video — elapsed',
    style: { text: `ELAPS\n${v('media_elapsed')}`, size: 'auto', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [],
  }
  presets.media_time = {
    type: 'button', category: MEDIA, name: 'Video — remaining / TRT (combined)',
    style: { text: `${v('media_remaining')}\nof ${v('media_trt')}`, size: 'auto', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [{ feedbackId: 'media_ending', options: { amberAt: 30, redAt: 10 } }],
  }
  presets.media_source = {
    type: 'button', category: MEDIA, name: 'Video — time source (measured / planned)',
    style: { text: `SOURCE\n${v('media_source_short')}`, size: '14', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [],
  }
  presets.open_file = button(MEDIA, 'OPEN\nFILE', 'open_file', { path: '', machines: '' })

  // ---- live monitoring (OSCPoint-parity feedback) ----
  presets.monitor_notes = {
    type: 'button', category: MONITOR, name: 'Presenter notes (current slide)',
    style: { text: `${v('slide_notes')}`, size: '7', color: WHITE, bgcolor: BLACK, alignment: 'left:top' },
    steps: [{ down: [], up: [] }], feedbacks: [],
  }
  presets.monitor_presentation = {
    type: 'button', category: MONITOR, name: 'Presentation name',
    style: { text: `${v('presentation_name')}`, size: 'auto', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [{ feedbackId: 'connected', options: {} }],
  }
  presets.monitor_slide_name = {
    type: 'button', category: MONITOR, name: 'Slide name / title',
    style: { text: `${v('slide_name')}`, size: 'auto', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [],
  }

  // ---- cue light (hardware-style; output-only colored lamps) ----
  // Drop one of these on a key, or tile many to build a big cue light. They light from the fleet's
  // moves — green on forward, red on back — no press behaviour. Build a two-zone DSAN-style panel with
  // GO lamps on some keys and BACK lamps on others.
  const CUELIGHT = 'KueSync · Cue Light'
  // 0.12.2: BOOLEAN lamp feedbacks (lamp_go / lamp_back), rendered by core Companion so they survive an app
  // restart. Each preset carries an EXPLICIT style so the colour applies the instant the feedback goes true
  // (no reliance on defaultStyle). DELETE old cue-light buttons and drag these fresh to move onto the new path.
  const IDLE = combineRgb(14, 20, 25)
  const GO = combineRgb(47, 180, 107)
  const BK = combineRgb(229, 72, 77)
  presets.cue_lamp = {
    type: 'button', category: CUELIGHT, name: 'Cue lamp (green forward / red back) — tileable',
    style: { text: '', size: '14', color: WHITE, bgcolor: IDLE },
    steps: [{ down: [], up: [] }],
    feedbacks: [{ feedbackId: 'lamp_go', options: {}, style: { bgcolor: GO, color: WHITE } }, { feedbackId: 'lamp_back', options: {}, style: { bgcolor: BK, color: WHITE } }],
  }
  presets.cue_back = {
    type: 'button', category: CUELIGHT, name: 'BACK lamp (red on reverse only)',
    style: { text: '<', size: '30', color: WHITE, bgcolor: IDLE },
    steps: [{ down: [], up: [] }],
    feedbacks: [{ feedbackId: 'lamp_back', options: {}, style: { bgcolor: BK, color: WHITE } }],
  }
  presets.cue_go = {
    type: 'button', category: CUELIGHT, name: 'Forward lamp — flashes GREEN on a forward move',
    style: { text: '>', size: '30', color: WHITE, bgcolor: IDLE },
    steps: [{ down: [], up: [] }],
    feedbacks: [{ feedbackId: 'lamp_go', options: {}, style: { bgcolor: GO, color: WHITE } }],
  }

  // ---- KueSync Timer (show timer) ----
  const KUETIMER = 'KueSync · Timer'
  const ktFb = [{ feedbackId: 'kuetimer', options: {} }]
  // Live timer display button: big M:SS that colours itself green/amber/red. Tap = Start/Pause.
  presets.kuetimer_display = {
    type: 'button', category: KUETIMER, name: 'KueSync Timer — live display (tap = start/pause)',
    style: { text: v('kuetimer_time'), size: '24', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [{ actionId: 'kuetimer_toggle', options: {} }], up: [] }],
    feedbacks: ktFb,
  }
  presets.kuetimer_5 = button(KUETIMER, 'START\n5:00', 'kuetimer_start', { minutes: 5, seconds: 0, warn: 60, end: 'blink' }, ktFb)
  presets.kuetimer_10 = button(KUETIMER, 'START\n10:00', 'kuetimer_start', { minutes: 10, seconds: 0, warn: 120, end: 'blink' }, ktFb)
  presets.kuetimer_20 = button(KUETIMER, 'START\n20:00', 'kuetimer_start', { minutes: 20, seconds: 0, warn: 120, end: 'blink' }, ktFb)
  presets.kuetimer_toggle = button(KUETIMER, 'START /\nPAUSE', 'kuetimer_toggle', {}, ktFb)
  presets.kuetimer_plus = button(KUETIMER, '+1:00', 'kuetimer_adjust', { delta: 60 })
  presets.kuetimer_minus = button(KUETIMER, '-1:00', 'kuetimer_adjust', { delta: -60 })
  presets.kuetimer_reset = button(KUETIMER, 'RESET', 'kuetimer_reset', {})
  presets.kuetimer_zero = button(KUETIMER, 'TO\nZERO', 'kuetimer_zero', {})
  presets.kuetimer_countup = button(KUETIMER, 'COUNT\nUP', 'kuetimer_countup', {}, ktFb)
  presets.kuetimer_tod = button(KUETIMER, 'TIME\nOF DAY', 'kuetimer_tod', {}, ktFb)
  // 0.12.10 — at-zero colour + on-screen message (parity with KueSync Control).
  presets.kuetimer_zero_green = button(KUETIMER, 'AT ZERO\nHOLD\nGREEN', 'kuetimer_setend', { end: 'zero', zerocolor: 'green' }, ktFb)
  presets.kuetimer_message = {
    type: 'button', category: KUETIMER, name: 'KueSync Timer — on-screen message (shows text; tap = clear)',
    style: { text: v('kuetimer_message'), size: '14', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [{ actionId: 'kuetimer_message_clear', options: {} }], up: [] }],
    feedbacks: [{ feedbackId: 'kuetimer_message_active', options: {} }],
  }
  // 0.12.10 — at-a-glance status: the whole timer setup (limit · warn · at-zero · state) on one button, colour-coded.
  presets.kuetimer_status = {
    type: 'button', category: KUETIMER, name: 'KueSync Timer — STATUS (full setup + state, colour-coded)',
    style: { text: v('kuetimer_summary'), size: '14', color: WHITE, bgcolor: BLACK, alignment: 'center:center' },
    steps: [{ down: [], up: [] }], feedbacks: ktFb,
  }
  // Compact readouts, one fact per key — build a status strip on a stream deck.
  presets.kuetimer_r_mode = {
    type: 'button', category: KUETIMER, name: 'KueSync Timer — mode readout',
    style: { text: 'MODE\n' + v('kuetimer_mode_label'), size: '14', color: WHITE, bgcolor: DARK },
    steps: [{ down: [], up: [] }], feedbacks: [],
  }
  presets.kuetimer_r_limit = {
    type: 'button', category: KUETIMER, name: 'KueSync Timer — limit readout',
    style: { text: 'LIMIT\n' + v('kuetimer_limit'), size: '18', color: WHITE, bgcolor: DARK },
    steps: [{ down: [], up: [] }], feedbacks: [],
  }
  presets.kuetimer_r_warn = {
    type: 'button', category: KUETIMER, name: 'KueSync Timer — warn readout',
    style: { text: 'WARN\n' + v('kuetimer_warn'), size: '18', color: WHITE, bgcolor: DARK },
    steps: [{ down: [], up: [] }], feedbacks: [],
  }
  presets.kuetimer_r_atzero = {
    type: 'button', category: KUETIMER, name: 'KueSync Timer — at-zero readout',
    style: { text: 'AT ZERO\n' + v('kuetimer_atzero_label') + '\n' + v('kuetimer_zerocolor'), size: '14', color: WHITE, bgcolor: DARK },
    steps: [{ down: [], up: [] }], feedbacks: [],
  }
  // 0.12.11 — FULL configurability: one button per at-zero behaviour, one per colour, and warn on/off.
  presets.kuetimer_end_blink = button(KUETIMER, 'AT ZERO\nBLINK', 'kuetimer_setmode', { end: 'blink' }, ktFb)
  presets.kuetimer_end_hold  = button(KUETIMER, 'AT ZERO\nHOLD', 'kuetimer_setmode', { end: 'zero' }, ktFb)
  presets.kuetimer_end_blank = button(KUETIMER, 'AT ZERO\nBLANK', 'kuetimer_setmode', { end: 'blank' }, ktFb)
  presets.kuetimer_end_over  = button(KUETIMER, 'AT ZERO\nOVERTIME', 'kuetimer_setmode', { end: 'up' }, ktFb)
  presets.kuetimer_col_red   = button(KUETIMER, 'COLOUR\nRED',   'kuetimer_setcolor', { zerocolor: 'red' },   [], { bgcolor: combineRgb(120, 20, 20) })
  presets.kuetimer_col_amber = button(KUETIMER, 'COLOUR\nAMBER', 'kuetimer_setcolor', { zerocolor: 'amber' }, [], { bgcolor: combineRgb(120, 80, 0) })
  presets.kuetimer_col_white = button(KUETIMER, 'COLOUR\nWHITE', 'kuetimer_setcolor', { zerocolor: 'white' }, [], { bgcolor: combineRgb(70, 70, 70) })
  presets.kuetimer_col_green = button(KUETIMER, 'COLOUR\nGREEN', 'kuetimer_setcolor', { zerocolor: 'green' }, [], { bgcolor: combineRgb(20, 100, 40) })
  presets.kuetimer_col_blue  = button(KUETIMER, 'COLOUR\nBLUE',  'kuetimer_setcolor', { zerocolor: 'blue' },  [], { bgcolor: combineRgb(25, 60, 130) })
  presets.kuetimer_warn_on   = button(KUETIMER, 'WARN\n1:00', 'kuetimer_setwarn', { warn: 60 }, ktFb)
  presets.kuetimer_warn_off  = button(KUETIMER, 'WARN\nOFF', 'kuetimer_warn_off', {}, ktFb)
  // 0.12.49.1 — LOAD (arm a limit WITHOUT starting; the timer feedback shows it blue = LOADED), plus single
  // toggling COLOUR / AT-ZERO buttons that replace the button-per-colour / button-per-mode panels.
  presets.kuetimer_load = button(KUETIMER, 'LOAD\n5:00', 'kuetimer_arm', { minutes: 5, seconds: 0, warn: 60, end: 'blink', zerocolor: 'red' }, ktFb)
  presets.kuetimer_cycle_color = {
    type: 'button', category: KUETIMER, name: 'KueSync Timer — at-zero COLOUR (single toggling button)',
    style: { text: 'COLOUR\n' + v('kuetimer_zerocolor'), size: '14', color: WHITE, bgcolor: DARK },
    steps: [{ down: [{ actionId: 'kuetimer_cycle_color', options: {} }], up: [] }], feedbacks: [],
  }
  presets.kuetimer_cycle_mode = {
    type: 'button', category: KUETIMER, name: 'KueSync Timer — at-zero BEHAVIOUR (single toggling button)',
    style: { text: 'AT ZERO\n' + v('kuetimer_atzero_label'), size: '14', color: WHITE, bgcolor: DARK },
    steps: [{ down: [{ actionId: 'kuetimer_cycle_mode', options: {} }], up: [] }], feedbacks: ktFb,
  }

  // ---- KueSync Notes (source + scroll) ----
  const NOTES = 'KueSync · Notes'
  // Live source readout — which machine is feeding the fleet's notes (AUTO / Leader / name·IP).
  presets.notes_source_status = {
    type: 'button', category: NOTES, name: 'Notes source (live readout)',
    style: { text: 'NOTES SRC\n' + v('notes_source'), size: '14', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [],
  }
  // Backup readout + a lamp that lights when a failover source is configured.
  presets.notes_backup_status = {
    type: 'button', category: NOTES, name: 'Notes backup (live readout)',
    style: { text: 'BACKUP\n' + v('notes_backup'), size: '14', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [{ feedbackId: 'notes_backup_set', options: {} }],
  }
  // Editable examples — set the source/backup to a machine (or AUTO). A press applies it fleet-wide.
  presets.notes_source_auto = button(NOTES, 'NOTES\nAUTO', 'notes_set_source', { source: '' },
    [{ feedbackId: 'notes_is_source', options: { source: '' } }])
  presets.notes_source_set = button(NOTES, 'SET NOTES\nSOURCE#', 'notes_set_source', { source: '' })
  presets.notes_backup_set = button(NOTES, 'SET NOTES\nBACKUP#', 'notes_set_backup', { source: '' })
  // Scroll the notes screen from a Streamdeck (these had an action but no ready-made buttons).
  presets.notes_scroll_up = button(NOTES, 'NOTES\n▲', 'notes_scroll', { dir: 'up' })
  presets.notes_scroll_down = button(NOTES, 'NOTES\n▼', 'notes_scroll', { dir: 'down' })
  presets.notes_scroll_top = button(NOTES, 'NOTES\nTOP', 'notes_scroll', { dir: 'top' })
  presets.notes_scroll_bottom = button(NOTES, 'NOTES\nEND', 'notes_scroll', { dir: 'bottom' })

  // ---- KueSync Look (backgrounds / chroma / Kue-Light pips) ----
  const LOOK = 'KueSync · Look'
  // Background cycle buttons paint themselves the CURRENT background colour (the swatch feedback), so the key
  // is both the control and the indicator.
  presets.look_clock_bg = {
    type: 'button', category: LOOK, name: 'Timer/Clock background — cycle (shows current)',
    style: { text: 'CLOCK BG\n' + v('clock_bg'), size: '14', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [{ actionId: 'clock_bg_cycle', options: {} }], up: [] }],
    feedbacks: [{ feedbackId: 'clock_bg_swatch', options: {} }],
  }
  presets.look_notes_bg = {
    type: 'button', category: LOOK, name: 'Notes background — cycle (shows current)',
    style: { text: 'NOTES BG\n' + v('notes_bg'), size: '14', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [{ actionId: 'notes_bg_cycle', options: {} }], up: [] }],
    feedbacks: [{ feedbackId: 'notes_bg_swatch', options: {} }],
  }
  presets.look_clock_chroma = {
    type: 'button', category: LOOK, name: 'Timer/Clock chroma text — toggle white/black',
    style: { text: 'CLK TEXT\n' + v('clock_chroma_text'), size: '14', color: WHITE, bgcolor: DARK },
    steps: [{ down: [{ actionId: 'clock_chroma_toggle', options: {} }], up: [] }],
    feedbacks: [{ feedbackId: 'clock_chroma_black', options: {} }],
  }
  presets.look_notes_chroma = {
    type: 'button', category: LOOK, name: 'Notes chroma text — toggle white/black',
    style: { text: 'NT TEXT\n' + v('notes_chroma_text'), size: '14', color: WHITE, bgcolor: DARK },
    steps: [{ down: [{ actionId: 'notes_chroma_toggle', options: {} }], up: [] }],
    feedbacks: [{ feedbackId: 'notes_chroma_black', options: {} }],
  }
  presets.look_clock_pip = {
    type: 'button', category: LOOK, name: 'Kue-Light pip on the Timer — toggle (green = on)',
    style: { text: 'CLK PIP\n' + v('clock_pip'), size: '14', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [{ actionId: 'clock_pip_toggle', options: { mode: 'toggle' } }], up: [] }],
    feedbacks: [{ feedbackId: 'clock_pip_on', options: {} }],
  }
  presets.look_notes_pip = {
    type: 'button', category: LOOK, name: 'Kue-Light pips on Notes — toggle (green = on)',
    style: { text: 'NT PIPS\n' + v('notes_pip'), size: '14', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [{ actionId: 'notes_pip_toggle', options: { mode: 'toggle' } }], up: [] }],
    feedbacks: [{ feedbackId: 'notes_pip_on', options: {} }],
  }
  presets.look_notes_pip_pos = button(LOOK, 'NT PIP\nTOP-R', 'notes_pip_pos', { pos: 'tr' })

  // ── 0.12.49.2 TIMER — encoders, workflow, message + duration presets, rich display ───────────────────────
  // ENCODER (Stream Deck +): rotate to scrub the clock, press to start/pause. The native screen has no knob —
  // this beats it. Fine = ±10s; a second encoder does coarse ±1:00. Requires options.rotaryActions.
  presets.kuetimer_encoder = {
    type: 'button', category: KUETIMER, name: 'KueSync Timer — ENCODER: rotate = ±10s · press = start/pause',
    options: { rotaryActions: true },
    style: { text: v('kuetimer_time'), size: '18', color: WHITE, bgcolor: BLACK },
    steps: [{
      down: [{ actionId: 'kuetimer_toggle', options: {} }],
      up: [],
      rotate_left: [{ actionId: 'kuetimer_adjust', options: { delta: -10 } }],
      rotate_right: [{ actionId: 'kuetimer_adjust', options: { delta: 10 } }],
    }],
    feedbacks: ktFb,
  }
  presets.kuetimer_encoder_coarse = {
    type: 'button', category: KUETIMER, name: 'KueSync Timer — ENCODER (coarse): rotate = ±1:00 · press = start/pause',
    options: { rotaryActions: true },
    style: { text: v('kuetimer_time'), size: '18', color: WHITE, bgcolor: BLACK },
    steps: [{
      down: [{ actionId: 'kuetimer_toggle', options: {} }],
      up: [],
      rotate_left: [{ actionId: 'kuetimer_adjust', options: { delta: -60 } }],
      rotate_right: [{ actionId: 'kuetimer_adjust', options: { delta: 60 } }],
    }],
    feedbacks: ktFb,
  }
  // WORKFLOW: one key that walks LOAD → START → RESET on successive presses (Companion cycles the steps).
  presets.kuetimer_workflow = {
    type: 'button', category: KUETIMER, name: 'KueSync Timer — one key: LOAD → START → RESET',
    style: { text: 'LOAD', size: '18', color: WHITE, bgcolor: DARK },
    steps: [
      { down: [{ actionId: 'kuetimer_arm', options: { minutes: 5, seconds: 0, warn: 60, end: 'blink', zerocolor: 'red' } }], up: [] },
      { down: [{ actionId: 'kuetimer_resume', options: {} }], up: [] },
      { down: [{ actionId: 'kuetimer_reset', options: {} }], up: [] },
    ],
    feedbacks: ktFb,
  }
  // Rich live display: big clock + projected show-end + over/under, colour-coded (incl. LOADED = blue).
  presets.kuetimer_pro = {
    type: 'button', category: KUETIMER, name: 'KueSync Timer — PRO display (clock · end · over/under)',
    style: { text: v('kuetimer_time') + '\\nend ' + v('show_end_tod') + '\\n' + v('cue_delta'), size: '7', color: WHITE, bgcolor: BLACK, alignment: 'center:center' },
    steps: [{ down: [{ actionId: 'kuetimer_toggle', options: {} }], up: [] }],
    feedbacks: ktFb,
  }
  // Full quick-set durations to match the native preset row (5/10/20 already exist above).
  presets.kuetimer_1 = button(KUETIMER, 'START\n1:00', 'kuetimer_start', { minutes: 1, seconds: 0, warn: 30, end: 'blink' }, ktFb)
  presets.kuetimer_3 = button(KUETIMER, 'START\n3:00', 'kuetimer_start', { minutes: 3, seconds: 0, warn: 60, end: 'blink' }, ktFb)
  presets.kuetimer_15 = button(KUETIMER, 'START\n15:00', 'kuetimer_start', { minutes: 15, seconds: 0, warn: 120, end: 'blink' }, ktFb)
  presets.kuetimer_30 = button(KUETIMER, 'START\n30:00', 'kuetimer_start', { minutes: 30, seconds: 0, warn: 300, end: 'blink' }, ktFb)
  presets.kuetimer_45 = button(KUETIMER, 'START\n45:00', 'kuetimer_start', { minutes: 45, seconds: 0, warn: 300, end: 'blink' }, ktFb)
  presets.kuetimer_60 = button(KUETIMER, 'START\n60:00', 'kuetimer_start', { minutes: 60, seconds: 0, warn: 300, end: 'blink' }, ktFb)

  // One-touch on-screen messages to the presenter (the native "Send message" field, as buttons).
  const MSG = 'KueSync · Timer Messages'
  const msgBtn = (label, text) => button(MSG, label, 'kuetimer_message', { text }, [{ feedbackId: 'kuetimer_message_active', options: {} }])
  presets.msg_2min = msgBtn('2\nMINUTES', '2 minutes')
  presets.msg_1min = msgBtn('1\nMINUTE', '1 minute')
  presets.msg_wrap = msgBtn('WRAP\nUP', 'Please wrap up')
  presets.msg_finish = msgBtn('PLEASE\nFINISH', 'Please finish')
  presets.msg_qa = msgBtn('Q & A', 'Q & A')
  presets.msg_time = msgBtn('TIME', 'TIME')
  presets.msg_slow = msgBtn('SLOW\nDOWN', 'Slow down')
  presets.msg_speed = msgBtn('SPEED\nUP', 'Speed up')
  presets.msg_clear = button(MSG, 'CLEAR\nMSG', 'kuetimer_message_clear', {}, [], { bgcolor: RED })
  presets.msg_custom = button(MSG, 'MSG…', 'kuetimer_message', { text: '' })

  // ── 0.12.49.2 TIMER CUES — build + fire a named cue list ─────────────────────────────────────────────────
  const CUES = 'KueSync · Cues'
  // Live cue-list readout (▶ on air / ▷ armed), projected show end, total runtime.
  presets.cues_list = {
    type: 'button', category: CUES, name: 'Cue list (live, multi-line)',
    style: { text: v('cue_list'), size: '7', color: WHITE, bgcolor: BLACK, alignment: 'left:top' },
    steps: [{ down: [], up: [] }], feedbacks: [],
  }
  presets.cues_showend = {
    type: 'button', category: CUES, name: 'Projected show END time',
    style: { text: 'ENDS\n' + v('show_end_tod'), size: '14', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [],
  }
  presets.cues_total = {
    type: 'button', category: CUES, name: 'Cue list total runtime',
    style: { text: 'RUNTIME\n' + v('cues_total_time'), size: '14', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [],
  }
  // Transport by name + arm next/prev (name-aware picker; survives reorder).
  presets.cue_take_named = button(CUES, 'TAKE\nBY NAME', 'cue_take_named', { cue: (self.cueChoices()[0] || {}).id || '' }, [], { bgcolor: combineRgb(46, 36, 19), color: combineRgb(240, 207, 138) })
  presets.cue_arm_named = button(CUES, 'ARM\nBY NAME', 'cue_preview_named', { cue: (self.cueChoices()[0] || {}).id || '' })
  // Authoring buttons.
  presets.cue_add = button(CUES, '+ ADD\nCUE', 'cue_add', { name: 'New cue', minutes: 5, seconds: 0, warn: 60, end: 'blink', speaker: '', note: '' })
  presets.cue_load = button(CUES, 'LOAD\nSHEET', 'cue_load', { text: 'Opening 5:00\nKeynote 20:00\nQ & A 10:00', mode: 'replace' })
  presets.cue_clear = button(CUES, 'CLEAR\nCUES', 'cue_clear', {}, [], { bgcolor: RED })
  presets.cue_del = button(CUES, 'DELETE\nCUE', 'cue_del', { cue: (self.cueChoices()[0] || {}).id || '' })
  // Auto-populated: one TAKE button per cue, labelled by name; red when on air, green when armed.
  ;(self.cuesData && self.cuesData.cues ? self.cuesData.cues : []).forEach((c, i) => {
    const nm = c.name || ('#' + (i + 1))
    presets[`cue_take_${i + 1}`] = {
      type: 'button', category: CUES, name: `TAKE cue: ${nm}`,
      style: { text: `TAKE\n${String(nm).slice(0, 14)}`, size: '14', color: WHITE, bgcolor: combineRgb(28, 22, 12) },
      steps: [{ down: [{ actionId: 'cue_take_named', options: { cue: c.id || String(i + 1) } }], up: [] }],
      feedbacks: [
        { feedbackId: 'cue_on_air', options: { cue: i + 1 }, style: { bgcolor: combineRgb(150, 30, 30), color: WHITE } },
        { feedbackId: 'cue_armed', options: { cue: i + 1 }, style: { bgcolor: combineRgb(31, 82, 52), color: WHITE } },
      ],
    }
  })

  // ── 0.12.49.2 BACK-TIMING — hard out + warp ──────────────────────────────────────────────────────────────
  const BACKTIME = 'KueSync · Back-timing'
  presets.bt_hardout = button(BACKTIME, 'OUT AT\n2:00 PM', 'timer_hardout', { out: '2:00 PM', warn: 300, start: true }, [{ feedbackId: 'kuetimer', options: {} }])
  presets.bt_warp_tod = button(BACKTIME, 'WARP TO\n2:00 PM', 'timer_warp', { to: 'tod', out: '2:00 PM', minutes: 60, seconds: 0, keep: false }, [{ feedbackId: 'kuetimer', options: {} }])
  presets.bt_warp_keep = button(BACKTIME, 'WARP\nSEAMLESS', 'timer_warp', { to: 'tod', out: '2:00 PM', minutes: 60, seconds: 0, keep: true })
  presets.bt_out_tod = {
    type: 'button', category: BACKTIME, name: 'Actual out time-of-day',
    style: { text: 'OUT\n' + v('kuetimer_out_tod'), size: '14', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [],
  }
  presets.bt_real = {
    type: 'button', category: BACKTIME, name: 'REAL remaining (operator truth)',
    style: { text: 'REAL\n' + v('kuetimer_real_time'), size: '18', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: ktFb,
  }
  presets.bt_delta = {
    type: 'button', category: BACKTIME, name: 'Show over / under (on plan)',
    style: { text: 'PLAN\n' + v('cue_delta'), size: '18', color: WHITE, bgcolor: BLACK },
    steps: [{ down: [], up: [] }], feedbacks: [{ feedbackId: 'cue_running_behind', options: { secs: 60 } }],
  }

  return presets
}
