const GitUtils = require('../utils/git');
const SymlinkUtils = require('../utils/symlinks');
const UI = require('../utils/ui');
const State = require('../utils/state');
const Hooks = require('../utils/hooks');

async function add(repo, options) {
  const git = new GitUtils();
  const symlinks = new SymlinkUtils();
  // Non-interactive flag honored on non-destructive prompts only. The
  // "Remove and reinstall?" prompt (line 40) stays interactive even with
  // --yes — that path requires explicit user intent, not flag inheritance.
  // Callers that have already established intent (e.g. `update --reset`)
  // rm -rf `.framework/` themselves, so `git.frameworkExists()` is false
  // here and the destructive prompt is never reached.
  const nonInteractive = options && options.yes === true;
  // When `add` is invoked programmatically (e.g. `update --reset`) the caller
  // wants failures to bubble up so it can roll back, instead of add()'s default
  // `process.exit(1)` which would kill the parent before it can recover.
  const throwOnError = options && options.throwOnError === true;

  // Repo resolution safety net. The CLI entrypoint (bin/baldart.js) defaults
  // `repo` to the upstream before calling here, but programmatic callers may
  // pass undefined. Resolve from the consumer's ledger (preserves forks) so
  // we never reach git.addSubtree() with an undefined repo (the crash that
  // broke `update --reset`: "Cannot read properties of undefined").
  if (!repo) repo = State.resolveRepo();
  if (!repo) {
    UI.error('No framework repository to install from.');
    UI.info('Expected from .baldart/state.json → framework_repo. Recover with:');
    UI.info('  npx baldart add <owner/repo> --yes');
    if (throwOnError) throw new Error('add: repository unresolved (state.json framework_repo empty)');
    process.exit(1);
  }

  try {
    // Step 1: Verify environment
    UI.header('STEP 1/5: Verify Environment');

    const spinner = UI.spinner('Checking Git repository...').start();

    const isRepo = await git.isGitRepo();
    if (!isRepo) {
      spinner.fail();
      UI.error('Not a Git repository!');
      UI.newline();
      UI.box('ERROR', [
        'BALDART requires your project to be a Git repository.',
        '',
        'Solution:',
        '  1. Initialize Git: git init',
        '  2. Or navigate to your project directory'
      ]);
      process.exit(1);
    }

    spinner.succeed('Git repository verified');

    // Step 2: Check for existing framework
    UI.header('STEP 2/5: Check Existing Installation');

    const exists = await git.frameworkExists();
    if (exists) {
      UI.warning('Framework already installed!');
      // A non-interactive `add --yes` must never block on (or auto-confirm) the
      // destructive "Remove and reinstall?" prompt. Reinstalling is an explicit
      // user decision routed through `update --reset` (which rm -rf's
      // `.framework/` itself, so this branch is never reached on that path).
      // Fail fast with a clear pointer instead of a generic catch below.
      if (nonInteractive) {
        UI.error('.framework/ already present — a non-interactive reinstall must go through `npx baldart update --reset --yes --i-know`.');
        process.exit(1);
      }
      const shouldReinstall = await UI.confirm(
        'Remove and reinstall? (You will lose customizations)',
        false
      );

      if (!shouldReinstall) {
        UI.info('Installation cancelled. Use "baldart update" to update.');
        process.exit(0);
      }

      UI.warning('Removing existing framework...');
      const fs = require('fs');
      fs.rmSync('.framework', { recursive: true, force: true });
    }

    UI.success('Ready for installation');

    // Step 3: Explain what will be installed
    UI.header('STEP 3/5: Installation Overview');

    UI.box('WHAT WILL BE INSTALLED', [
      '• Core Protocol: AGENTS.md (coordination rules)',
      '• 17 Agent Modules: architecture, workflows, testing...',
      '• 9 AI Agents: coder, code-reviewer, doc-reviewer...',
      '• 3 Commands: /new, /design-review, /issue-review',
      '• Templates: backlog cards, specs, UI guidelines',
      '• Pre-commit hooks (customizable)',
      '',
      'Framework will be installed in .framework/ directory',
      'Symlinks created for auto-updateable files',
      'Templates copied for customization'
    ]);

    const proceed = nonInteractive
      ? true
      : await UI.confirm('Install framework?', true);
    if (!proceed) {
      UI.info('Installation cancelled');
      process.exit(0);
    }

    // Step 4: Download framework
    UI.header('STEP 4/5: Download Framework');

    // `.framework/` MUST be trackable — `git subtree add` itself fails if a
    // pre-existing `.gitignore` rule matches the path. Heal proactively.
    // (since v3.14.0)
    try {
      const heal = await git.ensureFrameworkNotIgnored();
      if (heal.wasFixed) {
        UI.warning(`.framework/ was ignored by ${heal.source || 'a gitignore'} — auto-healed before subtree add.`);
        if (heal.removedLines > 0) {
          UI.info(`  Removed ${heal.removedLines} stale .framework line(s) from .gitignore.`);
        }
        if (heal.appendedNegation) {
          UI.info('  Appended `!.framework` negation block to .gitignore (idempotent).');
        }
        if (heal.stillIgnored) {
          UI.error('.framework/ is STILL ignored after auto-heal — install would fail.');
          UI.info(`Source: ${heal.recheck.source}:${heal.recheck.line} (${heal.recheck.pattern})`);
          UI.info('Resolve by removing that rule, then re-run `baldart add`.');
          // Deliberate abort — flag it so the local catch propagates instead of
          // downgrading to a warning, and let the single outer catch decide
          // throw (throwOnError) vs process.exit (CLI).
          const e = new Error('.framework/ still ignored after auto-heal');
          e.fatalInstall = true;
          throw e;
        }
      }
    } catch (err) {
      if (err && err.fatalInstall) throw err;
      UI.warning(`Could not check .gitignore for .framework: ${err.message}`);
    }

    const downloadSpinner = UI.spinner(`Downloading from ${repo}...`).start();

    try {
      await git.addSubtree(repo, options.branch);
      downloadSpinner.succeed('Framework downloaded');

      const version = await git.getFrameworkVersion();
      UI.success(`Version installed: ${version}`);
      try { State.recordInstall({ version, repo }); } catch (_) { /* non-fatal */ }
    } catch (error) {
      downloadSpinner.fail();
      UI.error('Failed to download framework!');
      UI.newline();
      UI.box('ERROR', [
        'Possible causes:',
        '  • No internet connection',
        '  • Repository not accessible',
        `  • Invalid repository: ${repo}`,
        '    (resolved from .baldart/state.json → framework_repo, else default)',
        '',
        'Solution:',
        '  • Check your internet connection',
        '  • Verify the repository exists / is reachable',
        '  • Recover with an explicit repo: npx baldart add <owner/repo> --yes'
      ]);
      throw error;
    }

    // Step 5: Setup structure
    UI.header('STEP 5/5: Setup Project Structure');

    UI.info('Creating directories...');
    symlinks.ensureDirectory('.claude');
    symlinks.ensureDirectory('docs/references');
    symlinks.ensureDirectory('templates');
    symlinks.ensureDirectory('backlog');
    UI.success('Directories created');

    UI.newline();
    // First-time install: 'safe' mode preserves any pre-existing user
    // customisations without prompting (no destructive backups on fresh
    // install paths). Per-skill merge runs for every enabled tool — by
    // default `['claude']`, plus `'codex'` if `~/.codex/` is present on
    // the user's machine (autodetected). Run `npx baldart configure` to
    // change the selection.
    const toolAdapters = require('../utils/tool-adapters');
    const enabledTools = toolAdapters.defaultEnabled();
    await symlinks.createAllSymlinks({ mode: 'safe', tools: enabledTools });

    UI.newline();
    symlinks.copyCustomizableFiles();

    // BALDART hooks (multi-hook registry since v3.18.0)
    // Auto-register every hook in src/utils/hooks.js HOOK_REGISTRY into
    // .claude/settings.json. Drift on an existing entry triggers an
    // interactive prompt (Keep / Replace / Show diff), or 'keep' in --yes mode.
    //
    // This runs BEFORE the optional prompts below (git aliases, configure) —
    // hook registration determines the correctness of the install and must
    // never sit behind a blocking prompt. If `add` is interrupted at an
    // optional prompt (non-TTY, Ctrl-C), the hooks are already in place.
    // (Reordered in v3.33.0 to close the silent partial-install gap.)
    try {
      const res = await Hooks.registerAll(process.cwd(), {
        onDrift: Hooks.createDriftPrompt(UI, { autoYes: nonInteractive }),
      });
      if (res.malformed) {
        UI.warning('.claude/settings.json is malformed; skipped hook registration. Run `npx baldart doctor` after fixing it.');
      } else {
        for (const r of res.results) {
          if (r.status === 'created') UI.success(`Registered hook \`${r.id}\` in .claude/settings.json`);
          else if (r.status === 'legacy-migrated') UI.success(`Migrated legacy hook to \`${r.id}\` (previous: ${r.previousCommand})`);
          else if (r.status === 'drift-replaced') UI.success(`Replaced drifted hook \`${r.id}\` (was: ${r.previousCommand})`);
          else if (r.status === 'drift-kept') UI.info(`Kept user-customized hook \`${r.id}\` as-is.`);
          else if (r.status === 'already') UI.info(`Hook \`${r.id}\` already registered.`);
        }
      }
    } catch (err) {
      UI.warning(`Hook registration skipped: ${err.message}`);
    }

    // Configure Git aliases
    UI.section('Configuring Git Aliases');

    const configureAliases = nonInteractive
      ? true
      : await UI.confirm('Configure git aliases (fw-version, fw-update, fw-push)?', true);

    if (configureAliases) {
      // simple-git >=3.21 rejects shell-command aliases (prefix `!`) unless
      // `allowUnsafeAlias` is set in its constructor — and even that is
      // version-gated. Spawning `git config` directly bypasses the safety
      // check (which is a simple-git policy, not a git policy: the user is
      // explicitly opting in to these aliases here). Any failure here is
      // cosmetic (the framework is fully installed without aliases), so we
      // warn and continue instead of bubbling up to the global catch — that
      // surfaced as "✗ Installation failed: Configuring alias is not
      // permitted without enabling allowUnsafeAlias" while the install was
      // actually fine.
      const { spawnSync } = require('child_process');
      const aliases = [
        ['alias.fw-version', '!cat .framework/VERSION'],
        ['alias.fw-update', '!npx baldart update'],
        ['alias.fw-push', '!npx baldart push'],
      ];
      let aliasFailures = 0;
      for (const [key, value] of aliases) {
        const r = spawnSync('git', ['config', key, value], { stdio: 'pipe' });
        if (r.status !== 0) {
          aliasFailures += 1;
        }
      }
      if (aliasFailures === 0) {
        UI.success('Git aliases configured');
      } else {
        UI.warning(`${aliasFailures}/${aliases.length} git alias(es) could not be configured (non-blocking — install continues).`);
      }
    }

    // Routines wizard (since v2.1.0)
    try {
      const routinesCmd = require('./routines');
      await routinesCmd.postUpdateWizard();
    } catch (err) {
      UI.warning(`Routines wizard skipped: ${err.message}`);
    }

    // Project configuration (since v3.0.0)
    // Skills now resolve project-specific facts from baldart.config.yml
    // instead of hard-coding paths. Configure prompts for these on install.
    UI.newline();
    const runConfigure = nonInteractive
      ? true
      : await UI.confirm(
          'Configure project context now (paths, identity, stack, feature flags)?',
          true
        );
    if (runConfigure) {
      try {
        const configureCmd = require('./configure');
        // In --yes mode, configure must write autodetected values without
        // prompting — otherwise its inquirer prompts block in non-TTY.
        await configureCmd(nonInteractive ? { nonInteractive: true } : undefined);
      } catch (err) {
        UI.warning(`Configure step failed: ${err.message}`);
        UI.info('Run `npx baldart configure` later to complete setup.');
      }
    } else {
      UI.info('Skipped. Run `npx baldart configure` before invoking skills.');
    }

    // Post-flight assert — never declare success with missing hooks.
    // Re-reads .claude/settings.json and confirms every active hook in
    // HOOK_REGISTRY is present. Guards against the silent partial-install
    // class (v3.27.1 / mayo 3.28→3.31). Covers `update --reset` too, since
    // that path delegates to add(undefined, { yes: true }).
    let verify = Hooks.verifyAll(process.cwd());
    if (!verify.ok && !verify.malformed) {
      // A single best-effort re-registration before failing hard.
      try {
        await Hooks.registerAll(process.cwd(), {
          onDrift: Hooks.createDriftPrompt(UI, { autoYes: nonInteractive }),
        });
      } catch (_) { /* fall through to the assert below */ }
      verify = Hooks.verifyAll(process.cwd());
    }
    if (!verify.ok) {
      UI.error(
        verify.malformed
          ? '.claude/settings.json is malformed — cannot verify hooks. Install is NOT complete.'
          : `Hooks missing after install: ${verify.missing.join(', ')}. Install is NOT complete.`
      );
      UI.info('Run `npx baldart doctor` to repair, then re-check `npx baldart status`.');
      if (throwOnError) {
        throw new Error(
          verify.malformed
            ? 'settings.json malformed — hooks unverifiable'
            : `hooks missing after install: ${verify.missing.join(', ')}`
        );
      }
      process.exit(1);
    }

    // Success summary
    UI.header('✓ INSTALLATION COMPLETE');

    UI.box('NEXT STEPS', [
      '1. Customize pre-commit hook:',
      '   • Edit .claude/hooks/lint-before-commit.sh.template',
      '   • Rename to lint-before-commit.sh',
      '   • Make executable: chmod +x .claude/hooks/lint-before-commit.sh',
      '',
      '2. Two editable templates landed at docs/references/ (untracked):',
      '   • ui-guidelines.template.md  — UI design language template.',
      '     Edit and rename to ui-guidelines.md if you adopt it.',
      '   • brand-guidelines.md        — Brand voice & visual identity.',
      '     Edit to taste, or delete if your project does not need one.',
      '   These are starting points. Commit them to track your project\'s',
      '   design language, or add them to .gitignore if you don\'t need them.',
      '',
      '3. Create backlog cards:',
      '   • Copy templates/feature-card.template.yml',
      '   • Use /new command to implement',
      '',
      '4. Review scheduled routines:',
      '   • npx baldart routines list',
      '   • Install missing ones to drive auto-learning loops',
      '',
      '5. Read documentation:',
      '   • cat AGENTS.md'
    ]);

    UI.newline();
    UI.section('Available Commands');
    UI.list([
      'baldart version  - Show framework version',
      'baldart update   - Update framework',
      'baldart push     - Contribute improvements',
      'baldart status   - Check installation status'
    ], 'cyan');

    UI.newline();
    UI.success('Framework ready to use!');

  } catch (error) {
    UI.error(`Installation failed: ${error.message}`);
    // Programmatic callers (update --reset) need the failure to bubble so they
    // can roll back; the CLI entrypoint keeps the historical exit-1 behavior.
    if (throwOnError) throw error;
    process.exit(1);
  }
}

module.exports = add;
