#!/usr/bin/env node
/**
 * OpenClaw Config — Hierarchical TOML Configuration System
 *
 * Features (migrated from ECC `ecc2/src/config/mod.rs`):
 *   1. Hierarchical merge: global (~/.openclaw/) → project (./.openclaw/)
 *   2. Profile inheritance: `inherits = "base_profile"` with cycle detection
 *   3. Template variables: `{{placeholder}}` interpolation
 *   4. TOML → JSON export for OpenClaw compatibility
 *
 * Usage:
 *   node src/config/toml-config.js --export          # export merged JSON
 *   node src/config/toml-config.js --migrate          # convert openclaw.json → openclaw.toml
 *   node src/config/toml-config.js --profile dev      # resolve a specific profile
 */

const fs = require('fs');
const path = require('path');
const { parseTOML, stringifyTOML } = require('./toml-parser');

// ─── Constants ───────────────────────────────────────────────────

const GLOBAL_CONFIG_DIR = path.join(process.env.HOME, '.openclaw');
const GLOBAL_TOML = path.join(GLOBAL_CONFIG_DIR, 'openclaw.toml');
const GLOBAL_JSON = path.join(GLOBAL_CONFIG_DIR, 'openclaw.json');
const PROJECT_TOML = '.openclaw.toml';
const MAX_INHERITANCE_DEPTH = 10;

// ─── TOML Loader ─────────────────────────────────────────────────

/**
 * Load and parse a TOML file.
 * Returns parsed object or null.
 */
function loadToml(filePath) {
  try {
    if (!fs.existsSync(filePath)) return null;
    const content = fs.readFileSync(filePath, 'utf8');
    return parseTOML(content);
  } catch (e) {
    console.error(`Failed to parse ${filePath}: ${e.message}`);
    return null;
  }
}

/**
 * Load and parse a JSON file.
 */
function loadJson(filePath) {
  try {
    if (!fs.existsSync(filePath)) return null;
    return JSON.parse(fs.readFileSync(filePath, 'utf8'));
  } catch (e) {
    console.error(`Failed to parse ${filePath}: ${e.message}`);
    return null;
  }
}

// ─── Hierarchical Merge ──────────────────────────────────────────

/**
 * Recursively merge overlay into base.
 * ECC algorithm: merge_toml_values (ecc2/src/config/mod.rs:290-330)
 *
 * Rules:
 *   - Tables: recursive merge (nested keys preserved)
 *   - Arrays: overlay replaces base (not concatenate)
 *   - Scalars: overlay overwrites base
 *   - null overlay: remove from base (unset)
 */
function mergeConfigs(base, overlay) {
  if (!overlay) return base;
  if (!base) return overlay;

  const result = { ...base };

  for (const [key, overlayValue] of Object.entries(overlay)) {
    if (overlayValue === null || overlayValue === undefined) {
      // null overlay → remove key
      delete result[key];
      continue;
    }

    const baseValue = result[key];

    if (isPlainObject(overlayValue) && isPlainObject(baseValue)) {
      // Recursive table merge
      result[key] = mergeConfigs(baseValue, overlayValue);
    } else if (Array.isArray(overlayValue) && Array.isArray(baseValue)) {
      // Array: overlay replaces (ECC behavior)
      result[key] = overlayValue;
    } else {
      // Scalar or type mismatch: overlay wins
      result[key] = overlayValue;
    }
  }

  return result;
}

function isPlainObject(v) {
  return v !== null && typeof v === 'object' && !Array.isArray(v);
}

// ─── Profile Inheritance ─────────────────────────────────────────

/**
 * Resolve a profile by following its inheritance chain.
 * ECC: resolve_agent_profile_inner (ecc2/src/config/mod.rs:240-260)
 *
 * @param {string} profileName - Profile to resolve
 * @param {object} profiles - All profile definitions (from [profiles.*])
 * @param {Set} visited - For cycle detection
 * @returns {object} Resolved profile config (merged up the chain)
 */
