-- factoryos.util — small shared helpers. No CC API dependencies.
local M = {}

local unpack = table.unpack or unpack
M.unpack = unpack

local rng = math.random

local function hex(n) return string.format("%x", n) end

--- Random UUID-ish identifier. Not crypto; uniqueness only.
function M.uuid()
  math.randomseed(os.clock() * 1e6 + (M._seed or 0))
  M._seed = (M._seed or 0) + 1
  local t = {}
  for _ = 1, 8 do t[#t + 1] = hex(rng(0, 0xffff)) end
  return table.concat(t, "-")
end

--- Monotonic-ish timestamp for messages (ms since epoch when available).
function M.now()
  if os.epoch then return os.epoch("utc") end
  return math.floor(os.clock() * 1000)
end

--- Seconds for intervals/timeouts (per-computer clock).
function M.clock()
  return os.clock()
end

function M.copy(t)
  local o = {}
  for k, v in pairs(t) do o[k] = v end
  return o
end

function M.keys(t)
  local o = {}
  for k in pairs(t) do o[#o + 1] = k end
  table.sort(o, function(a, b) return tostring(a) < tostring(b) end)
  return o
end

function M.clamp(v, lo, hi)
  if v < lo then return lo end
  if v > hi then return hi end
  return v
end

--- Compact number formatting for telemetry readouts (12.4k, 3.1M, 2.0G).
function M.eng(v, unit)
  unit = unit or ""
  if type(v) ~= "number" then return tostring(v) .. (unit ~= "" and (" " .. unit) or "") end
  local neg = v < 0
  local a = math.abs(v)
  local suf, d = "", a
  if a >= 1e12 then suf, d = "T", a / 1e12
  elseif a >= 1e9 then suf, d = "G", a / 1e9
  elseif a >= 1e6 then suf, d = "M", a / 1e6
  elseif a >= 1e3 then suf, d = "k", a / 1e3
  end
  local s = d >= 100 and string.format("%.0f", d) or string.format("%.1f", d)
  if neg then s = "-" .. s end
  return s .. suf .. (unit ~= "" and (" " .. unit) or "")
end

--- Word-wrap a string to width. Returns a list of lines; hard-breaks long
--- words rather than truncating.
function M.wrap(s, w)
  local out = {}
  for para in (tostring(s) .. "\n"):gmatch("([^\n]*)\n") do
    if para == "" then
      out[#out + 1] = ""
    else
      local line = ""
      for w0 in para:gmatch("%S+") do
        local cand = line == "" and w0 or (line .. " " .. w0)
        if #cand <= w then
          line = cand
        else
          if line ~= "" then out[#out + 1] = line end
          local rest = w0
          while #rest > w do
            out[#out + 1] = rest:sub(1, w)
            rest = rest:sub(w + 1)
          end
          line = rest
        end
      end
      if line ~= "" then out[#out + 1] = line end
    end
  end
  return out
end

--- Shallow-extend defaults with overrides.
function M.defaults(overrides, defs)
  local o = M.copy(defs)
  for k, v in pairs(overrides or {}) do o[k] = v end
  return o
end

return M
