-- factoryos.apps.config — remote node configuration from the desktop.
-- Reads a node's node.cfg (admin.node/get_cfg), edits fields, writes back
-- (admin.node/configure), reboots. Text fields need the mainframe
-- keyboard; nav-pad users can still pick roles/modules.

local KNOWN_ROLES   = { "controller", "console", "navpad", "macro",
  "displaytile", "mainframe", "gateway" }
local KNOWN_MODULES = { "power", "rswitch" }

return {
  id = "config", name = "CONFIGURE",
  desc = "Remote node configuration",

  create = function(ctx)
    local W     = require("factoryos.ui.widgets")
    local sched = require("factoryos.core.sched")
    local util  = require("factoryos.util")
    local node, wm = ctx.node, ctx.wm
    local C = W.colors
    local admin = function() return node.cfg.token_admin or node.cfg.token end

    local list = W.ListBox({ x = 2, y = 3, w = 54, h = 12 })
    local status = W.Label({ x = 2, y = 16, w = 54, text = "", color = C.dim })

    local function refresh()
      node:spawn(function()
        local ok, res = node:call("core.registry", "query")
        if not ok then status.text = "registry unreachable"; wm:draw(); return end
        local items = {}
        for _, id in ipairs(util.keys(res.nodes or {})) do
          local n = res.nodes[id]
          items[#items + 1] = {
            text = string.format("%-14s %-12s %-9s %s", n.name or "?",
              n.role or "?", n.facility or "?",
              n.adopted and (n.state or "?") or "UNCLAIMED"),
            data = n }
        end
        list.items = items
        wm:draw()
      end, "cfg.refresh")
    end

    -- "a=left; cab2=redstoneIntegrator_0:north" → outputs table
    local function parseOutputs(s)
      local out = {}
      for pair in tostring(s or ""):gmatch("[^;]+") do
        local k, v = pair:match("^%s*(.-)%s*=%s*(.-)%s*$")
        if k and v and #k > 0 then
          local pn, ps = v:match("^([%w_%.]+):([%a]+)$")
          out[k] = pn and { p = pn, side = ps } or v
        end
      end
      return out
    end

    local function serializeOutputs(t)
      local parts = {}
      for k, v in pairs(t or {}) do
        parts[#parts + 1] = type(v) == "table"
          and (k .. "=" .. v.p .. ":" .. v.side) or (k .. "=" .. v)
      end
      table.sort(parts)
      return table.concat(parts, "; ")
    end

    local function editor(n)
      local w = math.min(wm.cv.w - 6, 58)
      local st = W.Label({ x = 2, y = 14, w = w - 3, text = "loading cfg...",
        color = C.dim })
      local nameIn = W.TextInput({ x = 10, y = 3, w = w - 12,
        text = n.name or "" })
      local facIn  = W.TextInput({ x = 10, y = 4, w = w - 12,
        text = n.facility or "" })
      local outIn  = W.TextInput({ x = 10, y = 5, w = w - 12, text = "" })
      local roleLb = W.ListBox({ x = 10, y = 7, w = 18, h = 6, items = {} })
      local modLb  = W.ListBox({ x = 32, y = 7, w = 20, h = 6, items = {} })
      local selRole, selMods = n.role or "controller", {}

      local function fillMods(current)
        local items = {}
        for _, m in ipairs(KNOWN_MODULES) do
          selMods[m] = selMods[m] or false
          for _, cm in ipairs(current or {}) do
            if cm == m then selMods[m] = true end
          end
          items[#items + 1] = { text = (selMods[m] and "[x] " or "[ ] ") .. m,
            data = m }
        end
        modLb.items = items
      end
      fillMods(nil)
      modLb.onActivate = function(item)
        selMods[item.data] = not selMods[item.data]
        fillMods(nil)
        wm:draw()
      end
      local ri = {}
      for i, r in ipairs(KNOWN_ROLES) do
        ri[#ri + 1] = { text = r }
        if r == selRole then roleLb.sel = i end
      end
      roleLb.items = ri
      roleLb.onActivate = function(item) selRole = item.text end

      local win
      local function save()
        local mods = {}
        for _, m in ipairs(KNOWN_MODULES) do
          if selMods[m] then mods[#mods + 1] = m end
        end
        st.text = "writing cfg..."
        wm:draw()
        node:spawn(function()
          local set = {
            name = nameIn.text, facility = facIn.text, role = selRole,
            modules = mods, adopted = true,
            module_cfg = { rswitch = { outputs = parseOutputs(outIn.text) } },
          }
          local ok, res = node:call("admin.node", "configure",
            { set = set }, { to = n.id, timeout = 3, auth = admin() })
          if not ok then
            st.text = "save failed: " .. tostring(res and res.err or res)
            wm:draw(); return
          end
          st.text = "saved. rebooting..."
          wm:draw()
          node:call("admin.node", "reboot", nil,
            { to = n.id, timeout = 2, auth = admin() })
          wm:close(win); refresh()
        end, "cfg.save")
      end

      win = wm:open({
        title = "CONFIGURE " .. (n.name or n.id), modal = true,
        w = w, h = 17,
        x = math.floor((wm.cv.w - w) / 2), y = math.floor((wm.cv.h - 17) / 2),
        widgets = {
          W.Label({ x = 2, y = 1, w = w - 3, text = n.id, color = C.dim }),
          W.Label({ x = 2, y = 3, w = 7, text = "NAME", color = C.dim }),
          W.Label({ x = 2, y = 4, w = 7, text = "FACILITY", color = C.dim }),
          W.Label({ x = 2, y = 5, w = 7, text = "OUTPUTS", color = C.dim }),
          nameIn, facIn, outIn,
          W.Label({ x = 10, y = 6, w = 10, text = "ROLE", color = C.dim }),
          W.Label({ x = 32, y = 6, w = 10, text = "MODULES", color = C.dim }),
          roleLb, modLb, st,
          W.Button({ x = w - 20, y = 15, text = "SAVE+REBOOT",
            onActivate = save }),
        },
      })

      -- pull the node's current cfg to prefill
      node:spawn(function()
        local ok, res = node:call("admin.node", "get_cfg", nil,
          { to = n.id, timeout = 3, auth = admin() })
        if ok and res.cfg then
          nameIn.text = res.cfg.name or nameIn.text
          facIn.text = res.cfg.facility or facIn.text
          local mc = res.cfg.module_cfg or {}
          outIn.text = serializeOutputs(mc.rswitch and mc.rswitch.outputs)
          for _, m in ipairs(res.cfg.modules or {}) do selMods[m] = true end
          fillMods(nil)
          st.text = ""
        else
          st.text = "cfg read failed: " .. tostring(res and res.err or res)
        end
        wm:draw()
      end, "cfg.load")
    end

    list.onActivate = function(item) if item.data then editor(item.data) end end

    node:spawn(function()
      while true do sched.sleep(4); refresh() end
    end, "cfg.poll")

    refresh()
    return {
      title = "CONFIGURE", x = 3, y = 2, w = 58, h = 18,
      widgets = {
        W.Label({ x = 2, y = 2, w = 54, color = C.dim,
          text = "NAME           ROLE         FACILITY  STATE" }),
        list, status,
        W.Label({ x = 2, y = 17, w = 54, color = C.dim,
          text = "outputs syntax: name=side; name=integName:side" }),
      },
    }
  end,
}