function resolveProfile(profileName, profiles, visited = new Set(), depth = 0) {
  if (depth > MAX_INHERITANCE_DEPTH) {
    throw new Error(`Profile inheritance depth exceeded ${MAX_INHERITANCE_DEPTH}: ${profileName}`);
  }

  if (visited.has(profileName)) {
    throw new Error(`Circular profile inheritance detected: ${[...visited, profileName].join(' → ')}`);
  }

  const profile = profiles[profileName];
  if (!profile) {
    throw new Error(`Profile not found: ${profileName}`);
  }

  visited.add(profileName);

  // Resolve base profile first
  let baseConfig = {};
  if (profile.inherits) {
    const baseName = profile.inherits;
    // Support multiple inheritance: inherits = ["base1", "base2"]
    const baseNames = Array.isArray(baseName) ? baseName : [baseName];
    for (const name of baseNames) {
      const base = resolveProfile(name, profiles, new Set(visited), depth + 1);
      baseConfig = mergeConfigs(baseConfig, base);
    }
  }

  // Merge current profile on top (without 'inherits' key)
  const { inherits, ...currentConfig } = profile;

  return mergeConfigs(baseConfig, currentConfig);
}

// ─── Template Interpolation ──────────────────────────────────────

/**
 * Replace {{placeholder}} variables in strings.
 * ECC: interpolate_required_string (ecc2/src/config/mod.rs:340-370)
 *
 * Variables come from:
 *   1. Explicit `vars` map
 *   2. Environment variables (ENV_ prefix stripped)
 *   3. Built-in variables (cwd, home, hostname, etc.)
 */
const PLACEHOLDER_RE = /\{\{\s*([A-Za-z0-9_-]+)\s*\}\}/g;

function interpolate(value, vars = {}) {
  if (typeof value === 'string') {
    return value.replace(PLACEHOLDER_RE, (_, name) => {
      // Check explicit vars first
      if (name in vars) return String(vars[name]);

      // Check built-in vars
      switch (name) {
        case 'HOME': case 'home': return process.env.HOME || '';
        case 'CWD':  case 'cwd':  return process.cwd();
        case 'HOST': case 'hostname': return require('os').hostname();
        case 'USER': return process.env.USER || '';
        default:
          // Check env var
          if (name.startsWith('ENV_')) {
            return process.env[name.slice(4)] || '';
          }
          if (name in process.env) return process.env[name];

          // Unknown — leave as-is with warning
          console.error(`Warning: unknown template variable: {{${name}}}`);
          return `{{${name}}}`;
      }
    });
  }

  if (Array.isArray(value)) {
    return value.map(v => interpolate(v, vars));
  }

  if (isPlainObject(value)) {
    const result = {};
    for (const [k, v] of Object.entries(value)) {
      result[k] = interpolate(v, vars);
    }
    return result;
  }

  return value;
}

// ─── Full Config Loader ──────────────────────────────────────────

/**
 * Load full configuration from the hierarchy.
 *
 * Load order:
 *   1. Global openclaw.toml (or fallback to openclaw.json)
 *   2. Project .openclaw.toml (merged on top)
 *   3. Resolve profiles if [profiles] section exists
 *   4. Template interpolation
 *
 * @param {object} options - { projectDir, profile, vars }
 * @returns {object} Merged and resolved config
 */
function loadConfig(options = {}) {
  const { projectDir, profile, vars = {} } = options;

  // 1. Load global config (TOML preferred, JSON fallback)
  let config = loadToml(GLOBAL_TOML) || loadJson(GLOBAL_JSON) || {};

  // 2. Load project config if available
  if (projectDir) {
    const projectConfigPath = path.join(projectDir, PROJECT_TOML);
    const projectConfig = loadToml(projectConfigPath);
    if (projectConfig) {
      config = mergeConfigs(config, projectConfig);
    }
  }

  // 3. Profile resolution
  if (config.profiles) {
    const profiles = config.profiles;
    const activeProfile = profile || config.default_profile;

    if (activeProfile && profiles[activeProfile]) {
      const resolved = resolveProfile(activeProfile, profiles);
      // Merge resolved profile into root (profile keys override)
      config = mergeConfigs(config, resolved);
    }
  }

  // 4. Template interpolation
  config = interpolate(config, vars);

  return config;
}

