#!/usr/bin/env node

/**
 * Fabric.OpenclawPlugin installer (modeled on PaddedCell's install.mjs).
 *
 *   node install.mjs --install        build + install + configure
 *   node install.mjs --build-only     build only
 *   node install.mjs --uninstall      remove plugin + config
 *   flags: --skip-check --verbose -v  --openclaw-profile-path <dir>
 */

import { execSync } from 'child_process';
import { existsSync, mkdirSync, copyFileSync, readdirSync, rmSync, chmodSync } from 'fs';
import { dirname, join, resolve } from 'path';
import { fileURLToPath } from 'url';
import { homedir } from 'os';

const __dirname = resolve(dirname(fileURLToPath(import.meta.url)));
const PLUGIN_ID = 'fabric';
const DIST_DIR = join(__dirname, 'dist', 'fabric');

const args = process.argv.slice(2);
const opt = {
  buildOnly: args.includes('--build-only'),
  skipCheck: args.includes('--skip-check'),
  verbose: args.includes('--verbose') || args.includes('-v'),
  uninstall: args.includes('--uninstall'),
};
const pIdx = args.indexOf('--openclaw-profile-path');
const profileOverride = pIdx !== -1 && args[pIdx + 1] ? resolve(args[pIdx + 1]) : null;

function openclawPath() {
  if (profileOverride) return profileOverride;
  if (process.env.OPENCLAW_PATH) return resolve(process.env.OPENCLAW_PATH);
  return join(homedir(), '.openclaw');
}

const c = { reset: '\x1b[0m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m' };
const log = (m, k = 'reset') => console.log(`${c[k]}${m}${c.reset}`);
const step = (n, t, m) => log(`[${n}/${t}] ${m}`, 'cyan');
const ok = (m) => log(`  ✓ ${m}`, 'green');
const warn = (m) => log(`  ⚠ ${m}`, 'yellow');
const err = (m) => log(`  ✗ ${m}`, 'red');

function exec(cmd, o = {}) {
  return execSync(cmd, { cwd: __dirname, stdio: o.silent ? 'pipe' : 'inherit', encoding: 'utf8', ...o });
}
function cfgGet(key, def) {
  try {
    const out = exec(`openclaw config get ${key} --json 2>/dev/null || echo undefined`, { silent: true }).trim();
    return out === 'undefined' || out === '' ? def : JSON.parse(out);
  } catch {
    return def;
  }
}
function cfgSet(key, val) {
  exec(`openclaw config set ${key} '${JSON.stringify(val)}' --json`, { silent: true });
}
function cfgUnset(key) {
  try {
    exec(`openclaw config unset ${key}`, { silent: true });
  } catch {
    /* ignore */
  }
}
function copyDir(src, dest) {
  mkdirSync(dest, { recursive: true });
  for (const e of readdirSync(src, { withFileTypes: true })) {
    if (e.name === 'node_modules') continue;
    const s = join(src, e.name);
    const d = join(dest, e.name);
    e.isDirectory() ? copyDir(s, d) : copyFileSync(s, d);
  }
}

function detect() {
  step(1, 5, 'Detecting environment...');
  let node = null;
  try {
    node = exec('node --version', { silent: true }).trim();
    ok(`Node ${node}`);
  } catch {
    err('Node not found');
  }
  try {
    ok(`openclaw at ${exec('which openclaw', { silent: true }).trim()}`);
  } catch {
    warn('openclaw CLI not in PATH');
  }
  return { node };
}

function checkDeps(env) {
  if (opt.skipCheck) return;
  step(2, 5, 'Checking dependencies...');
  if (!env.node || parseInt(env.node.slice(1), 10) < 18) {
    err('Node 18+ required');
    process.exit(1);
  }
  ok('deps OK');
}

function build() {
  step(3, 5, 'Building plugin...');
  rmSync(join(__dirname, 'dist'), { recursive: true, force: true });
  exec('npm install', { silent: !opt.verbose });
  exec('npm run build', { silent: !opt.verbose });
  if (!existsSync(join(DIST_DIR, 'index.js'))) throw new Error('build produced no dist/fabric/index.js');
  ok('compiled -> dist/fabric');
}

function binTarget(base) {
  return join(base, 'bin', 'fabric-register');
}

function installBinScript(base) {
  const src = join(__dirname, 'bin', 'fabric-register.mjs');
  const dst = binTarget(base);
  mkdirSync(dirname(dst), { recursive: true });
  copyFileSync(src, dst);
  chmodSync(dst, 0o755);
  ok(`fabric-register -> ${dst}`);
}

