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

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

const WHITE = combineRgb(255, 255, 255)
const GREEN = combineRgb(0, 150, 0)
const RED = combineRgb(180, 0, 0)
const AMBER = combineRgb(180, 120, 0)
const BLUE = combineRgb(0, 80, 200)

const primaryState = (self) => (self.primary && self.primary() ? self.primary().state : { slide: 0, playing: false })

export function getFeedbacks(self) {
  return {
    // 0.9.6: one feedback that paints a follower button green when Active, red when Inactive (parked),
    // by machine number. Pair it with the Activate / Deactivate / Toggle actions for a button that both
    // controls AND shows state at a glance. Unknown / not-yet-reporting machine → left unstyled.
    follower_active: {
      type: 'advanced',
      name: 'Follower Active (green) / Inactive (red) — pick the follower',
      options: [
        { type: 'dropdown', id: 'machine', label: 'Follower (by name · IP)', default: (self.followerChoices ? (self.followerChoices()[0] || {}).id : '1') || '1', choices: self.followerChoices ? self.followerChoices() : [{ id: '1', label: 'Machine 1' }], allowCustom: true, tooltip: 'Pick the machine by its name (or type a machine number / IP).' },
        { type: 'colorpicker', id: 'activeColor', label: 'Active background', default: GREEN },
        { type: 'colorpicker', id: 'inactiveColor', label: 'Inactive background', default: RED },
      ],
      callback: (fb) => {
        const t = self.targetFor ? self.targetFor(String(fb.options.machine || '1').trim()) : null
        if (!t) return {}
        const active = t.state ? t.state.followerActive !== false : true
        return { bgcolor: active ? (fb.options.activeColor ?? GREEN) : (fb.options.inactiveColor ?? RED), color: WHITE }
      },
    },
    connected: {
      type: 'boolean', name: 'At least one machine connected',
      defaultStyle: { bgcolor: GREEN, color: WHITE }, options: [],
      callback: () => (self.connectedCount ? self.connectedCount() > 0 : false),
    },
    all_connected: {
      type: 'boolean', name: 'All machines connected',
      defaultStyle: { bgcolor: GREEN, color: WHITE }, options: [],
      callback: () => self.targets.length > 0 && self.connectedCount() === self.targets.length,
    },
    all_healthy: {
      // Green only when every configured machine is HEALTHY (reporting, not just socket-open). This
      // is the one to trust for redundancy — a wedged/frozen backup with an open socket is NOT green.
      type: 'boolean', name: 'All machines HEALTHY (reporting, not stale)',
      defaultStyle: { bgcolor: GREEN, color: WHITE }, options: [],
      callback: () => self.targets.length > 0 && self.healthyCount() === self.targets.length,
    },
    playing: {
      type: 'boolean', name: 'Slideshow playing (primary)',
      defaultStyle: { bgcolor: BLUE, color: WHITE }, options: [],
      callback: () => primaryState(self).playing === true,
    },
    on_slide: {
      type: 'boolean', name: 'On a specific slide (primary)',
      defaultStyle: { bgcolor: BLUE, color: WHITE },
      options: [{ type: 'number', id: 'slide', label: 'Slide number', default: 1, min: 1, max: 9999 }],
      callback: (fb) => primaryState(self).slide === Number(fb.options.slide),
    },
    cue_running: {
      type: 'boolean', name: 'Cue running',
      defaultStyle: { bgcolor: GREEN, color: WHITE }, options: [],
      callback: () => self.cue.running && !self.cue.paused,
    },
    fleet_in_sync: {
      // Goes RED when any connected machine is on a different slide than the rest — your real-time
      // confidence indicator for the redundant fleet (esp. in relative/+builds mode).
      type: 'boolean', name: 'Fleet OUT of sync (any machine on a different slide)',
      defaultStyle: { bgcolor: RED, color: WHITE }, options: [],
      callback: () => {
        const p = self.primary && self.primary()
        if (!(p && p.state && p.state.playing)) return false   // no show running → 'paused', not drift
        return self.fleetSync ? !self.fleetSync().inSync : false
      },
    },
    leader_known: {
      // GREEN when a healthy Leader is driving the fleet; AMBER when a Leader is reported but stale
      // (mid-failover); default (no colour) when there is no Leader (Companion-only / pre-v6 agents).
      type: 'advanced', name: 'Current Leader colour (healthy=green, stale=amber, none=neutral)', options: [],
      callback: () => {
        const ld = self.currentLeader ? self.currentLeader() : { present: false, healthy: false }
        if (ld.present && ld.healthy) return { bgcolor: GREEN, color: WHITE }
        if (ld.present) return { bgcolor: AMBER, color: WHITE }
        return {}
      },
    },
    media_ending: {
      // Turns the button amber, then red, as a playing video nears its end — so the operator sees the
      // wrap coming. Only fires while a video clock is actually running (not when armed/idle).
      type: 'advanced', name: 'Video ending soon (amber, then red in the final seconds)',
      options: [
        { type: 'number', id: 'amberAt', label: 'Amber at (seconds remaining)', default: 30, min: 1, max: 3600 },
        { type: 'number', id: 'redAt', label: 'Red at (seconds remaining)', default: 10, min: 1, max: 3600 },
      ],
      callback: (fb) => {
        if (!(self.videoActive && self.videoActive())) return {}
        const left = self.videoRemainingSecs ? self.videoRemainingSecs() : 999999
        const red = Number(fb.options.redAt), amber = Number(fb.options.amberAt)
        if (left <= red) return { bgcolor: RED, color: WHITE }
        if (left <= amber) return { bgcolor: AMBER, color: WHITE }
        return {}
      },
    },
    cue_light: {
      // hardware-style lamp: GREEN flashes on a forward move, RED on a backward one, mirroring
      // the presenter's clicker. Output only. Put it on one key, or tile it across many keys to build a
      // big cue light. "Go only" / "Back only" build a two-zone panel (green half, red half).
      type: 'advanced', name: 'Cue light (cue lamp — green forward / red back)',
      options: [
        { type: 'dropdown', id: 'lamp', label: 'Lamp', default: 'active', choices: [
          { id: 'active', label: 'Active — green on forward, red on back' },
          { id: 'go', label: 'Go lamp — green on forward only' },
          { id: 'back', label: 'Back lamp — red on back only' },
        ] },
        { type: 'colorpicker', id: 'goColor', label: 'Go colour (forward)', default: combineRgb(47, 180, 107) },
        { type: 'colorpicker', id: 'backColor', label: 'Back colour (reverse)', default: combineRgb(229, 72, 77) },
        { type: 'colorpicker', id: 'idleColor', label: 'Idle colour (between cues)', default: combineRgb(14, 20, 25) },
        { type: 'colorpicker', id: 'textColor', label: 'Text colour', default: WHITE },
      ],
      // 0.10.19: evaluate the moment a button gets this feedback (add OR config load) so the lamp lights
      // immediately — no more "remove + re-add to make it work". Deferred so the instance is registered first.
      subscribe: () => { try { setTimeout(() => self.checkFeedbacks('cue_light'), 0) } catch {} },
      callback: (fb) => {
        const st = self.cueState ? self.cueState() : 'idle'
        const lamp = fb.options.lamp || 'active'
        // Resolve EVERY colour with a ?? fallback (matches the kuetimer feedback). A button added under an
        // older module — before these colour options existed — has fb.options.*Color === undefined; reading
        // them raw returned { bgcolor: undefined } and the lamp painted NOTHING, so it looked dead until a
        // manual re-add seeded the new defaults. With fallbacks the lamp lights correctly with no re-add.
        const goC = fb.options.goColor ?? combineRgb(47, 180, 107)
        const backC = fb.options.backColor ?? combineRgb(229, 72, 77)
        const idleC = fb.options.idleColor ?? combineRgb(14, 20, 25)
        let bg = idleC
        if ((lamp === 'active' || lamp === 'go') && st === 'go') bg = goC
        else if ((lamp === 'active' || lamp === 'back') && st === 'back') bg = backC
        return { bgcolor: bg, color: fb.options.textColor ?? WHITE }
      },
    },
    // 0.12.1 CUE LAMP (BOOLEAN). The advanced `cue_light` above is provably correct but Companion 4.3.4 won't
    // re-render a PLACED advanced feedback after an app/connection reload (the lamp went dead until a manual
    // re-add). These BOOLEAN feedbacks are rendered by CORE Companion (it owns the style), which survives a
    // reload. Stack BOTH on one key for a green-forward / red-back lamp, or use one alone for a single-colour
    // zone. The colour is the feedback's editable style, so an operator can recolour without touching the module.
    lamp_go: {
      type: 'boolean', name: 'Cue lamp — GREEN on a forward move',
      description: 'Turns the key green when the fleet advances (flash or latch per the connection’s Cue light mode). Stack with the RED lamp to build a two-colour cue light.',
      defaultStyle: { bgcolor: combineRgb(47, 180, 107), color: WHITE },
      options: [],
      callback: () => (self.cueState ? self.cueState() === 'go' : false),
    },
    lamp_back: {
      type: 'boolean', name: 'Cue lamp — RED on a back move',
      description: 'Turns the key red when the fleet goes back.',
      defaultStyle: { bgcolor: combineRgb(229, 72, 77), color: WHITE },
      options: [],
      callback: () => (self.cueState ? self.cueState() === 'back' : false),
    },
    kuetimer: {
      // KueSync Timer button colour: green while running, amber in the warning window, red at/past zero (overtime).
      // Pair with the $(kuetimer:kuetimer_time) variable for a live timer button on a stream deck.
      type: 'advanced', name: 'KueSync Timer status colour (running=green, warning=amber, overtime=red)',
      options: [
        { type: 'colorpicker', id: 'runColor', label: 'Running', default: GREEN },
        { type: 'colorpicker', id: 'warnColor', label: 'Warning', default: AMBER },
        { type: 'colorpicker', id: 'overColor', label: 'Overtime / time up', default: RED },
        { type: 'colorpicker', id: 'textColor', label: 'Text colour', default: WHITE },
      ],
      callback: (fb) => {
        const k = self.kuetimer ? self.kuetimer() : { state: 'idle' }
        const txt = fb.options.textColor ?? WHITE
        if (k.state === 'overtime') return { bgcolor: fb.options.overColor ?? RED, color: txt }
        if (k.state === 'warning') return { bgcolor: fb.options.warnColor ?? AMBER, color: txt }
        if (k.state === 'running' || k.state === 'countup' || k.state === 'tod') return { bgcolor: fb.options.runColor ?? GREEN, color: txt }
        return {}
      },
    },
    license_state: {
      type: 'advanced', name: 'License status colour (trial=amber, expired=red)', options: [],
      callback: () => {
        const st = (self.fleetEntitlement ? self.fleetEntitlement() : self.license.evaluate()).state
        if (st === 'expired') return { bgcolor: RED, color: WHITE }
        if (st === 'trial') return { bgcolor: AMBER, color: WHITE }
        return {}
      },
    },
    // Fleet version awareness — a mixed-version fleet is a real sync risk; surface it (and any
    // published update) BEFORE the show. Amber = agents disagree; blue = a newer KueSync exists.
    fleet_version: {
      type: 'advanced', name: 'Fleet version status (amber = mixed versions, blue = update available)', options: [],
      callback: () => {
        const ts = self.targets || []
        const vers = new Set(ts.map((t) => t && t.state && t.state.agentVersion).filter(Boolean))
        const upd = ts.map((t) => t && t.state && t.state.updateVersion).find((v) => v)
        if (vers.size > 1) return { bgcolor: AMBER, color: WHITE }
        if (upd) return { bgcolor: combineRgb(61, 139, 255), color: WHITE }
        return {}
      },
    },
  }
}
