local DEFAULT_THROTTLE_SECONDS = 5;
local DEFAULT_MAX_REPLY_CHARS = 160;
local DEFAULT_PREFIX = 'TrapGPT';
local DEFAULT_SESSION_SETTING_KEY = 'trapgpt.opencc.session_id';
local SILENCE = 'SILENCE';

local function nowSeconds()
  if os.epoch then
    return os.epoch('utc') / 1000;
  end
  return os.clock();
end

local function resolveNumber(value, defaultValue)
  local n = tonumber(value);
  if not n or n < 0 then return defaultValue; end
  return n;
end

local function trim(s)
  return tostring(s or ''):gsub('^%s+', ''):gsub('%s+$', '');
end

local function truncate(s, maxChars)
  s = trim(s);
  if #s <= maxChars then return s; end
  if maxChars <= 3 then return string.sub(s, 1, maxChars); end
  return string.sub(s, 1, maxChars - 3) .. '...';
end

local function formatChatLine(message)
  local at = message.at and ('@' .. tostring(math.floor(message.at)) .. ' ') or '';
  return at .. tostring(message.username or '?') .. ': ' .. tostring(message.text or '');
end

local function buildPrompt(messages, firstBatch, maxReplyChars)
  local lines = {
    'Tu es TrapGPT dans le chat Minecraft.',
    'Reponds seulement si utile.',
    'Reponse tres concise: une phrase courte, maximum ' .. tostring(maxReplyChars) .. ' caracteres.',
    'Pas de markdown. Ne repete pas l historique.',
    'Si aucune reponse utile, reponds exactement: ' .. SILENCE,
  };
  if firstBatch then
    lines[#lines + 1] = 'Contexte initial: voici les premiers messages recus.';
  end
  lines[#lines + 1] = '';
  lines[#lines + 1] = 'Nouveaux messages chat depuis le dernier envoi:';
  for _, message in ipairs(messages) do
    lines[#lines + 1] = formatChatLine(message);
  end
  return table.concat(lines, '\n');
end

local function createTrapGpt(opts)
  opts = opts or {};

  local settingsLib = opts.settings or settings;
  local nowFunc = opts.now or nowSeconds;
  local sleepFunc = opts.sleep or sleep;
  local ai = opts.ai or require('/apis/libai')();
  local chatBox = opts.chatBox;
  local log = opts.log or print;

  local api = {};
  local history = {};
  local sentIndex = 0;
  local firstBatch = true;
  local lastSendAt = 0;
  local active = false;
  local stopped = false;

  local function throttleSeconds()
    return resolveNumber(settingsLib.get('trapgpt.throttle_seconds'), DEFAULT_THROTTLE_SECONDS);
  end

  local function maxReplyChars()
    return math.max(1, resolveNumber(settingsLib.get('trapgpt.max_reply_chars'), DEFAULT_MAX_REPLY_CHARS));
  end

  local function prefix()
    local value = settingsLib.get('trapgpt.prefix');
    if type(value) ~= 'string' or value == '' then return DEFAULT_PREFIX; end
    return value;
  end

  local function queuedMessages()
    local messages = {};
    for i = sentIndex + 1, #history do
      messages[#messages + 1] = history[i];
    end
    return messages;
  end

  local function shouldIgnore(username, text, isHidden)
    if isHidden then return true; end
    if type(text) ~= 'string' or trim(text) == '' then return true; end
    if type(username) == 'string' and username == prefix() then return true; end
    return false;
  end

  function api.onChat(username, message, uuid, isHidden, messageUtf8)
    local text = messageUtf8 or message;
    if shouldIgnore(username, text, isHidden) then return false; end
    history[#history + 1] = {
      username = username,
      text = text,
      uuid = uuid,
      at = nowFunc(),
    };
    return true;
  end

  function api.pendingCount()
    return #history - sentIndex;
  end

  function api.history()
    return history;
  end

  function api.buildPrompt(messages)
    return buildPrompt(messages, firstBatch, maxReplyChars());
  end

  function api.processOnce()
    if active or api.pendingCount() <= 0 then return false; end

    local waitSeconds = throttleSeconds() - (nowFunc() - lastSendAt);
    if waitSeconds > 0 then sleepFunc(waitSeconds); end

    local startIndex = sentIndex + 1;
    local messages = queuedMessages();
    if #messages == 0 then return false; end

    active = true;
    local ok, result = ai.ask(buildPrompt(messages, firstBatch, maxReplyChars()), {
      sessionSettingKey = DEFAULT_SESSION_SETTING_KEY,
      sessionTitle = 'trapgpt',
    });
    active = false;
    lastSendAt = nowFunc();

    if not ok then
      log('trapgpt ai error: ' .. tostring(result));
      return false;
    end

    sentIndex = startIndex + #messages - 1;
    firstBatch = false;

    local reply = truncate(result.reply, maxReplyChars());
    if reply == '' or reply == SILENCE then return true; end
    if chatBox then
      local sent, err = chatBox.sendMessage(reply, prefix());
      if not sent then log('trapgpt chat error: ' .. tostring(err)); end
    end
    return true, reply;
  end

  function api.stop()
    stopped = true;
  end

  function api.run()
    while not stopped do
      if api.pendingCount() > 0 then
        api.processOnce();
      else
        sleepFunc(0.25);
      end
    end
  end

  return api;
end

return createTrapGpt;
