#!/usr/bin/env node
/**
 * Refactor helper: rewrites require('./xxx') / require('../xxx') paths in
 * dashcaddy-api to point to the new src/<subdir>/xxx.js locations.
 *
 * Algorithm:
 *   1. For each require() call with a relative spec:
 *   2. If the resolved file exists, leave it alone.
 *   3. If the resolved file does NOT exist, the bare name of the spec
 *      (or the directory name 'dns-providers') might be one of the
 *      modules that was moved out of the repo root. In that case, rewrite
 *      the spec to the correct relative path to the new location.
 *   4. Otherwise leave alone.
 */
const fs = require('fs');
const path = require('path');

const REPO = process.cwd();

// Map: bare module name (no extension) -> new repo-relative path (no extension)
const NEW_LOCATIONS = {
  'auth-manager': 'src/managers/auth-manager',
  'credential-manager': 'src/managers/credential-manager',
  'license-manager': 'src/managers/license-manager',
  'port-lock-manager': 'src/managers/port-lock-manager',
  'state-manager': 'src/managers/state-manager',
  'notification-manager': 'src/managers/notification-manager',
  'resource-monitor': 'src/managers/resource-monitor',
  'config-drift-detector': 'src/managers/config-drift-detector',
  'auto-restart-manager': 'src/managers/auto-restart-manager',
  'update-manager': 'src/managers/update-manager',
  'dependency-manager': 'src/managers/dependency-manager',
  'csrf-protection': 'src/security/csrf-protection',
  'crypto-utils': 'src/security/crypto-utils',
  'docker-security': 'src/security/docker-security',
  'input-validator': 'src/security/input-validator',
  'keychain-manager': 'src/security/keychain-manager',
  'log-digest': 'src/security/log-digest',
  'audit-logger': 'src/security/audit-logger',
  'docker-maintenance': 'src/docker/docker-maintenance',
  'app-templates': 'src/docker/app-templates',
  'self-updater': 'src/docker/self-updater',
  'dns-propagation': 'src/dns/dns-propagation',
  'recipe-templates': 'src/recipes/recipe-templates',
  'bundled-workflows': 'src/recipes/bundled-workflows',
  'health-checker': 'src/monitoring/health-checker',
  'metrics': 'src/monitoring/metrics',
  'ssl-monitor': 'src/monitoring/ssl-monitor',
  'backup-manager': 'src/utilities/backup-manager',
  'error-handler': 'src/utilities/error-handler',
  'errors': 'src/utilities/errors',
  'fs-helpers': 'src/utilities/fs-helpers',
  'pagination': 'src/utilities/pagination',
  'url-resolver': 'src/utilities/url-resolver',
  'config-schema': 'src/utilities/config-schema',
  'constants': 'src/utilities/constants',
  'middleware': 'src/utilities/middleware',
  'startup-validator': 'src/utilities/startup-validator',
  'cache-config': 'src/utilities/cache-config',
};

const SKIP_DIRS = new Set(['node_modules', '.git']);
const SKIP_FILE_PATTERNS = [/\/scripts\/refactor-requires\.js$/];

function* walk(dir) {
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    if (SKIP_DIRS.has(entry.name)) continue;
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      yield* walk(full);
    } else if (entry.name.endsWith('.js')) {
      yield full;
    }
  }
}

function toRelativeFromFile(filePath, targetRel) {
  const fromDir = path.dirname(filePath);
  const targetAbs = path.resolve(REPO, targetRel);
  let rel = path.relative(fromDir, targetAbs);
  if (!rel.startsWith('.')) rel = './' + rel;
  return rel.split(path.sep).join('/');
}

function fileExistsWithJsOrIndex(p) {
  // exists if p is a file, or p is a dir with index.js
  try {
    if (fs.existsSync(p) && fs.statSync(p).isFile()) return true;
  } catch (_) {}
  try {
    if (fs.existsSync(p + '.js') && fs.statSync(p + '.js').isFile()) return true;
  } catch (_) {}
  try {
    if (
      fs.existsSync(p) &&
      fs.statSync(p).isDirectory() &&
      fs.existsSync(path.join(p, 'index.js'))
    )
      return true;
  } catch (_) {}
  return false;
}

function refactor(filePath) {
  const relFile = path.relative(REPO, filePath);
  if (SKIP_FILE_PATTERNS.some((re) => re.test(relFile))) return false;

  const content = fs.readFileSync(filePath, 'utf8');
  let changed = false;

  const requireRe = /require\(\s*(['"])([^'"]+)\1\s*\)/g;
  const newContent = content.replace(requireRe, (full, quote, spec) => {
    if (!spec.startsWith('.')) return full; // package require, leave alone
    const fromDir = path.dirname(filePath);
    const resolvedBase = path.resolve(fromDir, spec);
    // If the resolved file exists, the require is correct as-is.
    if (fileExistsWithJsOrIndex(resolvedBase)) {
      // But — check for the special case: require to <REPO>/dns-providers/x
      // which after move becomes <REPO>/src/dns/dns-providers/x — wait,
      // that doesn't exist anymore. The dir was moved.
      const dnsProvidersOld = path.resolve(REPO, 'dns-providers');
      if (
        resolvedBase === dnsProvidersOld ||
        resolvedBase.startsWith(dnsProvidersOld + path.sep)
      ) {
        const subPath = resolvedBase === dnsProvidersOld ? '' : resolvedBase.slice(dnsProvidersOld.length + 1);
        const newResolved = path.resolve(REPO, 'src/dns/dns-providers', subPath);
        let rel = path.relative(fromDir, newResolved);
        if (!rel.startsWith('.')) rel = './' + rel;
        const newSpec = rel.split(path.sep).join('/');
        changed = true;
        return `require(${quote}${newSpec}${quote})`;
      }
      return full;
    }
    // The file does not exist. Check if the bare name is a moved module.
    const bare = path.basename(resolvedBase);
    if (bare in NEW_LOCATIONS) {
      const target = NEW_LOCATIONS[bare];
      const newSpec = toRelativeFromFile(filePath, target);
      changed = true;
      return `require(${quote}${newSpec}${quote})`;
    }
    // Bare not in map. Check for the special case: the spec points into
    // the OLD dns-providers dir (now src/dns/dns-providers). E.g. spec
    // could be '../dns-providers/registry' or './dns-providers/registry'
    // from somewhere else.
    if (spec.includes('dns-providers')) {
      const dnsProvidersOld = path.resolve(REPO, 'dns-providers');
      if (
        resolvedBase === dnsProvidersOld ||
        resolvedBase.startsWith(dnsProvidersOld + path.sep)
      ) {
        const subPath =
          resolvedBase === dnsProvidersOld ? '' : resolvedBase.slice(dnsProvidersOld.length + 1);
        const newResolved = path.resolve(REPO, 'src/dns/dns-providers', subPath);
        let rel = path.relative(fromDir, newResolved);
        if (!rel.startsWith('.')) rel = './' + rel;
        const newSpec = rel.split(path.sep).join('/');
        changed = true;
        return `require(${quote}${newSpec}${quote})`;
      }
    }
    return full;
  });

  if (changed) {
    fs.writeFileSync(filePath, newContent);
  }
  return changed;
}

let count = 0;
for (const file of walk(REPO)) {
  if (refactor(file)) {
    count += 1;
    console.log('rewrote', path.relative(REPO, file));
  }
}
console.log(`\nDone: rewrote ${count} file(s).`);
