local _VERSION = '6.1.0';

local REPO_BASE = 'https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/master/';
local LOCAL_STATE_DIR = '/trapos';
local LOCAL_MANIFEST_PATH = '/trapos/manifest.json';
local LOCAL_CONFIG_PATH = '/trapos/ccpm.json';
local LOCAL_LOCK_PATH = '/trapos/ccpm.lock.json';
local DEFAULT_REGISTRY_NAME = 'guillaumearm/cc-libs';
local INSTALL_BRANCH = 'master';

local function printUsage()
  print('install-trapos usage:');
  print();
  print('\t\twget run <install-url>');
  print('\t\twget run <install-url> --cpm-only');
end

local function readJsonFile(path)
  if not fs.exists(path) then return nil; end
  local f = fs.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 writeJsonFile(path, value)
  fs.makeDir(LOCAL_STATE_DIR);
  local f = fs.open(path, 'w');
  if not f then return false; end
  f.write(textutils.serializeJSON(value));
  f.close();
  return true;
end

local function fetchJson(url)
  local response = http.get(url);
  if not response then return nil end
  local body = response.readAll();
  response.close();
  if not body or body == '' then return nil end
  return textutils.unserializeJSON(body);
end

local function fetchManifest()
  return fetchJson(REPO_BASE .. 'manifest.json');
end

local function fetchDescriptor(pkg)
  return fetchJson(REPO_BASE .. 'packages/' .. pkg .. '/ccpm.json');
end

local function ensureProgramsPath()
  local current = shell.path();
  for entry in string.gmatch(current, '[^:]+') do
    if entry == '/programs' then return; end
  end
  if current == '' then
    shell.setPath('/programs');
    return;
  end
  shell.setPath(current .. ':/programs');
end

-- Resolve a list of package names + their dependencies into install order
-- (deps first). Returns an ordered list of descriptors or nil, err.
local function resolvePackages(names)
  local ordered = {};
  local state = {}; -- name -> 'visiting' | 'done'

  local function visit(name)
    if state[name] == 'done' then return true end
    if state[name] == 'visiting' then
      return false, 'dependency cycle detected at ' .. name;
    end
    state[name] = 'visiting';
    local desc = fetchDescriptor(name);
    if not desc then
      return false, 'package not found: ' .. name;
    end
    for _, dep in ipairs(desc.dependencies or {}) do
      local ok, err = visit(dep);
      if not ok then return false, err end
    end
    state[name] = 'done';
    ordered[#ordered + 1] = desc;
    return true;
  end

  for _, name in ipairs(names) do
    local ok, err = visit(name);
    if not ok then return nil, err end
  end
  return ordered;
end

-- Seed/refresh the default ccpm registry so it tracks the install branch.
local function seedCcpmConfig()
  local cfg = readJsonFile(LOCAL_CONFIG_PATH) or { registries = {} };
  cfg.registries = cfg.registries or {};
  local found = false;
  for _, r in ipairs(cfg.registries) do
    if r.name == DEFAULT_REGISTRY_NAME then
      r.type = 'gitea';
      r.branch = INSTALL_BRANCH;
      found = true;
    end
  end
  if not found then
    table.insert(cfg.registries, 1, { name = DEFAULT_REGISTRY_NAME, type = 'gitea', branch = INSTALL_BRANCH });
  end
  writeJsonFile(LOCAL_CONFIG_PATH, cfg);
end

local rawArgs = table.pack(...);
local cpmOnly = false;

for i = 1, rawArgs.n do
  local a = rawArgs[i];
  if a == 'version' or a == '-version' or a == '--version' then
    print('install-trapos v' .. _VERSION);
    return;
  elseif a == 'help' or a == '-help' or a == '--help' then
    printUsage();
    return;
  elseif a == '--cpm-only' or a == '-cpm-only' then
    cpmOnly = true;
  elseif a ~= nil and a ~= '' then
    printUsage();
    return;
  end
end

print('Fetching manifest from branch: ' .. INSTALL_BRANCH);
local manifest = fetchManifest();
if not manifest then
  print('Failed to fetch or parse manifest.json from ' .. INSTALL_BRANCH);
  return;
end

local requested = { 'trapos-core' };
local explicitBootstrap = {};
if cpmOnly then
  for _, name in ipairs(requested) do
    explicitBootstrap[name] = true;
  end
end

local resolved, resolveErr = resolvePackages(requested);
if not resolved then
  print('Failed to resolve packages: ' .. resolveErr);
  return;
end

-- Legacy file cleanup (pre-manifest installs).
fs.delete('ping-server.lua');
fs.delete('ping.lua');
fs.delete('cube.lua');
fs.delete('router.lua');
fs.delete('servers/cube-startup.lua');
fs.delete('programs/cube.lua');
fs.delete('programs/goo.lua');
fs.delete('servers/cube-server.lua');
fs.delete('servers/cube-boot.lua');

local previousDir = shell.dir();
shell.setDir('/');

fs.makeDir('/programs');
fs.makeDir('/apis');
fs.makeDir('/startup');
fs.makeDir('/daemons');
fs.makeDir(LOCAL_STATE_DIR);

local allFiles = {};
local seenFile = {};
local autostart = {};
local seenAuto = {};
local lockPackages = {};

for _, desc in ipairs(resolved) do
  for _, filePath in ipairs(desc.files or {}) do
    if not seenFile[filePath] then
      seenFile[filePath] = true;
      allFiles[#allFiles + 1] = filePath;
      fs.delete(filePath);
      -- Sources live under packages/<pkg>/ in the repo but install flat, so
      -- fetch from the package path and write to the flat destination.
      shell.execute('wget', REPO_BASE .. 'packages/' .. desc.name .. '/' .. filePath, filePath);
    end
  end
  for _, srv in ipairs(desc.autostart or {}) do
    if not seenAuto[srv] then
      seenAuto[srv] = true;
      autostart[#autostart + 1] = srv;
    end
  end
  lockPackages[desc.name] = {
    version = desc.version,
    registry = DEFAULT_REGISTRY_NAME,
    files = desc.files or {},
    dependencies = desc.dependencies or {},
    autostart = desc.autostart or {},
    explicit = explicitBootstrap[desc.name] == true,
  };
end

-- Aggregated OS state for motd/daemons/upgrade (unchanged consumers).
writeJsonFile(LOCAL_MANIFEST_PATH, {
  name = manifest.name or 'TrapOS',
  version = manifest.version or '?',
  branch = INSTALL_BRANCH,
  files = allFiles,
  autostart = autostart,
});

writeJsonFile(LOCAL_LOCK_PATH, { packages = lockPackages });
seedCcpmConfig();
ensureProgramsPath();

print();
print('=> ccpm installed (branch: ' .. INSTALL_BRANCH .. ')');
print('=> Default registry: ' .. DEFAULT_REGISTRY_NAME);

if cpmOnly then
  print('=> Run: ccpm update');
  print('=> Run: ccpm install trapos');
  shell.setDir(previousDir);
  return;
end

-- Full install: sync the registry and install the TrapOS meta-package.
shell.execute('ccpm', 'update');
shell.execute('ccpm', 'install', 'trapos');
shell.execute('trapos-postinstall');
shell.setDir(previousDir);
print();
print('=> Rebooting...');
os.reboot();
