-- factoryos.apps.nodes — network topology / node inspector.
return {
  id = "nodes", name = "NODES",
  desc = "Registered nodes & services",

  create = function(ctx)
    local W = require("factoryos.ui.widgets")
    local sched = require("factoryos.core.sched")
    local node, wm = ctx.node, ctx.wm
    local list

    local function snapshot()
      local ok, res = node:call("core.registry", "query")
      return ok and res.nodes or {}
    end

    local function refresh()
      local nodes = snapshot()
      local items = {}
      local order = require("factoryos.util").keys(nodes)
      for _, id in ipairs(order) do
        local n = nodes[id]
        items[#items + 1] = {
          text = string.format("%-12s %-10s %-8s %s",
            n.name or "?", n.role or "?", n.facility or "?", n.state or "?"),
          data = n,
        }
      end
      if #items == 0 then items[1] = { text = "no nodes registered" } end
      list.items = items
      if list.sel > #items then list.sel = math.max(1, #items) end
    end

    -- "switch" is a convenience pseudo-role: controller + rswitch module
    local ROLES = { "controller", "switch", "console", "navpad", "macro",
      "displaytile", "mainframe", "gateway" }
    local PSEUDO = { switch = { role = "controller", modules = { "rswitch" } } }

    local function adoptPicker(n)
      local pw = 30
      local status = W.Label({ x = 2, y = 7, w = pw - 3, text = "" })
      local picker
      local roleList = W.ListBox({ x = 2, y = 2, w = pw - 4, h = 5,
        items = (function()
          local items = {}
          for _, r in ipairs(ROLES) do items[#items + 1] = { text = r } end
          return items
        end)() })
      picker = wm:open({
        title = "ADOPT " .. (n.name or n.id), modal = true,
        w = pw, h = 10,
        x = math.floor((wm.cv.w - pw) / 2),
        y = math.floor((wm.cv.h - 10) / 2),
        widgets = {
          W.Label({ x = 2, y = 1, w = pw - 3, text = "assign role:",
            color = W.colors.dim }),
          roleList,
          status,
          W.Button({ x = 2, y = 8, text = "ADOPT + REBOOT",
            onActivate = function()
              local role = roleList.items[roleList.sel]
              if not role then return end
              status.text = "claiming..."
              node:spawn(function()
                local sel = PSEUDO[role.text]
                local name = role.text .. "-" .. tostring(n.id):sub(1, 6)
                local ok, res = node:call("admin.node", "adopt", {
                  name = name, role = sel and sel.role or role.text,
                  facility = n.facility,
                  modules = sel and sel.modules or nil,
                  token = node.cfg.token, token_admin = node.cfg.token_admin,
                }, { to = n.id, timeout = 4,
                  auth = node.cfg.token_admin or node.cfg.token })
                if not ok then
                  status.text = "FAIL: " .. tostring(res and res.err or res)
                  wm:draw()
                  return
                end
                status.text = "adopted; rebooting..."
                wm:draw()
                node:call("admin.node", "reboot", nil,
                  { to = n.id, timeout = 2, auth = node.cfg.token_admin })
                wm:close(picker)
                refresh(); wm:draw()
              end, "adopt")
            end }),
        },
      })
    end

    local function detail(n)
      if not n then return end
      local w = math.min(wm.cv.w - 6, 56)
      local status = W.Label({ x = 2, y = 6, w = w - 3, text = "",
        color = W.colors.warn })
      local widgets = {
        W.Label({ x = 2, y = 2, w = w - 3,
          text = string.format("id: %s", n.id) }),
        W.Label({ x = 2, y = 3, w = w - 3,
          text = string.format("role: %s   facility: %s   state: %s   %s",
            n.role or "?", n.facility or "?", n.state or "?",
            n.adopted and "adopted" or "UNCLAIMED") }),
        W.Label({ x = 2, y = 4, w = w - 3,
          text = "services: " .. table.concat(n.services or {}, ", ") }),
        status,
      }
      local win
      if not n.adopted then
        widgets[#widgets + 1] = W.Button({ x = 2, y = 7, text = "ADOPT",
          onActivate = function() wm:close(win); adoptPicker(n) end })
      end
      widgets[#widgets + 1] = W.Button({ x = 12, y = 7, text = "PING",
        onActivate = function()
          node:spawn(function()
            local ok, res = node:call("admin.node", "info", nil,
              { to = n.id, timeout = 2 })
            status.text = ok and string.format("up %ss cid=%s",
              string.format("%.1f", res.uptime or 0), tostring(res.cid))
              or ("no answer: " .. tostring(res))
            wm:draw()
          end, "ping")
        end })
      win = wm:open({
        title = "NODE: " .. (n.name or "?"), modal = true,
        w = w, h = 10,
        x = math.floor((wm.cv.w - w) / 2), y = math.floor((wm.cv.h - 10) / 2),
        widgets = widgets,
      })
    end

    list = W.ListBox({ x = 2, y = 3, w = 54, h = 12,
      onActivate = function(item) detail(item.data) end })

    node:spawn(function()
      while true do sched.sleep(3); refresh(); wm:draw() end
    end, "nodes.refresh")

    refresh()
    return {
      title = "NETWORK", x = 3, y = 2, w = 58, h = 16,
      widgets = {
        W.Label({ x = 2, y = 2, w = 54,
          text = "NAME         ROLE       FACILITY STATE", color = W.colors.dim }),
        list,
      },
    }
  end,
}
