-- RevueShow grandMA3 current cue sync prototype
--
-- Purpose:
--   Poll the selected sequence's current cue and send it to RevueShow whenever
--   it changes. This catches Goto, macro, executor, and manual state changes
--   that may not appear as normal Go+/Go- command feedback.
--
-- Setup:
--   1. In grandMA3, configure OSC line OSC_LINE below to send to the Mac
--      running RevueShow on the RevueShow OSC Input port.
--   2. Enable OSC Output and Send Command for that line.
--   3. Import this code into a grandMA3 plugin and run it once to start.
--      Run it again to stop the polling loop.
--
-- RevueShow listens for:
--   /revueshow/ma3/current_cue,ifs,<sequence>,<cue>,<label>
--
-- Notes:
--   This uses documented grandMA3 Lua/command-line pieces: GetCurrentCue(),
--   SelectedSequence(), object Get(), CmdIndirect(), coroutine.yield(), and
--   the SendOSC keyword. Verify property names on the target MA3 software
--   build with GetCurrentCue():Dump() before using this in show conditions.

return function()
    local OSC_LINE = 1
    local POLL_SECONDS = 0.25

    if RevueShowCurrentCueRunning then
        RevueShowCurrentCueRunning = false
        Printf("RevueShow current cue sync stopping")
        return
    end

    RevueShowCurrentCueRunning = true
    Printf("RevueShow current cue sync started")

    local function prop(handle, key)
        if handle == nil then return nil end
        local ok, value = pcall(function()
            if handle.Get ~= nil then return handle:Get(key) end
            return nil
        end)
        if ok and value ~= nil and tostring(value) ~= "" then
            return tostring(value)
        end
        return nil
    end

    local function object_name(handle)
        if handle == nil then return "" end
        if handle.name ~= nil then return tostring(handle.name) end
        return prop(handle, "Name") or prop(handle, "name") or ""
    end

    local function number_from_handle(handle, fallback)
        local raw = prop(handle, "No") or prop(handle, "Number") or prop(handle, "no")
        if raw ~= nil then
            local n = tonumber(raw)
            if n ~= nil then return n end
        end
        if handle ~= nil and handle.Addr ~= nil then
            local ok, addr = pcall(function() return handle:Addr() end)
            if ok and addr ~= nil then
                local n = tostring(addr):match("(%d+%.?%d*)$")
                if n ~= nil then return tonumber(n) or fallback end
            end
        end
        return fallback
    end

    local function clean_osc_string(value)
        value = tostring(value or "")
        value = value:gsub(",", " ")
        value = value:gsub('"', "'")
        value = value:gsub("[%c]", " ")
        return value
    end

    local function selected_sequence()
        if SelectedSequence == nil then return nil end
        local ok, seq = pcall(function() return SelectedSequence() end)
        if ok then return seq end
        return nil
    end

    local last_key = ""

    while RevueShowCurrentCueRunning do
        local cue = GetCurrentCue()
        if cue ~= nil then
            local seq = selected_sequence()
            local seq_no = number_from_handle(seq, 1)
            local cue_no = number_from_handle(cue, nil)
            local label = object_name(cue)

            if cue_no ~= nil then
                local key = tostring(seq_no) .. ":" .. tostring(cue_no) .. ":" .. label
                if key ~= last_key then
                    last_key = key
                    local packet = string.format(
                        "/revueshow/ma3/current_cue,ifs,%d,%.3f,%s",
                        math.floor(seq_no),
                        cue_no,
                        clean_osc_string(label)
                    )
                    CmdIndirect(string.format('SendOSC %d "%s"', OSC_LINE, packet))
                end
            end
        end

        coroutine.yield(POLL_SECONDS)
    end

    Printf("RevueShow current cue sync stopped")
end
