'use strict';

const fs = require('node:fs');
const path = require('node:path');
const {
  HOOK_MANIFEST,
  MAX_HOOK_MANIFEST_BYTES,
  validateHookManifest,
} = require('./manifest');

function discoverHookPackages({ roots = [], enabled = null } = {}) {
  const packages = [];
  const diagnostics = [];
  const enabledSet = enabled ? new Set(enabled) : null;

  for (const root of roots || []) {
    const rootResult = safeRealpath(root);
    if (!rootResult.ok) {
      diagnostics.push({ level: 'warn', root, reason: rootResult.reason });
      continue;
    }
    const rootReal = rootResult.realpath;
    let entries = [];
    try {
      entries = fs.readdirSync(rootReal, { withFileTypes: true });
    } catch (err) {
      diagnostics.push({ level: 'warn', root: rootReal, reason: err.message });
      continue;
    }
    const candidateDirs = [
      { name: '.', dir: rootReal },
      ...entries.filter(entry => entry.isDirectory()).map(entry => ({ name: entry.name, dir: path.join(rootReal, entry.name) })),
    ];
    for (const candidate of candidateDirs) {
      const manifestPath = path.join(candidate.dir, HOOK_MANIFEST);
      if (!fs.existsSync(manifestPath)) continue;
      const loaded = loadHookManifest(manifestPath, { rootReal, packageDir: candidate.dir });
      diagnostics.push(...loaded.diagnostics);
      if (!loaded.ok) continue;
      if (enabledSet && !enabledSet.has(loaded.package.manifest.id)) {
        diagnostics.push({ level: 'info', id: loaded.package.manifest.id, reason: 'not enabled' });
        continue;
      }
      packages.push(loaded.package);
    }
  }
  return { packages, diagnostics };
}

function loadHookManifest(manifestPath, { rootReal, packageDir }) {
  const diagnostics = [];
  let manifestReal;
  let packageReal;
  try {
    manifestReal = fs.realpathSync(manifestPath);
    packageReal = fs.realpathSync(packageDir);
  } catch (err) {
    return { ok: false, diagnostics: [{ level: 'warn', path: manifestPath, reason: err.message }] };
  }
  if (!isInside(rootReal, manifestReal) || !isInside(rootReal, packageReal)) {
    return { ok: false, diagnostics: [{ level: 'error', path: manifestPath, reason: 'hook package escapes root' }] };
  }
  const stat = fs.statSync(manifestReal);
  if (stat.size > MAX_HOOK_MANIFEST_BYTES) {
    return { ok: false, diagnostics: [{ level: 'error', path: manifestPath, reason: 'manifest too large' }] };
  }
  if ((fs.statSync(packageReal).mode & 0o002) !== 0) {
    return { ok: false, diagnostics: [{ level: 'error', path: packageReal, reason: 'hook package is world-writable' }] };
  }

  let raw;
  let parsed;
  try {
    raw = fs.readFileSync(manifestReal, 'utf8');
    parsed = JSON.parse(raw);
  } catch (err) {
    return { ok: false, diagnostics: [{ level: 'error', path: manifestPath, reason: err.message }] };
  }
  const validation = validateHookManifest(parsed, { packageDir: packageReal });
  if (!validation.ok) {
    return {
      ok: false,
      diagnostics: validation.errors.map(reason => ({ level: 'error', path: manifestPath, reason })),
    };
  }
  const entryPath = path.resolve(packageReal, validation.manifest.entry);
  const entryRealResult = safeRealpath(entryPath);
  if (!entryRealResult.ok || !isInside(packageReal, entryRealResult.realpath)) {
    return { ok: false, diagnostics: [{ level: 'error', path: entryPath, reason: 'entry escapes hook package' }] };
  }
  return {
    ok: true,
    package: {
      manifest: validation.manifest,
      manifestPath: manifestReal,
      entryPath: entryRealResult.realpath,
    },
    diagnostics,
  };
}

function safeRealpath(target) {
  try {
    return { ok: true, realpath: fs.realpathSync(target) };
  } catch (err) {
    return { ok: false, reason: err.message };
  }
}

function isInside(root, target) {
  const rel = path.relative(root, target);
  return rel === '' || (!!rel && !rel.startsWith('..') && !path.isAbsolute(rel));
}

module.exports = {
  discoverHookPackages,
  isInside,
  loadHookManifest,
};
