-- factoryos.ui.desktop — launcher layer: status bar, app grid, status line.
-- The desktop is a borderless bottom window; nav/focus behave uniformly.

local W    = require("factoryos.ui.widgets")
local util = require("factoryos.util")
local C    = W.colors

local Desktop = {}
Desktop.__index = Desktop

function Desktop.new(wm, node, opts)
  local self = setmetatable({}, Desktop)
  self.wm, self.node = wm, node
  self.opts = opts or {}
  self.apps = {}
  self.win = nil
  return self
end

function Desktop:registerApp(app)
  -- app = {id, name, desc, create(ctx) -> window spec}
  self.apps[app.id] = app
end

function Desktop:_buildWidgets()
  local cvw, cvh = self.wm.cv.w, self.wm.cv.h
  local widgets = {}
  self.clockW = W.Label({ x = cvw - 7, y = 1, w = 8, text = "--:--", color = C.dim })
  self.alertW = W.Label({ x = 26, y = 1, w = 20, text = "", color = C.warn })
  widgets[#widgets + 1] = W.Label({ x = 2, y = 1, w = 24, text = "FACTORYOS | OPERATIONS" })
  widgets[#widgets + 1] = self.alertW
  widgets[#widgets + 1] = self.clockW
  widgets[#widgets + 1] = W.Sep({ x = 1, y = 2, w = cvw })

  -- app grid
  local colw, rowh, x0, y0 = 16, 4, 3, 4
  local i = 0
  for _, id in ipairs(util.keys(self.apps)) do
    local app = self.apps[id]
    local col, row = i % math.max(1, math.floor((cvw - 4) / colw)),
      math.floor(i / math.max(1, math.floor((cvw - 4) / colw)))
    widgets[#widgets + 1] = W.Button({
      x = x0 + col * colw, y = y0 + row * rowh, w = 14,
      text = app.name,
      onActivate = function() self:openApp(id) end,
    })
    i = i + 1
  end

  self.statusW = W.Label({ x = 2, y = cvh, w = cvw - 4, text = "", color = C.dim })
  widgets[#widgets + 1] = W.Sep({ x = 1, y = cvh - 1, w = cvw })
  widgets[#widgets + 1] = self.statusW
  return widgets
end

function Desktop:open()
  self.win = self.wm:open({
    id = "desktop", desktop = true, border = false,
    x = 1, y = 1, w = self.wm.cv.w, h = self.wm.cv.h,
    widgets = self:_buildWidgets(),
    onBack = function() return true end, -- desktop swallows back
  })
  return self.win
end

function Desktop:openApp(id)
  local app = self.apps[id]
  if not app then return false, "no_such_app" end
  if app._win then self.wm:focusWindow(app._win); return true end
  local ctx = { node = self.node, wm = self.wm, desktop = self }
  local spec = app.create(ctx)
  spec.app, spec.id = app, "app:" .. id
  local win = self.wm:open(spec)
  app._win = win
  local oldBack = spec.onBack
  win.onBack = function(w)
    if oldBack then oldBack(w) end
    app._win = nil
    self.wm:close(w)
    return true
  end
  return true
end

function Desktop:goHome()
  if self.win then self.wm:focusWindow(self.win) end
  return true
end

local function fmtClock()
  if os.epoch and textutils.formatTime then
    return textutils.formatTime((os.epoch("local") / 1000 % 86400) / 3600, false)
  end
  return string.format("%06.1f", os.clock())
end

--- Periodic refresh of clock/status; cheap — canvas diffing does the rest.
function Desktop:tick()
  if not self.win then return end
  self.clockW.text = fmtClock()
  local online, total = 0, 0
  for _, p in pairs(self.node.peers) do
    total = total + 1
    local rec = self.registryNodes and self.registryNodes[p.id]
    if not rec or rec.state == "online" then online = online + 1 end
  end
  self.statusW.text = string.format(
    "NET %s  |  NODES %d/%d  |  <^v> move  ENTER select  BACK close",
    self.node.modem and "UP" or "DOWN", online, total)
end

return Desktop
