-- factoryos.modhost — loads module manifests onto a node: service
-- registration, telemetry scheduling, failsafe link supervision.

local sched = require("factoryos.core.sched")
local log   = require("factoryos.core.log")
local util  = require("factoryos.util")

local M = {}

--- Load cfg.modules onto node. Returns {name -> manifest}.
function M.load(node, cfg)
  local loaded = {}

  -- backbone link supervision → module failsafe hooks
  local linkUp = true
  local linkTimeout = cfg.linkTimeout or node.hbInterval * 6
  local function setLink(up)
    if linkUp == up then return end
    linkUp = up
    log.warn("backbone %s", up and "restored" or "lost")
    for _, m in pairs(loaded) do
      local hook = up and m.onLinkUp or m.onLinkLost
      if hook then pcall(hook, m.ctx) end
    end
  end
  node:spawn(function()
    while true do
      sched.sleep(5)
      if node.lastRx and (util.clock() - node.lastRx) > linkTimeout then
        setLink(false)
      elseif node.lastRx then
        setLink(true)
      end
    end
  end, "link.watch")

  for _, mname in ipairs(cfg.modules or {}) do
    local okm, manifest = pcall(require, "factoryos.modules." .. mname)
    if not okm then
      log.error("module %s load failed: %s", mname, manifest)
    else
      local ctx = {
        node = node, log = log,
        cfg = (cfg.module_cfg or {})[mname] or {},
        state = {},
      }
      local oki, ierr = pcall(manifest.init or function() end, ctx)
      if not oki then
        log.error("module %s init failed: %s", mname, ierr)
      else
        manifest.ctx = ctx
        loaded[mname] = manifest

        for svcName, svcDef in pairs(manifest.services or {}) do
          local ops = {}
          for op, spec in pairs(svcDef.ops or {}) do
            local fn, level, danger = spec, nil, nil
            if type(spec) == "table" then
              fn, level, danger = spec.fn, spec.level, spec.danger
            end
            ops[op] = { level = level, danger = danger,
              fn = function(env, p) return fn(env, p, ctx) end }
          end
          node:service(svcName, { ops = ops })
        end

        if manifest.telemetry then
          node:spawn(function()
            local interval = ctx.cfg.interval or cfg.telemetryInterval or 2
            while true do
              local okt, metrics = pcall(manifest.telemetry, ctx)
              if okt and metrics then
                node:emit("core.telemetry", "push", { metrics = metrics },
                  { chan = node.chan.telemetry })
              elseif not okt then
                log.warn("module %s telemetry error: %s", mname, metrics)
              end
              sched.sleep(interval)
            end
          end, "telemetry:" .. mname)
        end
        log.info("module %s loaded", mname)
      end
    end
  end
  return loaded
end

return M
