--[[
  platform.service — Service lifecycle helpers for cc-platform-core

  Provides a lightweight service context for Opus-based CC services.
  Handles modem discovery, channel management, config loading, and
  message sending using platform conventions.

  Usage:
    local Service  = require('platform.service')

    local ctx = Service.create({
      name = 'inventory-manager',
      defaults = { scanInterval = 120 },
    })

    Service.openChannels(ctx)

    -- Send on a named channel
    Service.send(ctx, 'inventory.broadcast', 'state', { items = {...} })

    -- Broadcast on both legacy and target channels during migration
    Service.broadcast(ctx, 'inventory.broadcast', 'state', { items = {...} })
]]

local Config   = require('opus.config')
local Protocol = require('platform.protocol')
local Channels = require('platform.channels')

local peripheral = _G.peripheral

local Service = {}

--- Create a new service context.
-- Initializes the protocol identity, loads config, discovers modem.
-- @param opts table  Options:
--   name     string   (required) Service identifier
--   defaults table    Default config values
--   modem    boolean  Set to false to skip modem discovery
--   channelPrefix string  Override for channel name prefix (defaults to name)
-- @return table  Service context
function Service.create(opts)
  assert(opts and opts.name, 'Service name required')

  Protocol.setServiceId(opts.name)

  local prefix = opts.channelPrefix or opts.name
  local ctx = {
    name     = opts.name,
    config   = Config.load(opts.name, opts.defaults or {}),
    prefix   = prefix,
    channels = Channels.forService(prefix),
    modem    = nil,
  }

  -- Discover modem
  if opts.modem ~= false then
    -- Prefer Opus device registry
    if _G.device and _G.device.wireless_modem then
      ctx.modem = _G.device.wireless_modem
    else
      -- Fallback: scan peripherals (wired or wireless)
      for _, name in ipairs(peripheral.getNames()) do
        if peripheral.getType(name) == 'modem' then
          ctx.modem = peripheral.wrap(name)
          break
        end
      end
    end
  end

  return ctx
end

--- Open all registered channels for this service on its modem.
-- Respects dual-mode: opens both current and target channels when appropriate.
-- @param ctx table  Service context from Service.create()
function Service.openChannels(ctx)
  if not ctx.modem then return end

  local channelList = Channels.openListForService(ctx.prefix)
  for _, ch in ipairs(channelList) do
    ctx.modem.open(ch)
  end
end

--- Close all registered channels for this service.
-- @param ctx table  Service context
function Service.closeChannels(ctx)
  if not ctx.modem then return end

  local channelList = Channels.openListForService(ctx.prefix)
  for _, ch in ipairs(channelList) do
    ctx.modem.close(ch)
  end
end

--- Send a message on a single named channel.
-- Uses Protocol.message() to construct a well-formed envelope.
-- @param ctx table          Service context
-- @param channelName string Logical channel name (e.g. 'inventory.broadcast')
-- @param msgType string     Message type discriminator
-- @param data table|nil     Payload fields
-- @param opts table|nil     Protocol.message options (id, src, noMeta)
-- @return boolean  true if sent successfully
function Service.send(ctx, channelName, msgType, data, opts)
  if not ctx.modem then return false end
  local ch = Channels.get(channelName)
  local msg = Protocol.message(msgType, data, opts)
  ctx.modem.transmit(ch, ch, msg)
  return true
end

--- Broadcast a message, sending on both channels during dual-mode migration.
-- In current or target mode, behaves identically to send().
-- @param ctx table          Service context
-- @param channelName string Logical channel name
-- @param msgType string     Message type discriminator
-- @param data table|nil     Payload fields
-- @param opts table|nil     Protocol.message options
-- @return boolean  true if sent on at least one channel
function Service.broadcast(ctx, channelName, msgType, data, opts)
  if not ctx.modem then return false end

  local msg = Protocol.message(msgType, data, opts)

  if Channels.mode == 'dual' then
    local sent = false
    for _, ch in ipairs(Channels.getBoth(channelName)) do
      ctx.modem.transmit(ch, ch, msg)
      sent = true
    end
    return sent
  else
    local ch = Channels.get(channelName)
    ctx.modem.transmit(ch, ch, msg)
    return true
  end
end

--- Save the service config back to persistent storage.
-- @param ctx table  Service context
function Service.saveConfig(ctx)
  Config.update(ctx.name, ctx.config)
end

--- Reload the service config from persistent storage.
-- @param ctx table  Service context
-- @param defaults table|nil  Default values
function Service.reloadConfig(ctx, defaults)
  ctx.config = Config.load(ctx.name, defaults or ctx.config)
end

return Service