// ─── Migration: JSON → TOML ──────────────────────────────────────

/**
 * Convert existing openclaw.json → openclaw.toml
 */
function migrateJsonToToml() {
  if (!fs.existsSync(GLOBAL_JSON)) {
    console.error('No openclaw.json found at', GLOBAL_JSON);
    return false;
  }

  const jsonConfig = loadJson(GLOBAL_JSON);
  if (!jsonConfig) return false;

  // Convert to TOML
  const tomlContent = jsonToToml(jsonConfig);

  // Write
  fs.writeFileSync(GLOBAL_TOML, tomlContent, 'utf8');

  console.log(`Migrated: ${GLOBAL_JSON} → ${GLOBAL_TOML}`);
  console.log(`Backup: ${GLOBAL_JSON} preserved`);
  return true;
}

/**
 * Simple JSON → TOML converter (handles nested structures)
 */
function jsonToToml(obj, prefix = '') {
  const lines = [];

  for (const [key, value] of Object.entries(obj)) {
    const fullKey = prefix ? `${prefix}.${key}` : key;

    if (isPlainObject(value)) {
      // Section header for nested objects
      if (Object.keys(value).length > 0) {
        lines.push(`\n[${fullKey}]`);
        for (const [subKey, subValue] of Object.entries(value)) {
          lines.push(formatTomlValue(subKey, subValue));
        }
      }
    } else if (Array.isArray(value)) {
      if (value.every(v => isPlainObject(v))) {
        // Array of tables
        for (const item of value) {
          lines.push(`\n[[${fullKey}]]`);
          for (const [subKey, subValue] of Object.entries(item)) {
            lines.push(formatTomlValue(subKey, subValue));
          }
        }
      } else {
        // Simple array
        lines.push(`${key} = ${JSON.stringify(value)}`);
      }
    } else {
      lines.push(formatTomlValue(key, value));
    }
  }

  return `# OpenClaw Configuration (migrated from openclaw.json)\n# Generated: ${new Date().toISOString()}\n` + lines.join('\n');
}

function formatTomlValue(key, value) {
  if (typeof value === 'string') {
    // Escape special chars
    const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
    return `${key} = "${escaped}"`;
  }
  if (typeof value === 'boolean') return `${key} = ${value}`;
  if (typeof value === 'number') return `${key} = ${value}`;
  if (value === null) return `# ${key} = null (omitted)`;
  return `${key} = ${JSON.stringify(value)}`;
}

// ─── CLI ─────────────────────────────────────────────────────────

function main() {
  const args = process.argv.slice(2);

  if (args.includes('--help') || args.includes('-h')) {
    console.log(`OpenClaw Config — Hierarchical TOML Configuration

Usage:
  node toml-config.js --export             Export merged config as JSON
  node toml-config.js --migrate            Convert openclaw.json → openclaw.toml
  node toml-config.js --profile <name>     Load with specific profile
  node toml-config.js --project <path>     Include project-level config
  node toml-config.js --var KEY=VALUE      Template variables

Example:
  node toml-config.js --profile dev --export
  node toml-config.js --migrate`);
    return;
  }

  if (args.includes('--migrate')) {
    migrateJsonToToml();
    return;
  }

  const profileIdx = args.indexOf('--profile');
  const profile = profileIdx >= 0 ? args[profileIdx + 1] : null;

  const projectIdx = args.indexOf('--project');
  const projectDir = projectIdx >= 0 ? args[projectIdx + 1] : process.cwd();

  // Parse --var KEY=VALUE
  const vars = {};
  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--var' && i + 1 < args.length) {
      const [k, v] = args[i + 1].split('=');
      vars[k] = v;
    }
  }

  try {
    const config = loadConfig({ projectDir, profile, vars });
    console.log(JSON.stringify(config, null, 2));
  } catch (e) {
    console.error(`Config error: ${e.message}`);
    process.exit(1);
  }
}

// ─── Module exports ──────────────────────────────────────────────

module.exports = {
  loadConfig,
  loadToml,
  mergeConfigs,
  resolveProfile,
  interpolate,
  migrateJsonToToml,
};

if (require.main === module) {
  main();
}
