-- libversion: resolve a file's version from its owning package descriptor.
--
-- A factory: `local createVersion = require('/apis/libversion'); local v = createVersion();`
--
-- Resolution order for `api.forFile(path)`:
--   1. `<stateDir>/ccpm.lock.json` — production lookup against installed packages.
--   2. `<repoRoot>/packages/*/ccpm.json` — dev fallback (e.g. `just trapos`,
--      where the repo is mounted read-only at `/trapos` and ccpm has never run).
--   3. `'?'` when no descriptor lists the file.
--
-- `api.forSelf()` returns the version of the currently running program by
-- delegating to `shell.getRunningProgram()`.

local DEFAULT_STATE_DIR = '/trapos';
local DEFAULT_REPO_ROOT = '/trapos';

local function normalizePath(path)
  if type(path) ~= 'string' or path == '' then return nil; end
  path = path:gsub('\\', '/');
  path = path:gsub('//+', '/');
  if path:sub(1, 1) ~= '/' then
    path = '/' .. path;
  end
  return path;
end

local function readJsonFile(fsLib, path)
  if not fsLib.exists(path) then return nil; end
  local f = fsLib.open(path, 'r');
  if not f then return nil; end
  local data = f.readAll();
  f.close();
  if not data or data == '' then return nil; end
  return textutils.unserializeJSON(data);
end

local function fileMatches(files, target)
  if type(files) ~= 'table' then return false; end
  for _, raw in ipairs(files) do
    if normalizePath(raw) == target then
      return true;
    end
  end
  return false;
end

local function createVersion(opts)
  opts = opts or {};
  local fsLib = opts.fs or fs;
  local shellLib = opts.shell or shell;
  local stateDir = opts.stateDir or DEFAULT_STATE_DIR;
  local repoRoot = opts.repoRoot or DEFAULT_REPO_ROOT;

  local lockPath = stateDir .. '/ccpm.lock.json';
  local packagesDir = repoRoot .. '/packages';

  local api = {};

  local function lookupInLock(target)
    local lock = readJsonFile(fsLib, lockPath);
    if not lock or type(lock.packages) ~= 'table' then return nil; end
    for _, entry in pairs(lock.packages) do
      if fileMatches(entry.files, target) then
        return entry.version;
      end
    end
    return nil;
  end

  local function lookupInRepo(target)
    if not fsLib.isDir(packagesDir) then return nil; end
    for _, name in ipairs(fsLib.list(packagesDir)) do
      local descPath = packagesDir .. '/' .. name .. '/ccpm.json';
      local desc = readJsonFile(fsLib, descPath);
      if desc and fileMatches(desc.files, target) then
        return desc.version;
      end
    end
    return nil;
  end

  function api.forFile(path)
    local target = normalizePath(path);
    if not target then return '?'; end
    local v = lookupInLock(target);
    if v then return v; end
    v = lookupInRepo(target);
    if v then return v; end
    return '?';
  end

  function api.forSelf()
    if not shellLib or not shellLib.getRunningProgram then return '?'; end
    local path = shellLib.getRunningProgram();
    if not path or path == '' then return '?'; end
    return api.forFile(path);
  end

  return api;
end

return createVersion;