function clearInstall(base) {
  const dest = join(base, 'plugins', PLUGIN_ID);
  if (existsSync(dest)) {
    rmSync(dest, { recursive: true, force: true });
    ok(`removed ${dest}`);
  }
  const bin = binTarget(base);
  if (existsSync(bin)) {
    rmSync(bin, { force: true });
    ok(`removed ${bin}`);
  }
}
function cleanupConfig(base) {
  const dest = join(base, 'plugins', PLUGIN_ID);
  const allow = cfgGet('plugins.allow', []);
  if (Array.isArray(allow) && allow.includes(PLUGIN_ID)) {
    cfgSet('plugins.allow', allow.filter((x) => x !== PLUGIN_ID));
    ok('removed from plugins.allow');
  }
  const paths = cfgGet('plugins.load.paths', []);
  if (Array.isArray(paths) && paths.includes(dest)) {
    cfgSet('plugins.load.paths', paths.filter((x) => x !== dest));
    ok('removed from plugins.load.paths');
  }
  cfgUnset(`plugins.entries.${PLUGIN_ID}`);
  ok('removed plugin entry');
}

function install() {
  step(4, 5, 'Installing...');
  const base = openclawPath();
  const dest = join(base, 'plugins', PLUGIN_ID);
  log(`  OpenClaw path: ${base}`, 'blue');

  if (existsSync(dest)) {
    warn('existing install -> replacing');
    clearInstall(base);
  }
  mkdirSync(dirname(dest), { recursive: true });
  copyDir(DIST_DIR, dest);
  copyFileSync(join(__dirname, 'openclaw.plugin.json'), join(dest, 'openclaw.plugin.json'));
  copyFileSync(join(__dirname, 'package.json'), join(dest, 'package.json'));
  ok(`plugin files -> ${dest}`);
  exec('npm install --omit=dev', { cwd: dest, silent: !opt.verbose });
  ok('runtime deps installed');
  installBinScript(base);
  return { base, dest };
}

function configure(base, dest) {
  step(5, 5, 'Configuring OpenClaw...');
  const paths = cfgGet('plugins.load.paths', []);
  if (Array.isArray(paths) && !paths.includes(dest)) {
    cfgSet('plugins.load.paths', [...paths, dest]);
  }
  ok(`plugins.load.paths includes ${dest}`);

  const allow = cfgGet('plugins.allow', []);
  if (Array.isArray(allow) && !allow.includes(PLUGIN_ID)) {
    cfgSet('plugins.allow', [...allow, PLUGIN_ID]);
  }
  ok(`plugins.allow includes ${PLUGIN_ID}`);

  if (cfgGet(`plugins.entries.${PLUGIN_ID}.enabled`, undefined) === undefined) {
    cfgSet(`plugins.entries.${PLUGIN_ID}.enabled`, true);
  }
  if (cfgGet('channels.fabric.centerApiBase', undefined) === undefined) {
    cfgSet('channels.fabric.centerApiBase', 'http://localhost:7001/api');
    ok('channels.fabric.centerApiBase = http://localhost:7001/api (default)');
  }
  ok('plugin entry configured (missing defaults only)');
}

function main() {
  console.log('');
  log('Fabric.OpenclawPlugin installer', 'cyan');
  console.log('');
  try {
    const env = detect();
    if (opt.uninstall) {
      const base = openclawPath();
      clearInstall(base);
      cleanupConfig(base);
      log('\nRun: openclaw gateway restart', 'yellow');
      return;
    }
    checkDeps(env);
    build();
    if (opt.buildOnly) {
      log('\nbuild-only — not installed.', 'yellow');
      return;
    }
    const { base, dest } = install();
    configure(base, dest);
    console.log('');
    log('Install complete. Next:', 'blue');
    log('  1. Mint an agent key:  (in Center) node dist/cli.js user apikey --email <agent-email>', 'cyan');
    log('  2. Bind it to an agent (one-time), either:', 'cyan');
    log('       AGENT_ID=<agent> ~/.openclaw/bin/fabric-register --api-key <fak_…>', 'cyan');
    log('       (or pass --agent-id <agent>; or set channels.fabric.accounts.<agent>)', 'cyan');
    log('  3. openclaw gateway restart', 'cyan');
    console.log('');
  } catch (e) {
    log(`\nInstall failed: ${e.message}`, 'red');
    process.exit(1);
  }
}

main();
