-- factoryos.programs.setup — first-boot wizard, run by startup.lua when
-- /factoryos/node.cfg is missing.
--
-- Two paths:
--   * press a key within `timeout` seconds -> interactive wizard writes
--     node.cfg and reboots into the chosen role (this is how you set up
--     the mainframe itself)
--   * timeout -> boot unclaimed: the node announces itself and can be
--     adopted/configured from a mainframe's NODES/CONFIGURE apps
--
-- Wizard is io-injected so it's testable on the host.

local util = require("factoryos.util")

local M = {}

M.ROLES = { "mainframe", "controller", "console", "navpad", "macro",
  "displaytile", "gateway" }

local function ask(io, prompt, dflt)
  io.write(prompt .. (dflt and (" [" .. dflt .. "]") or "") .. ": ")
  local s = io.read()
  if s == nil or s == "" then return dflt end
  return s
end

local function askRole(io, dflt)
  io.write("role:\n")
  for i, r in ipairs(M.ROLES) do io.write("  " .. i .. ") " .. r .. "\n") end
  while true do
    local s = ask(io, "pick 1-" .. #M.ROLES, tostring(dflt))
    local n = tonumber(s)
    if n and M.ROLES[n] then return M.ROLES[n] end
    io.write("invalid\n")
  end
end

local function genToken()
  return "tok-" .. (util.uuid():gsub("-", ""):sub(1, 12))
end

--- Interactive wizard. io = {write=fn, read=fn() -> line|nil}.
--- Returns the cfg table (not yet written).
function M.wizard(io)
  io.write("\n--- FactoryOS setup ---\n")
  local role = askRole(io, 2)
  local dfltName = role .. "-" .. tostring(os.getComputerID and
    os.getComputerID() or "0")
  local cfg = {
    role = role,
    name = ask(io, "node name", dfltName),
    facility = ask(io, "facility", "main"),
    adopted = true,
  }
  if peripheral and peripheral.find then
    local mon = peripheral.find("monitor")
    if mon and role == "console" then
      cfg.display = peripheral.getName(mon)
    end
  end
  io.write("cluster token — must match the mainframe's token.\n")
  cfg.token = ask(io, "token", genToken())
  cfg.token_admin = ask(io, "admin token", genToken())
  if role == "displaytile" then
    cfg.tile = ask(io, "tile id", "t1")
  end
  return cfg
end

--- Persist cfg the same way the agent does.
function M.writeCfg(cfg, path)
  path = path or "/factoryos/node.cfg"
  local h = fs.open(path, "w")
  if not h then return false, "open failed" end
  h.write("return " .. textutils.serialize(cfg))
  h.close()
  return true
end

--- Entry point. Returns true if cfg was written (caller should reboot).
--- io defaults to the real terminal; timeout defaults to 8s.
function M.main(opts)
  opts = opts or {}
  local io_ = opts.io or { write = write or function(s) io.write(s) end,
    read = read }

  io_.write("FactoryOS — no node.cfg found.\n")
  io_.write("Press a key within " .. (opts.timeout or 8) ..
    "s for interactive setup,\n")
  io_.write("or wait to boot UNCLAIMED (adoptable from a mainframe).\n")

  local tid = os.startTimer(opts.timeout or 8)
  while true do
    local ev, a = os.pullEvent()
    if ev == "timer" and a == tid then
      io_.write("unclaimed.\n")
      return false
    elseif ev == "key" or ev == "char" or ev == "monitor_touch"
      or ev == "mouse_click" then
      break
    end
  end

  local cfg = M.wizard(io_)
  local ok, err = M.writeCfg(cfg)
  if not ok then
    io_.write("could not write node.cfg: " .. tostring(err) .. "\n")
    io_.write("booting unclaimed instead.\n")
    return false
  end
  io_.write("wrote node.cfg — " .. cfg.role .. " '" .. cfg.name ..
    "' in '" .. cfg.facility .. "'\n")
  io_.write("token=" .. cfg.token .. "\n")
  io_.write("rebooting...\n")
  if opts.reboot ~= false then os.reboot() end
  return true
end

if ... == nil or select("#", ...) == 0 then
  -- run directly via dofile from startup.lua
  M.main()
end

return M
