/**
 * Configuration loading utilities for CLI commands
 *
 * Follows ESLint pattern: all commands load config by default, --no-config to opt-out
 */

import { findConfigFile, validateConfig } from '../../utils/config/types';
import { loadConfigWithExtends, getBuiltinPresetPath } from '../../utils/config/extends';
import { ConfigError, validateAllRuleOptions } from '../../utils/config/resolver';
import { ClaudeLintConfig } from '../../utils/config/types';
import { CustomRuleLoader } from '../../utils/rules/loader';
import { logger } from './logger';

/**
 * Load custom rules so their IDs are in the registry before config validation.
 * Uses the synchronous load path since jiti (the TypeScript loader) is synchronous.
 */
let customRulesLoaded = false;
export function ensureCustomRulesLoaded(): void {
  if (customRulesLoaded) return;
  customRulesLoaded = true;

  const loader = new CustomRuleLoader({
    customRulesPath: '.claudelint/rules',
    enableCustomRules: true,
  });

  loader.loadCustomRulesSync(process.cwd());
}

interface ConfigLoaderOptions {
  config?: string | false; // Commander sets config to false when --no-config is used
  preset?: string; // Override default preset (e.g., 'all', 'strict')
  verbose?: boolean;
  debugConfig?: boolean;
}

/**
 * Load configuration without validation
 *
 * Use this when custom rules need to be registered before config validation
 * (custom rule IDs must be in the registry for validation to pass).
 *
 * @param options - Command options (config, verbose, debugConfig)
 * @returns Loaded config object (empty if --no-config)
 */
export function loadConfig(options: ConfigLoaderOptions): ClaudeLintConfig {
  // Skip config loading if --no-config flag is set (Commander sets config to false)
  if (options.config === false) {
    if (options.verbose || options.debugConfig) {
      logger.info('Skipping config file (--no-config)');
    }
    return {};
  }

  let config: ClaudeLintConfig = {};

  // Load from explicit path or auto-discover
  if (options.config) {
    if (options.debugConfig) {
      logger.info(`[Config Debug] Loading config from: ${options.config}`);
    }
    try {
      config = loadConfigWithExtends(options.config);
      if (options.verbose || options.debugConfig) {
        logger.info(`Using config file: ${options.config}`);
      }
      if (options.debugConfig) {
        logger.info('[Config Debug] Loaded config (with extends resolved):');
        logger.log(JSON.stringify(config, null, 2));
      }
    } catch (error: unknown) {
      logger.error(`Error loading config file: ${options.config}`);
      logger.error(error instanceof Error ? error.message : String(error));
      process.exit(2);
    }
  } else {
    if (options.debugConfig) {
      logger.info(`[Config Debug] Searching for config file from: ${process.cwd()}`);
    }
    const configPath = findConfigFile(process.cwd());
    if (configPath) {
      if (options.debugConfig) {
        logger.info(`[Config Debug] Found config file: ${configPath}`);
      }
      try {
        config = loadConfigWithExtends(configPath);
        if (options.verbose || options.debugConfig) {
          logger.info(`Using config file: ${configPath}`);
        }
        if (options.debugConfig) {
          logger.info('[Config Debug] Loaded config (with extends resolved):');
          logger.log(JSON.stringify(config, null, 2));
        }
      } catch (error: unknown) {
        logger.error(`Error loading config file: ${configPath}`);
        logger.error(error instanceof Error ? error.message : String(error));
        process.exit(2);
      }
    } else {
      // No config file found — load a built-in preset as default
      const presetName = options.preset || 'recommended';
      const presetPath = getBuiltinPresetPath(presetName);
      if (presetPath) {
        try {
          config = loadConfigWithExtends(presetPath);
          if (options.verbose || options.debugConfig) {
            logger.info(`No config file found, using claudelint:${presetName} preset`);
          }
        } catch {
          if (options.debugConfig) {
            logger.info(`[Config Debug] Failed to load ${presetName} preset, using defaults`);
          }
        }
      } else if (options.debugConfig) {
        logger.info(`[Config Debug] Unknown preset: ${presetName}, using defaults`);
      }
    }
  }

  return config;
}

/**
 * Validate a loaded configuration against the rule registry
 *
 * Call this after custom rules have been registered so their IDs are recognized.
 *
 * @param config - Previously loaded config object
 */
export function validateLoadedConfig(config: ClaudeLintConfig): void {
  // Validate config against rule registry (rule IDs exist)
  const configErrors = validateConfig(config);
  if (configErrors.length > 0) {
    logger.newline();
    logger.error('Configuration validation errors:');
    for (const error of configErrors) {
      const prefix = error.severity === 'error' ? '✗ Error:' : '! Warning:';
      logger.error(`${prefix} ${error.message}`);
    }
    const hasErrors = configErrors.some((e) => e.severity === 'error');
    if (hasErrors) {
      logger.newline();
      logger.error('Please fix configuration errors before continuing.');
      process.exit(2);
    }
    logger.newline();
  }

  // Validate all rule options early (ESLint pattern: fail fast before running validators)
  try {
    validateAllRuleOptions(config);
  } catch (error) {
    if (error instanceof ConfigError) {
      logger.newline();
      logger.error('Configuration error:');
      logger.error(error.message);
      logger.newline();
      logger.error('Please fix your .claudelintrc.json file and try again.');
      process.exit(2);
    }
    throw error; // Re-throw unexpected errors
  }
}

/**
 * Load and validate configuration for commands
 * Follows ESLint pattern: all commands load config by default, --no-config to opt-out
 *
 * For commands that load custom rules, use loadConfig() + validateLoadedConfig()
 * separately so custom rules can register before validation.
 *
 * @param options - Command options (config, verbose, debugConfig)
 * @returns Loaded and validated config object (empty if --no-config)
 */
export function loadAndValidateConfig(options: ConfigLoaderOptions): ClaudeLintConfig {
  const config = loadConfig(options);
  ensureCustomRulesLoaded();
  validateLoadedConfig(config);
  return config;
}
