import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
import { execSync, spawnSync } from 'node:child_process';
import { access, readdir, readFile, mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path';

class GoBack extends Error { constructor() { super('GoBack'); } }

async function writeMcpSettings(cwd) {
  const settingsPath = path.join(cwd, '.claude', 'settings.json');
  let existing = {};
  try { existing = JSON.parse(await readFile(settingsPath, 'utf8')); } catch {}
  if (existing.mcpServers?.['chrome-devtools']) return;
  const updated = {
    ...existing,
    mcpServers: {
      ...(existing.mcpServers || {}),
      'chrome-devtools': { command: 'npx', args: ['-y', 'chrome-devtools-mcp', '--browserUrl', 'http://127.0.0.1:9222'] }
    }
  };
  await mkdir(path.dirname(settingsPath), { recursive: true });
  await writeFile(settingsPath, JSON.stringify(updated, null, 2), 'utf8');
}

const c = {
  cyan:   (s) => `\x1b[36m${s}\x1b[0m`,
  green:  (s) => `\x1b[32m${s}\x1b[0m`,
  yellow: (s) => `\x1b[33m${s}\x1b[0m`,
  blue:   (s) => `\x1b[34m${s}\x1b[0m`,
  bold:   (s) => `\x1b[1m${s}\x1b[0m`,
  dim:    (s) => `\x1b[2m${s}\x1b[0m`,
};

const AGENTS = [
  { value: 'claude-code',       label: 'Claude Code',              desc: 'Terminal da Anthropic' },
  { value: 'claude-code-agent', label: 'Claude Code com Agentes',  desc: 'Claude Code + sub-agentes autônomos' },
  { value: 'cursor',            label: 'Cursor IDE',               desc: 'Editor com IA integrada' },
  { value: 'windsurf',          label: 'Windsurf IDE',             desc: 'Editor com IA integrada' },
  { value: 'github-copilot',    label: 'GitHub Copilot',           desc: 'VS Code com GitHub Copilot' },
  { value: 'gemini-cli',        label: 'Gemini CLI',               desc: 'Terminal do Google' },
  { value: 'codex',             label: 'Codex CLI',                desc: 'Terminal da OpenAI' },
  { value: 'qwen-code',         label: 'Qwen Code',                desc: 'Terminal da Alibaba' },
];

const OVERWRITE_OPTIONS = [
  { value: 'prompt', label: 'Perguntar para cada arquivo', desc: 'Decide caso a caso (recomendado)' },
  { value: 'skip',   label: 'Manter arquivos existentes',  desc: 'Nunca substitui o que já existe' },
  { value: 'force',  label: 'Substituir tudo',             desc: 'Sobrescreve sem perguntar' },
];

const MAIN_MENU = [
  { value: 'web',          label: 'Abrir interface web',                     desc: 'Abre o painel no navegador — mais fácil para o time de negócio' },
  { value: 'install',      label: 'Instalar SDD no meu projeto',             desc: 'Configura os comandos no seu editor/terminal de IA' },
  { value: 'wizard',       label: 'Planejar nova funcionalidade',             desc: 'Guia passo a passo para criar e acompanhar uma funcionalidade' },
  { value: 'dashboard',    label: 'Ver minhas funcionalidades',               desc: 'Painel com status, progresso e próximos passos de cada feature' },
  { value: 'ado',          label: 'Configurar integração ADO',                desc: 'Gera token PAT e configura o Azure DevOps no projeto' },
  { value: 'instructions', label: 'Ver como usar o SDD (guia completo)',      desc: 'Referência rápida com exemplos reais e ADO' },
  { value: 'exit',         label: 'Sair' },
];

// Command syntax per agent — used by the wizard to show the correct commands
const AGENT_COMMANDS = {
  'claude-code': {
    label: 'Claude Code',
    open: { cmd: 'claude', note: 'Rode no terminal, dentro da pasta do seu projeto.' },
    init:          (f) => `/sdd:spec-init ${f}`,
    requirements:  (s) => `/sdd:spec-requirements ${s}`,
    design:        (s) => `/sdd:spec-design ${s}`,
    tasks:         (s) => `/sdd:spec-tasks ${s}`,
    impl:          (s) => `/sdd:spec-impl ${s}`,
    validateGap:   (s) => `/sdd:validate-gap ${s}`,
    validateDesign:(s) => `/sdd:validate-design ${s}`,
    validateImpl:  (s) => `/sdd:validate-impl ${s}`,
    steering:            '/sdd:steering',
    steeringAdo:         '/sdd:steering-custom Integração com Azure DevOps',
  },
  'claude-code-agent': {
    label: 'Claude Code (Agentes)',
    open: { cmd: 'claude', note: 'Rode no terminal, dentro da pasta do seu projeto.' },
    init:          (f) => `/sdd:spec-init ${f}`,
    requirements:  (s) => `/sdd:spec-requirements ${s}`,
    design:        (s) => `/sdd:spec-design ${s}`,
    tasks:         (s) => `/sdd:spec-tasks ${s}`,
    impl:          (s) => `/sdd:spec-impl ${s}`,
    quick:         (f) => `/sdd:spec-quick ${f}`,
    validateGap:   (s) => `/sdd:validate-gap ${s}`,
    validateDesign:(s) => `/sdd:validate-design ${s}`,
    validateImpl:  (s) => `/sdd:validate-impl ${s}`,
    steering:            '/sdd:steering',
    steeringAdo:         '/sdd:steering-custom Integração com Azure DevOps',
  },
  cursor: {
    label: 'Cursor IDE',
    open: { cmd: null, note: 'Abra o Cursor IDE na pasta do projeto.' },
    init:          (f) => `/sdd/spec-init ${f}`,
    requirements:  (s) => `/sdd/spec-requirements ${s}`,
    design:        (s) => `/sdd/spec-design ${s}`,
    tasks:         (s) => `/sdd/spec-tasks ${s}`,
    impl:          (s) => `/sdd/spec-impl ${s}`,
    validateGap:   (s) => `/sdd/validate-gap ${s}`,
    validateDesign:(s) => `/sdd/validate-design ${s}`,
    validateImpl:  (s) => `/sdd/validate-impl ${s}`,
    steering:            '/sdd/steering',
    steeringAdo:         '/sdd/steering-custom Integração com Azure DevOps',
  },
  windsurf: {
    label: 'Windsurf IDE',
    open: { cmd: null, note: 'Abra o Windsurf IDE na pasta do projeto.' },
    init:          (f) => `/sdd-spec-init ${f}`,
    requirements:  (s) => `/sdd-spec-requirements ${s}`,
    design:        (s) => `/sdd-spec-design ${s}`,
    tasks:         (s) => `/sdd-spec-tasks ${s}`,
    impl:          (s) => `/sdd-spec-impl ${s}`,
    validateGap:   (s) => `/sdd-validate-gap ${s}`,
    validateDesign:(s) => `/sdd-validate-design ${s}`,
    validateImpl:  (s) => `/sdd-validate-impl ${s}`,
    steering:            '/sdd-steering',
    steeringAdo:         '/sdd-steering-custom Integração com Azure DevOps',
  },
  'github-copilot': {
    label: 'GitHub Copilot',
    open: { cmd: null, note: 'Abra o VS Code na pasta do projeto com GitHub Copilot ativo.' },
    init:          (f) => `/sdd-spec-init ${f}`,
    requirements:  (s) => `/sdd-spec-requirements ${s}`,
    design:        (s) => `/sdd-spec-design ${s}`,
    tasks:         (s) => `/sdd-spec-tasks ${s}`,
    impl:          (s) => `/sdd-spec-impl ${s}`,
    validateGap:   (s) => `/sdd-validate-gap ${s}`,
    validateDesign:(s) => `/sdd-validate-design ${s}`,
    validateImpl:  (s) => `/sdd-validate-impl ${s}`,
    steering:            '/sdd-steering',
    steeringAdo:         '/sdd-steering-custom Integração com Azure DevOps',
  },
  'gemini-cli': {
    label: 'Gemini CLI',
    open: { cmd: 'gemini', note: 'Rode no terminal, dentro da pasta do seu projeto.' },
    init:          (f) => `/sdd:spec-init ${f}`,
    requirements:  (s) => `/sdd:spec-requirements ${s}`,
    design:        (s) => `/sdd:spec-design ${s}`,
    tasks:         (s) => `/sdd:spec-tasks ${s}`,
    impl:          (s) => `/sdd:spec-impl ${s}`,
    validateGap:   (s) => `/sdd:validate-gap ${s}`,
    validateDesign:(s) => `/sdd:validate-design ${s}`,
    validateImpl:  (s) => `/sdd:validate-impl ${s}`,
    steering:            '/sdd:steering',
    steeringAdo:         '/sdd:steering-custom Integração com Azure DevOps',
  },
  codex: {
    label: 'Codex CLI',
    open: { cmd: 'codex', note: 'Rode no terminal, dentro da pasta do seu projeto.' },
    init:          (f) => `/prompts:sdd-spec-init ${f}`,
    requirements:  (s) => `/prompts:sdd-spec-requirements ${s}`,
    design:        (s) => `/prompts:sdd-spec-design ${s}`,
    tasks:         (s) => `/prompts:sdd-spec-tasks ${s}`,
    impl:          (s) => `/prompts:sdd-spec-impl ${s}`,
    validateGap:   (s) => `/prompts:sdd-validate-gap ${s}`,
    validateDesign:(s) => `/prompts:sdd-validate-design ${s}`,
    validateImpl:  (s) => `/prompts:sdd-validate-impl ${s}`,
    steering:            '/prompts:sdd-steering',
    steeringAdo:         '/prompts:sdd-steering-custom Integração com Azure DevOps',
  },
  'qwen-code': {
    label: 'Qwen Code',
    open: { cmd: 'qwen', note: 'Rode no terminal, dentro da pasta do seu projeto.' },
    init:          (f) => `/sdd:spec-init ${f}`,
    requirements:  (s) => `/sdd:spec-requirements ${s}`,
    design:        (s) => `/sdd:spec-design ${s}`,
    tasks:         (s) => `/sdd:spec-tasks ${s}`,
    impl:          (s) => `/sdd:spec-impl ${s}`,
    validateGap:   (s) => `/sdd:validate-gap ${s}`,
    validateDesign:(s) => `/sdd:validate-design ${s}`,
    validateImpl:  (s) => `/sdd:validate-impl ${s}`,
    steering:            '/sdd:steering',
    steeringAdo:         '/sdd:steering-custom Integração com Azure DevOps',
  },
};

// ─── Helpers ────────────────────────────────────────────────────────────────

// showBack=false on the main menu (it has "Sair" instead); true everywhere else.
// Typing '0' or 'b' throws GoBack so any caller can propagate it up.
async function select(rl, title, options, defaultIndex = 0, showBack = true) {
  output.write(`\n${c.bold(title)}\n\n`);
  options.forEach((opt, i) => {
    const num = c.cyan(`${i + 1}.`);
    const desc = opt.desc ? c.dim(`  — ${opt.desc}`) : '';
    output.write(`   ${num} ${opt.label}${desc}\n`);
  });
  if (showBack) output.write(`\n   ${c.dim('0.')} ${c.dim('← Voltar')}\n`);
  output.write('\n');
  while (true) {
    const range = showBack ? `0-${options.length}` : `1-${options.length}`;
    const ans = await rl.question(`Escolha [${range}] (Enter para ${defaultIndex + 1}): `);
    const trimmed = ans.trim();
    if (showBack && (trimmed === '0' || trimmed.toLowerCase() === 'b')) throw new GoBack();
    if (!trimmed) return options[defaultIndex].value;
    const n = parseInt(trimmed, 10);
    if (!isNaN(n) && n >= 1 && n <= options.length) return options[n - 1].value;
    output.write(c.yellow(`Por favor, escolha entre ${showBack ? '0' : '1'} e ${options.length}.\n`));
  }
}

function hr() {
  output.write(`${c.dim('─────────────────────────────────────────────')}\n`);
}

// ─── Portfolio helpers ───────────────────────────────────────────────────────

const SDD_DIR = '.sdd';

async function loadPortfolio() {
  try {
    const raw = await readFile(path.join(process.cwd(), SDD_DIR, 'portfolio.json'), 'utf8');
    return JSON.parse(raw);
  } catch {
    return { epics: [] };
  }
}

async function savePortfolio(portfolio) {
  try {
    const dir = path.join(process.cwd(), SDD_DIR);
    await mkdir(dir, { recursive: true });
    await writeFile(path.join(dir, 'portfolio.json'), JSON.stringify(portfolio, null, 2), 'utf8');
  } catch { /* ignore — non-fatal */ }
}

async function addFeatureToPortfolio(specName, featureName, epicName) {
  if (!epicName) return;
  const portfolio = await loadPortfolio();
  const epic = portfolio.epics.find(e => e.name === epicName);
  if (epic) {
    if (!epic.features.includes(specName)) epic.features.push(specName);
    epic.featureNames = epic.featureNames ?? {};
    epic.featureNames[specName] = featureName;
  } else {
    portfolio.epics.push({ name: epicName, features: [specName], featureNames: { [specName]: featureName } });
  }
  await savePortfolio(portfolio);
}

async function saveBrief(specName, content) {
  try {
    const dir = path.join(process.cwd(), SDD_DIR, 'specs', specName);
    await mkdir(dir, { recursive: true });
    await writeFile(path.join(dir, 'brief.md'), content, 'utf8');
  } catch { /* non-fatal */ }
}

// ─── Spec scanning helpers ───────────────────────────────────────────────────

function getPhaseInfo(spec, specName, cmd) {
  const a = spec.approvals ?? {};
  const req = a.requirements ?? {};
  const des = a.design ?? {};
  const tsk = a.tasks ?? {};
  if (tsk.approved || spec.ready_for_implementation) {
    return { label: c.green('Pronta para implementar'), nextCmd: cmd.impl(specName) };
  }
  if (tsk.generated && !tsk.approved) {
    return { label: c.yellow('Revise as tarefas'), nextCmd: null };
  }
  if (des.approved && !tsk.generated) {
    return { label: c.blue('Gerar tarefas'), nextCmd: cmd.tasks(specName) };
  }
  if (des.generated && !des.approved) {
    return { label: c.yellow('Revise o design'), nextCmd: null };
  }
  if (req.approved && !des.generated) {
    return { label: c.blue('Gerar design'), nextCmd: cmd.design(specName) };
  }
  if (req.generated && !req.approved) {
    return { label: c.yellow('Revise os requisitos'), nextCmd: null };
  }
  return { label: c.dim('Gerar requisitos'), nextCmd: cmd.requirements(specName) };
}

async function readTaskProgress(specName) {
  try {
    const content = await readFile(
      path.join(process.cwd(), SDD_DIR, 'specs', specName, 'tasks.md'), 'utf8',
    );
    const done  = (content.match(/- \[x\]/gi) ?? []).length;
    const total = done + (content.match(/- \[ \]/g) ?? []).length;
    return total > 0 ? `${done}/${total} tarefas` : null;
  } catch {
    return null;
  }
}

async function scanSpecs(cmd) {
  try {
    const specsDir = path.join(process.cwd(), SDD_DIR, 'specs');
    const entries = await readdir(specsDir, { withFileTypes: true });
    const results = [];
    for (const entry of entries) {
      if (!entry.isDirectory()) continue;
      const specName = entry.name;
      try {
        const raw = await readFile(path.join(specsDir, specName, 'spec.json'), 'utf8');
        const spec = JSON.parse(raw);
        const [phaseInfo, taskProgress] = await Promise.all([
          Promise.resolve(getPhaseInfo(spec, specName, cmd)),
          readTaskProgress(specName),
        ]);
        results.push({ specName, featureName: spec.feature_name ?? specName, phaseInfo, taskProgress });
      } catch { /* skip malformed */ }
    }
    return results;
  } catch {
    return [];
  }
}

// Detects which agent is installed in the current project by checking known directories
async function detectInstalledAgent() {
  const checks = [
    { agent: 'claude-code-agent', path: '.claude/agents/sdd' },
    { agent: 'claude-code',       path: '.claude/commands/sdd' },
    { agent: 'cursor',            path: '.cursor/commands/sdd' },
    { agent: 'windsurf',          path: '.windsurf/workflows' },
    { agent: 'github-copilot',    path: '.github/skills/sdd-spec-init' },
    { agent: 'gemini-cli',        path: '.gemini/commands/sdd' },
    { agent: 'codex',             path: '.codex/prompts' },
    { agent: 'qwen-code',         path: '.qwen/commands/sdd' },
  ];
  for (const check of checks) {
    try { await access(check.path); return check.agent; } catch { /* not found */ }
  }
  return null;
}

// Spawns claude with a slash command and waits for it to finish.
// ctx = { stepNum, totalSteps, stepTitle, nextStepTitle, featureName }
// Does NOT close rl — spawnSync returns the terminal cleanly when the subprocess exits.
async function runInClaude(command, rl, ctx = {}) {
  const { stepNum, totalSteps, stepTitle, nextStepTitle, featureName } = ctx;
  console.clear();
  hr();
  output.write(`${c.bold('Claude Code')}`);
  if (featureName) output.write(`${c.dim(` — ${featureName}`)}`);
  output.write('\n');
  if (stepNum && totalSteps) {
    output.write(`${c.dim(`Etapa ${stepNum} de ${totalSteps}:`)} ${c.bold(stepTitle ?? command)}\n`);
  }
  output.write(`${c.dim('Comando: ')}${c.cyan(command)}\n`);
  if (nextStepTitle) output.write(`${c.dim(`Próximo passo: ${nextStepTitle}`)}\n`);
  hr();
  output.write(`\n${c.yellow('Quando terminar, digite')} ${c.bold('/exit')} ${c.yellow('para voltar ao sdd-fp.')}\n\n`);

  let message = 'Responda sempre em português do Brasil.\n\n';
  if (stepNum && totalSteps && stepTitle) {
    message += `Contexto: você está na etapa ${stepNum} de ${totalSteps} — ${stepTitle}`;
    if (featureName) message += ` da funcionalidade "${featureName}"`;
    message += '.\n';
    if (nextStepTitle) message += `Após concluir, lembre o usuário que o próximo passo é: ${nextStepTitle}.\n`;
    message += '\n';
  }
  message += command;

  const result = spawnSync('claude', [message], { stdio: 'inherit' });
  if (result.error) {
    output.write(`\n${c.yellow(`⚠️  Não foi possível abrir o Claude Code: ${result.error.message}`)}\n`);
    output.write(`${c.dim('Instale com: npm install -g @anthropic-ai/claude-code')}\n`);
  }
  if (nextStepTitle) {
    output.write(`\n${c.dim('Próximo passo:')} ${c.bold(nextStepTitle)}\n`);
  }
  await rl.question(c.dim('Pressione Enter para voltar ao sdd-fp...'));
}

// Copies text to the system clipboard (Mac / Windows / Linux)
function copyToClipboard(text) {
  try {
    if (process.platform === 'darwin') {
      execSync('pbcopy', { input: text, stdio: ['pipe', 'ignore', 'ignore'] });
    } else if (process.platform === 'win32') {
      execSync('clip', { input: text, stdio: ['pipe', 'ignore', 'ignore'] });
    } else {
      try {
        execSync('xclip -selection clipboard', { input: text, stdio: ['pipe', 'ignore', 'ignore'] });
      } catch {
        execSync('xsel --clipboard --input', { input: text, stdio: ['pipe', 'ignore', 'ignore'] });
      }
    }
    return true;
  } catch {
    return false;
  }
}

// Validates ADO PAT against the profile API — returns true/false/null (null = network error)
async function validateAdoToken(token) {
  try {
    const b64 = Buffer.from(`:${token}`).toString('base64');
    const res = await fetch('https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=7.1', {
      headers: { Authorization: `Basic ${b64}` },
      signal: AbortSignal.timeout(8000),
    });
    if (res.status === 200) return true;
    if (res.status === 401 || res.status === 403) return false;
    return null;
  } catch {
    return null;
  }
}

// ─── ADO token setup ────────────────────────────────────────────────────────

async function setupAdoToken(rl, steeringAdoCmd) {
  console.clear();
  output.write(`${c.bold('Configuração do Azure DevOps — Token Pessoal (PAT)')}\n\n`);
  output.write(`${c.yellow('🔐 Segurança:')} ${c.dim('O token que você vai colar aqui fica')}\n`);
  output.write(`   ${c.dim('apenas na memória deste processo enquanto o assistente roda.')}\n`);
  output.write(`   ${c.dim('Ele nunca é gravado em arquivo, banco de dados ou log.')}\n`);
  output.write(`   ${c.dim('Quando o assistente encerrar, o token some automaticamente.')}\n`);

  // Passo 1 — Acessar ADO
  hr();
  output.write(`${c.bold(' Passo 1 de 4')}${c.dim(' — ')}${c.bold('Acesse o Azure DevOps')}\n`);
  hr();
  output.write(`\n   Abra no navegador:\n\n`);
  output.write(`   ${c.cyan('https://dev.azure.com')}\n\n`);
  output.write(`   ${c.dim('→ Entre com sua conta Microsoft ou GitHub.')}\n`);
  output.write(`   ${c.dim('→ Se não tiver conta, crie gratuitamente — o plano free inclui')}\n`);
  output.write(`   ${c.dim('   5 usuários com acesso completo ao Boards.')}\n\n`);
  await rl.question(c.dim('   Pressione Enter quando estiver na página do ADO...'));

  // Passo 2 — Gerar token
  console.clear();
  hr();
  output.write(`${c.bold(' Passo 2 de 4')}${c.dim(' — ')}${c.bold('Gere seu token pessoal (PAT)')}\n`);
  hr();
  output.write(`\n   Dentro do Azure DevOps:\n\n`);
  output.write(`   ${c.cyan('1.')} Clique no ícone do seu ${c.bold('perfil')} (canto superior direito)\n`);
  output.write(`   ${c.cyan('2.')} Selecione ${c.bold('"Personal access tokens"')}\n`);
  output.write(`   ${c.cyan('3.')} Clique em ${c.bold('"+ New Token"')}\n`);
  output.write(`   ${c.cyan('4.')} Preencha:\n\n`);
  output.write(`      ${c.dim('Nome:')}       ${c.bold('ai-sdd')}\n`);
  output.write(`      ${c.dim('Expiração:')}  ${c.bold('90 dias')} ${c.dim('(recomendado)')}\n`);
  output.write(`      ${c.dim('Permissão:')}  ${c.bold('Work Items')} → marque ${c.bold('Read & write')}\n\n`);
  output.write(`   ${c.cyan('5.')} Clique em ${c.bold('"Create"')} e ${c.yellow('copie o token imediatamente')}\n`);
  output.write(`      ${c.dim('⚠️  O token só aparece uma vez. Se fechar a tela, precisará gerar outro.')}\n\n`);
  await rl.question(c.dim('   Pressione Enter quando tiver o token copiado...'));

  // Passo 3 — Colar e validar token
  let token = '';
  while (true) {
    console.clear();
    hr();
    output.write(`${c.bold(' Passo 3 de 4')}${c.dim(' — ')}${c.bold('Cole seu token aqui')}\n`);
    hr();
    output.write(`\n   ${c.dim('O token não será salvo em nenhum arquivo.')}\n`);
    output.write(`   ${c.dim('Ele ficará apenas na memória desta sessão.')}\n\n`);

    token = (await rl.question(`   Cole o token: `)).trim();

    if (!token) {
      output.write(`\n   ${c.yellow('Nenhum token informado. Pulando configuração do ADO.')}\n\n`);
      await rl.question(c.dim('   Pressione Enter para continuar...'));
      return false;
    }

    output.write(`\n   ${c.dim('Validando token com o Azure DevOps...')}\n`);
    const valid = await validateAdoToken(token);

    if (valid === true) {
      output.write(`   ${c.green('✅ Token válido! Conexão com o ADO confirmada.')}\n`);
      break;
    } else if (valid === false) {
      output.write(`   ${c.yellow('⚠️  Token inválido ou sem permissão suficiente.')}\n`);
      output.write(`   ${c.dim('Verifique se a permissão "Work Items → Read & write" está marcada.')}\n`);
      const retry = await select(rl, 'O que quer fazer?', [
        { value: 'retry',    label: 'Inserir outro token',     desc: 'Voltar e colar um token diferente' },
        { value: 'continue', label: 'Continuar mesmo assim',   desc: 'O ADO pode não funcionar até o token ser corrigido' },
      ]);
      if (retry === 'retry') continue;
      break;
    } else {
      output.write(`   ${c.dim('⚠️  Não foi possível validar (sem conexão). Continuando...')}\n`);
      break;
    }
  }

  const shell = (process.env.SHELL || '').includes('zsh') ? '~/.zshrc' : '~/.bashrc';

  output.write(`\n   ${c.bold('Para ativar na sessão atual')} — rode no seu terminal:\n\n`);
  output.write(`   ${c.cyan(`export ADO_PAT="${token}"`)}\n`);

  const copied = copyToClipboard(`export ADO_PAT="${token}"`);
  if (copied) output.write(`   ${c.dim('(copiado para o clipboard ✅)')}\n`);

  const persist = (await select(rl, 'Quer que o token fique ativo em todos os terminais futuros?', [
    { value: 'yes', label: 'Sim, tornar permanente', desc: `Adiciona ao ${shell}` },
    { value: 'no',  label: 'Não, só para agora',     desc: 'Você roda o export manualmente quando precisar' },
  ])) === 'yes';

  if (persist) {
    const persistCmd = `echo 'export ADO_PAT="${token}"' >> ${shell} && source ${shell}`;
    output.write(`\n   ${c.bold('Para tornar permanente')} — rode no seu terminal:\n\n`);
    output.write(`   ${c.cyan(persistCmd)}\n`);
    const copiedPersist = copyToClipboard(persistCmd);
    if (copiedPersist) output.write(`   ${c.dim('(copiado para o clipboard ✅)')}\n`);
  }

  await rl.question(c.dim('\n   Pressione Enter para continuar (a tela será limpa para proteger o token)...'));
  console.clear();

  // Passo 4 — Configurar arquivo do projeto
  hr();
  output.write(`${c.bold(' Passo 4 de 4')}${c.dim(' — ')}${c.bold('Configure o arquivo do projeto')}\n`);
  hr();
  output.write(`\n   No seu editor/terminal de IA, rode:\n\n`);
  output.write(`   ${c.cyan(steeringAdoCmd)}\n\n`);
  output.write(`   ${c.dim('→ A IA vai criar o arquivo .sdd/steering/azure-devops.md.')}\n`);
  output.write(`   ${c.dim('→ Abra o arquivo e preencha os campos:')}\n\n`);
  output.write(`      ${c.dim('ado_organization: ')}${c.cyan('"sua-empresa"')}     ${c.dim('← URL: dev.azure.com/sua-empresa')}\n`);
  output.write(`      ${c.dim('ado_project:      ')}${c.cyan('"SeuProjeto"')}\n`);
  output.write(`      ${c.dim('ado_process:      ')}${c.cyan('"Agile"')}           ${c.dim('← ou Scrum / CMMI')}\n\n`);
  output.write(`   ${c.dim('→ Faça commit do arquivo — o time herda a configuração automaticamente.')}\n`);
  output.write(`      ${c.dim('⚠️  Não coloque o token no arquivo. Apenas o azure-devops.md.')}\n\n`);

  await rl.question(c.dim('   Pressione Enter para continuar para o roteiro de comandos...'));
  console.clear();
  return true;
}

// ─── Install flow ────────────────────────────────────────────────────────────

// GoBack at step 0 (agent selection) propagates to the main menu.
// GoBack at step 1 (overwrite) goes back to step 0.
async function runInstall(rl) {
  let step = 0;
  let agent, overwrite;
  while (step < 2) {
    try {
      if (step === 0) {
        agent = await select(rl, 'Qual ferramenta de IA você usa?', AGENTS);
        step = 1;
      } else {
        overwrite = await select(rl, 'Como tratar arquivos existentes?', OVERWRITE_OPTIONS);
        step = 2;
      }
    } catch (err) {
      if (err instanceof GoBack) {
        if (step === 0) throw err;
        step--;
        continue;
      }
      throw err;
    }
  }
  return { agent, overwrite };
}

// ─── Wizard ─────────────────────────────────────────────────────────────────

function toSpecName(name) {
  return name
    .normalize('NFD').replace(/[̀-ͯ]/g, '')
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '');
}

// ctx = { toolLabel, isTerminal, featureName }
function showStep(num, total, title, command, notes, adoNote, ctx = {}) {
  const { toolLabel, isTerminal, featureName } = ctx;
  output.write(`\n`);
  hr();
  output.write(`${c.bold(` Passo ${num} de ${total}`)}${c.dim(' — ')}${c.bold(title)}\n`);
  hr();
  if (featureName) {
    output.write(`\n   ${c.dim('Funcionalidade:')} ${c.bold(featureName)}\n`);
  }
  if (command) {
    const where = toolLabel
      ? (isTerminal
          ? `No terminal do ${toolLabel}, cole o comando abaixo:`
          : `No chat do ${toolLabel}, cole o comando abaixo:`)
      : 'Cole o comando abaixo no seu editor de IA:';
    output.write(`\n   ${where}\n\n`);
    output.write(`   ${c.cyan(command)}\n\n`);
  }
  notes.forEach(n => output.write(`   ${c.dim(n)}\n`));
  if (adoNote) output.write(`\n   ${c.green('ADO:')} ${c.dim(adoNote)}\n`);
  output.write('\n');
}

// ctx = { toolLabel, isTerminal }
async function waitNext(rl, current, total, command, ctx = {}) {
  const { toolLabel, isTerminal } = ctx;
  const where = toolLabel
    ? (isTerminal ? `no terminal do ${toolLabel}` : `no chat do ${toolLabel}`)
    : 'no seu editor de IA';
  if (command) {
    output.write(`${c.dim(`Execute ${where}, depois confirme aqui quando terminar.`)}\n`);
  }
  while (true) {
    const prompt = command
      ? c.dim(`  ['c' copiar | Enter quando executar]: `)
      : c.dim(`  Pressione Enter quando estiver pronto... `);
    const ans = (await rl.question(prompt)).trim().toLowerCase();
    if (ans === 'c' && command) {
      const ok = copyToClipboard(command);
      output.write(ok
        ? `  ${c.green('✅ Copiado! Cole')} ${c.dim(`${where}.`)}\n`
        : `  ${c.yellow('⚠️  Não foi possível copiar automaticamente.')}\n`,
      );
      continue;
    }
    break;
  }
  console.clear();
}

// Step cursor pattern — GoBack at step 0 propagates so main loop can return to menu.
// GoBack at any later step decrements the cursor and re-shows that step's prompt.
async function runWizard(rl) {
  const detectedAgentKey = await detectInstalledAgent();
  const cmd = AGENT_COMMANDS[detectedAgentKey] ?? AGENT_COMMANDS['claude-code'];

  function showWizardHeader() {
    console.clear();
    output.write(`${c.bold('Planejar nova funcionalidade')}\n`);
    output.write(`${c.dim('Responda as perguntas e receberá o roteiro de comandos pronto para executar.')}\n`);
    if (detectedAgentKey) {
      output.write(`\n   ${c.dim('Ferramenta detectada:')} ${c.green(cmd.label)}\n`);
      output.write(`   ${c.dim('Os comandos abaixo já estão no formato correto para ela.')}\n`);
    } else {
      output.write(`\n   ${c.dim('Nenhuma instalação detectada no projeto atual.')}\n`);
      output.write(`   ${c.dim('Os comandos serão mostrados no formato Claude Code.')}\n`);
      output.write(`   ${c.dim('Execute "Instalar SDD" no menu principal primeiro se ainda não instalou.')}\n`);
    }
  }

  showWizardHeader();

  let step = 0;
  let projectType, featureName, specName, briefData, epicName, useAdo, adoReady, mode;

  // Steps: 0=project type, 1=feature name+slug, 2=brief (optional), 3=Epic, 4=ADO?, 5=ADO config, 6=mode
  // Step 5 is skipped when !useAdo — handled in the GoBack decrement logic below.
  while (step < 7) {
    try {
      switch (step) {
        case 0: {
          projectType = await select(rl, 'Qual é o tipo do seu projeto?', [
            { value: 'new',      label: 'Projeto novo',      desc: 'Construindo do zero' },
            { value: 'existing', label: 'Projeto existente', desc: 'Já tem código, vou adicionar uma funcionalidade' },
          ]);
          step = 1;
          break;
        }
        case 1: {
          output.write(`\n${c.bold('Qual funcionalidade você quer construir?')}\n`);
          output.write(`${c.dim('  Exemplos: "Checkout com Pix", "Cadastro de usuário", "Relatório mensal"')}\n`);
          output.write(`${c.dim('  (Digite 0 para voltar)')}\n\n`);
          const raw = (await rl.question('  Nome da funcionalidade: ')).trim();
          if (raw === '0') throw new GoBack();
          featureName = raw || 'Minha Funcionalidade';
          const suggestedSlug = toSpecName(featureName);
          output.write(`\n  ${c.dim('Código gerado:')} ${c.cyan(suggestedSlug)}\n`);
          const customSlug = (await rl.question(`  ${c.dim('Enter para confirmar ou escreva um código personalizado: ')}`)).trim();
          if (customSlug === '0') throw new GoBack();
          specName = (customSlug && toSpecName(customSlug)) ? toSpecName(customSlug) : suggestedSlug;
          if (specName !== suggestedSlug) output.write(`  ${c.dim('Código confirmado:')} ${c.cyan(specName)}\n`);
          step = 2;
          break;
        }
        case 2: {
          // Brief: business team describes the feature in plain language (optional)
          showWizardHeader();
          output.write(`\n${c.bold('Descreva a funcionalidade em linguagem natural')} ${c.dim('(opcional)')}\n`);
          output.write(`${c.dim('  Isso ajuda a IA a gerar requisitos mais precisos.')}\n`);
          output.write(`${c.dim('  Preencha o que souber — pode deixar campos em branco.')}\n\n`);

          output.write(`${c.bold('1. O que os usuários poderão fazer?')}\n`);
          output.write(`${c.dim('   Ex: "O cliente poderá pagar com Pix e receber confirmação por e-mail"')}\n`);
          const whatText = (await rl.question('   → ')).trim();
          if (whatText === '0') throw new GoBack();

          output.write(`\n${c.bold('2. Quem vai usar essa funcionalidade?')}\n`);
          output.write(`${c.dim('   Ex: "Clientes do app mobile", "Gestores do backoffice"')}\n`);
          const whoText = (await rl.question('   → ')).trim();

          output.write(`\n${c.bold('3. Como saberemos que está pronto?')} ${c.dim('(critérios de aceite)')}\n`);
          output.write(`${c.dim('   Ex: "O pagamento é confirmado em até 5 segundos, o e-mail chega em até 1 minuto"')}\n`);
          const doneText = (await rl.question('   → ')).trim();

          output.write(`\n${c.bold('4. Alguma restrição ou regra de negócio importante?')} ${c.dim('(opcional)')}\n`);
          output.write(`${c.dim('   Ex: "Só para usuários com CPF verificado", "Limite de R$ 5.000 por transação"')}\n`);
          const rulesText = (await rl.question('   → ')).trim();

          if (whatText || whoText || doneText || rulesText) {
            const lines = [`# Brief: ${featureName}\n`];
            if (whatText)  lines.push(`## O que os usuários poderão fazer\n${whatText}\n`);
            if (whoText)   lines.push(`## Quem vai usar\n${whoText}\n`);
            if (doneText)  lines.push(`## Critérios de aceite\n${doneText}\n`);
            if (rulesText) lines.push(`## Restrições e regras de negócio\n${rulesText}\n`);
            briefData = lines.join('\n');
            output.write(`\n${c.green('✅ Descrição registrada.')} ${c.dim('A IA usará isso ao gerar os requisitos.')}\n`);
          } else {
            briefData = null;
            output.write(`\n${c.dim('Nenhuma descrição informada — a IA fará perguntas ao gerar os requisitos.')}\n`);
          }
          step = 3;
          break;
        }
        case 3: {
          const portfolio = await loadPortfolio();
          const existingEpics = portfolio.epics.map(e => ({
            value: e.name,
            label: e.name,
            desc: `${e.features.length} funcionalidade(s)`,
          }));
          const epicOptions = [
            ...existingEpics,
            { value: '__new__',  label: 'Novo Epic',  desc: 'Criar um novo grupo de funcionalidades' },
            { value: '__none__', label: 'Sem Epic',   desc: 'Não associar a nenhum grupo' },
          ];
          const epicChoice = await select(rl, 'A qual Epic pertence esta funcionalidade?', epicOptions);
          if (epicChoice === '__new__') {
            output.write(`\n${c.bold('Nome do novo Epic:')}\n`);
            output.write(`${c.dim('  Exemplos: "Plataforma de E-commerce", "App Mobile", "Portal do Cliente"')}\n\n`);
            const epicRaw = (await rl.question('  Nome do Epic: ')).trim();
            if (epicRaw === '0') throw new GoBack();
            epicName = epicRaw || 'Novo Epic';
          } else if (epicChoice === '__none__') {
            epicName = null;
          } else {
            epicName = epicChoice;
          }
          step = 4;
          break;
        }
        case 4: {
          useAdo = (await select(rl, 'Você usa Azure DevOps (ADO)?', [
            { value: 'yes', label: 'Sim', desc: 'Os itens do ADO serão criados automaticamente' },
            { value: 'no',  label: 'Não', desc: 'Seguir só com os comandos SDD' },
          ])) === 'yes';
          if (!useAdo) adoReady = true;
          step = 5;
          break;
        }
        case 5: {
          if (useAdo) {
            const already = (await select(rl, 'O ADO já está configurado no projeto?', [
              { value: 'yes', label: 'Sim, já configurei',           desc: 'Já tenho o token e o arquivo azure-devops.md preenchido' },
              { value: 'no',  label: 'Não ainda — configure agora',  desc: 'Te guio passo a passo para gerar o token e configurar o projeto' },
            ])) === 'yes';
            adoReady = already ? true : await setupAdoToken(rl, cmd.steeringAdo);
          }
          step = 6;
          break;
        }
        case 6: {
          mode = await select(rl, 'Agora vou mostrar o roteiro de comandos para criar esta funcionalidade. Como quer trabalhar?', [
            { value: 'step', label: 'Passo a passo',       desc: 'Mostra um comando por vez — abre o Claude Code, você executa e volta aqui para o próximo' },
            { value: 'all',  label: 'Ver tudo de uma vez', desc: 'Exibe o roteiro completo para consulta; você executa quando quiser' },
          ]);
          step = 7;
          break;
        }
      }
    } catch (err) {
      if (err instanceof GoBack) {
        if (step === 0) throw err;  // propagate: user wants to exit wizard back to main menu
        const prev = step - 1;
        // Skip ADO config step (5) when going backward if useAdo is false
        step = (prev === 5 && useAdo === false) ? 4 : prev;
        showWizardHeader();
        continue;
      }
      throw err;
    }
  }

  // Persist Epic association and brief before showing the roteiro
  await addFeatureToPortfolio(specName, featureName, epicName);
  if (briefData) await saveBrief(specName, briefData);

  // Build steps using agent-specific commands
  const steps = [];

  if (projectType === 'new') {
    steps.push({
      title: `Abra o ${cmd.label} no seu projeto`,
      command: cmd.open.cmd,
      notes: [cmd.open.note],
      adoNote: null,
    });
    steps.push({
      title: 'Apresente o projeto para a IA (recomendado)',
      command: cmd.steering,
      notes: [
        '→ Cole o comando e a IA vai perguntar sobre tecnologias e padrões do projeto.',
        '→ Responda às perguntas no chat — isso melhora a qualidade de todos os próximos passos.',
        '→ Faça uma vez por projeto.',
      ],
      adoNote: null,
    });
    if (cmd.quick) {
      steps.push({
        title: 'Atalho: planeje tudo de uma vez (opcional)',
        command: cmd.quick(featureName),
        notes: [
          '→ Cole o comando e a IA vai conduzir todo o planejamento sem parar.',
          '→ Você responde às perguntas dela no chat e ela gera tudo automaticamente.',
          `→ Se usar este atalho, pule os próximos 4 passos e vá direto para "Implemente".`,
        ],
        adoNote: useAdo ? `Epic, User Stories e Tasks criados automaticamente. ✅` : null,
      });
    }
    steps.push({
      title: 'Inicie o planejamento da funcionalidade',
      command: cmd.init(featureName),
      notes: [
        '→ Cole o comando seguido de uma breve descrição do que você quer construir.',
        '→ A IA vai criar o plano inicial — leia e confirme se faz sentido antes de continuar.',
      ],
      adoNote: useAdo ? `Epic "${featureName}" criado automaticamente. ✅` : null,
    });
    steps.push({
      title: 'Detalhe os requisitos',
      command: cmd.requirements(specName),
      notes: [
        '→ A IA vai fazer perguntas sobre a funcionalidade no chat.',
        '→ Responda com detalhes e exemplos reais de uso.',
        '→ Ao final, leia o documento gerado e diga à IA se aprova ou quer ajustar.',
      ],
      adoNote: useAdo ? 'User Stories criadas e linkadas ao Epic. ✅' : null,
    });
    steps.push({
      title: 'Aprove o plano de desenvolvimento',
      command: cmd.design(specName),
      notes: [
        '→ A IA vai propor como construir a funcionalidade — leia o plano gerado no chat.',
        '→ Se concordar, responda que aprovou e siga para o próximo passo.',
        '→ Se quiser mudanças, peça ajustes à IA antes de continuar.',
      ],
      adoNote: useAdo ? 'Descrição do Epic atualizada com as decisões de design. ✅' : null,
    });
    steps.push({
      title: 'Divida em tarefas',
      command: cmd.tasks(specName),
      notes: [
        '→ A IA vai gerar uma lista detalhada de tarefas para o time.',
        '→ Leia a lista no chat, ajuste se necessário e diga à IA que aprovou.',
      ],
      adoNote: useAdo ? 'Tasks criadas e linkadas às User Stories. Kanban pronto. ✅' : null,
    });
    steps.push({
      title: 'Implemente',
      command: cmd.impl(specName),
      notes: [
        '→ A IA vai implementar as tarefas uma a uma e mostrar o código gerado.',
        '→ Revise cada entrega no chat e confirme quando estiver satisfeito.',
      ],
      adoNote: null,
    });
    steps.push({
      title: 'Valide o que foi construído (opcional)',
      command: cmd.validateImpl(specName),
      notes: [
        '→ A IA vai gerar um relatório comparando o código com o plano original.',
        '→ Leia o relatório — se houver pendências, peça à IA para corrigir antes de encerrar.',
      ],
      adoNote: null,
    });
  } else {
    steps.push({
      title: `Abra o ${cmd.label} no seu projeto`,
      command: cmd.open.cmd,
      notes: [cmd.open.note],
      adoNote: null,
    });
    steps.push({
      title: 'Apresente o projeto existente para a IA',
      command: cmd.steering,
      notes: [
        '→ A IA vai analisar o código existente e criar um documento de contexto.',
        '→ Revise o documento gerado no chat e corrija qualquer informação imprecisa.',
        '→ Faça isso uma vez por projeto — ou quando o código mudar muito.',
      ],
      adoNote: null,
    });
    steps.push({
      title: 'Inicie o planejamento da funcionalidade',
      command: cmd.init(featureName),
      notes: [
        '→ Cole o comando seguido de uma breve descrição do que você quer construir.',
        '→ A IA vai criar o plano considerando o que já existe — leia e confirme.',
      ],
      adoNote: useAdo ? `Epic "${featureName}" criado automaticamente. ✅` : null,
    });
    steps.push({
      title: 'Detalhe os requisitos',
      command: cmd.requirements(specName),
      notes: [
        '→ A IA vai fazer perguntas sobre a funcionalidade no chat.',
        '→ Responda com exemplos reais de uso — quanto mais detalhado, melhor.',
        '→ Ao final, leia o documento gerado e diga se aprova ou quer ajustar.',
      ],
      adoNote: useAdo ? 'User Stories criadas e linkadas ao Epic. ✅' : null,
    });
    steps.push({
      title: 'Analise o que já existe no projeto',
      command: cmd.validateGap(specName),
      notes: [
        '→ A IA vai comparar o código atual com os requisitos que você acabou de definir.',
        '→ Leia o relatório no chat — ele mostra o que precisa ser criado, alterado ou reaproveitado.',
      ],
      adoNote: null,
    });
    steps.push({
      title: 'Aprove o plano de desenvolvimento',
      command: cmd.design(specName),
      notes: [
        '→ A IA vai propor como integrar a nova funcionalidade ao código existente.',
        '→ Leia o plano no chat, peça ajustes se necessário e diga à IA que aprovou.',
      ],
      adoNote: useAdo ? 'Descrição do Epic atualizada com as decisões de design. ✅' : null,
    });
    steps.push({
      title: 'Confirme a integração do plano (opcional)',
      command: cmd.validateDesign(specName),
      notes: [
        '→ A IA vai verificar se o plano se encaixa sem conflitos no código existente.',
        '→ Leia o relatório — se tudo estiver bem, siga em frente; se houver conflitos, peça correções.',
        '→ Opcional — pode pular se o passo anterior não gerou dúvidas.',
      ],
      adoNote: null,
    });
    steps.push({
      title: 'Divida em tarefas',
      command: cmd.tasks(specName),
      notes: [
        '→ A IA vai gerar uma lista de tarefas ordenadas por dependência.',
        '→ Leia a lista, ajuste se necessário e diga à IA que aprovou.',
      ],
      adoNote: useAdo ? 'Tasks criadas e linkadas às User Stories. Kanban pronto. ✅' : null,
    });
    steps.push({
      title: 'Implemente',
      command: cmd.impl(specName),
      notes: [
        '→ A IA vai implementar as tarefas uma a uma e mostrar o código gerado.',
        '→ Revise cada entrega no chat e confirme quando estiver satisfeito.',
      ],
      adoNote: null,
    });
    steps.push({
      title: 'Valide o que foi construído (opcional)',
      command: cmd.validateImpl(specName),
      notes: [
        '→ A IA vai gerar um relatório comparando o código com o plano original.',
        '→ Leia o relatório — se houver pendências, peça à IA para corrigir antes de encerrar.',
      ],
      adoNote: null,
    });
  }

  const total = steps.length;
  const stepCtx = {
    toolLabel:   cmd.label,
    isTerminal:  cmd.open.cmd !== null,
    featureName,
  };

  console.clear();
  output.write(`\n${c.bold(`Roteiro — ${featureName}`)}\n`);
  output.write(`${c.dim(`${total} passos · ${cmd.label}${useAdo ? ' · ADO ativado' : ''}${projectType === 'existing' ? ' · Projeto existente' : ' · Projeto novo'}`)}\n`);

  for (let i = 0; i < steps.length; i++) {
    const s = steps[i];
    showStep(i + 1, total, s.title, s.command, s.notes, s.adoNote, stepCtx);
    if (mode === 'step' && i < steps.length - 1) {
      await waitNext(rl, i + 1, total, s.command, stepCtx);
    }
  }

  hr();
  output.write(`\n${c.green('✅ Roteiro completo!')} Boa sorte com "${featureName}".\n\n`);

  const firstCmdIdx = steps.findIndex(s => s.command?.startsWith('/'));
  const firstCmd    = firstCmdIdx >= 0 ? steps[firstCmdIdx].command : null;
  if (firstCmd) {
    const action = await select(rl, 'Próximo passo:', [
      { value: 'exec', label: 'Executar agora',   desc: `Abre o Claude Code e já inicia o primeiro passo` },
      { value: 'back', label: 'Voltar ao menu',    desc: 'Você executa quando quiser pelo painel' },
    ]);
    if (action === 'exec') {
      await runInClaude(firstCmd, rl, {
        stepNum:       firstCmdIdx + 1,
        totalSteps:    steps.length,
        stepTitle:     steps[firstCmdIdx].title,
        nextStepTitle: steps[firstCmdIdx + 1]?.title,
        featureName,
      });
    }
  }
}

// ─── Dashboard ──────────────────────────────────────────────────────────────

async function runDashboard(rl) {
  const detectedKey = await detectInstalledAgent();
  const cmd = AGENT_COMMANDS[detectedKey] ?? AGENT_COMMANDS['claude-code'];

  const [specs, portfolio] = await Promise.all([scanSpecs(cmd), loadPortfolio()]);

  // Build set of spec names that already have directories
  const existingSpecNames = new Set(specs.map(s => s.specName));

  // Portfolio features that were planned via wizard but have no spec dir yet
  const pendingFromPortfolio = [];
  for (const epic of portfolio.epics) {
    for (const specName of epic.features) {
      if (!existingSpecNames.has(specName)) {
        const featureName = epic.featureNames?.[specName] ?? specName;
        pendingFromPortfolio.push({ specName, featureName, epicName: epic.name });
      }
    }
  }

  console.clear();
  output.write(`${c.bold('Minhas Funcionalidades')}\n`);

  if (specs.length === 0 && pendingFromPortfolio.length === 0) {
    output.write(`\n${c.dim('Nenhuma funcionalidade encontrada em .sdd/specs/.')}\n`);
    output.write(`${c.dim('Use "Planejar nova funcionalidade" para criar a primeira.')}\n\n`);
    await rl.question(c.dim('Pressione Enter para voltar...'));
    return;
  }

  const totalCount = specs.length + pendingFromPortfolio.length;
  output.write(`${c.dim(`${totalCount} funcionalidade(s) · ${detectedKey ? cmd.label : 'Claude Code'}`)}\n`);

  // Map spec → epic
  const specToEpic = new Map();
  for (const epic of portfolio.epics) {
    for (const f of epic.features) specToEpic.set(f, epic.name);
  }

  const byEpic = new Map();
  const noEpic = [];
  for (const spec of specs) {
    const epicName = specToEpic.get(spec.specName);
    if (epicName) {
      if (!byEpic.has(epicName)) byEpic.set(epicName, []);
      byEpic.get(epicName).push(spec);
    } else {
      noEpic.push(spec);
    }
  }

  const indexed = [];
  let counter = 1;

  function printSpecRow(spec) {
    const num    = c.cyan(`${counter}.`);
    const name   = c.bold(spec.featureName ?? spec.specName);
    const slug   = c.dim(`[${spec.specName}]`);
    const status = spec.phaseInfo.label;
    const tasks  = spec.taskProgress ? c.dim(` · ${spec.taskProgress}`) : '';
    output.write(`\n   ${num} ${name} ${slug}\n`);
    output.write(`      ${status}${tasks}\n`);
    if (spec.phaseInfo.nextCmd) {
      output.write(`      ${c.dim('→')} ${c.cyan(spec.phaseInfo.nextCmd)}\n`);
    }
    indexed.push({ num: counter, ...spec });
    counter++;
  }

  for (const [epicName, epicSpecs] of byEpic) {
    output.write('\n');
    hr();
    output.write(`${c.bold(`Epic: ${epicName}`)} ${c.dim(`(${epicSpecs.length})`)}\n`);
    for (const s of epicSpecs) printSpecRow(s);
  }
  if (noEpic.length > 0) {
    output.write('\n');
    hr();
    output.write(`${c.bold('Sem Epic')} ${c.dim(`(${noEpic.length})`)}\n`);
    for (const s of noEpic) printSpecRow(s);
  }

  // Features planned via wizard but spec not yet started in the AI tool
  if (pendingFromPortfolio.length > 0) {
    output.write('\n');
    hr();
    output.write(`${c.bold('Planejadas — aguardando início no assistente de IA')} ${c.dim(`(${pendingFromPortfolio.length})`)}\n`);
    const byEpicPending = new Map();
    const noEpicPending = [];
    for (const p of pendingFromPortfolio) {
      if (p.epicName) {
        if (!byEpicPending.has(p.epicName)) byEpicPending.set(p.epicName, []);
        byEpicPending.get(p.epicName).push(p);
      } else {
        noEpicPending.push(p);
      }
    }
    const printPendingRow = (p) => {
      const num  = c.cyan(`${counter}.`);
      const name = c.bold(p.featureName);
      const slug = c.dim(`[${p.specName}]`);
      output.write(`\n   ${num} ${name} ${slug}\n`);
      output.write(`      ${c.dim('Pendente — aguardando início no assistente de IA')}\n`);
      indexed.push({ num: counter, specName: p.specName, featureName: p.featureName, phaseInfo: { label: c.dim('Pendente'), nextCmd: cmd.init(p.featureName) }, taskProgress: null });
      counter++;
    };
    for (const [epicName, items] of byEpicPending) {
      output.write(`\n   ${c.dim(`Epic: ${epicName}`)}\n`);
      for (const p of items) printPendingRow(p);
    }
    for (const p of noEpicPending) printPendingRow(p);
  }

  output.write('\n');
  hr();

  // Summary counts
  const ready    = specs.filter(s => s.phaseInfo.nextCmd === cmd.impl(s.specName)).length;
  const waiting  = specs.filter(s => !s.phaseInfo.nextCmd).length;
  const ongoing  = specs.length - ready - waiting;
  output.write(
    `${c.green(`${ready} pronta(s) para implementar`)}` +
    `  ${c.yellow(`${waiting} aguardando revisão`)}` +
    `  ${c.dim(`${ongoing} em andamento`)}\n`,
  );

  if (indexed.length > 0) {
    output.write(`\n${c.dim(`Digite o número para ver detalhes (Enter para voltar):`)}\n`);
    while (true) {
      const ans = (await rl.question(c.dim(`  Número [1-${indexed.length}]: `))).trim();
      if (!ans) break;
      const n = parseInt(ans, 10);
      const found = indexed.find(s => s.num === n);
      if (!found) { output.write(c.yellow(`  Número inválido.\n`)); continue; }

      // Feature detail view
      console.clear();
      output.write(`\n${c.bold(found.featureName)} ${c.dim(`[${found.specName}]`)}\n`);
      output.write(`${c.dim('Status:')} ${found.phaseInfo.label}${found.taskProgress ? c.dim(` · ${found.taskProgress}`) : ''}\n`);
      if (found.phaseInfo.nextCmd) {
        output.write(`\n${c.dim('Próximo passo:')}\n`);
        output.write(`   ${c.cyan(found.phaseInfo.nextCmd)}\n`);
      }
      output.write('\n');

      if (found.phaseInfo.nextCmd) {
        const action = await select(rl, 'O que quer fazer?', [
          { value: 'exec', label: 'Executar próximo passo', desc: 'Abre o Claude Code e executa o comando agora' },
          { value: 'copy', label: 'Copiar comando',         desc: 'Copia para o clipboard e executa depois' },
          { value: 'back', label: 'Voltar à lista',         desc: 'Retorna ao painel de funcionalidades' },
        ]);
        if (action === 'exec') {
          await runInClaude(found.phaseInfo.nextCmd, rl, {
            stepTitle:     found.phaseInfo.label.replace(/\x1b\[[0-9;]*m/g, ''), // strip ANSI
            featureName:   found.featureName,
          });
          return; // Exit runDashboard — main loop re-creates rl e reentra
        }
        if (action === 'copy') {
          const ok = copyToClipboard(found.phaseInfo.nextCmd);
          output.write(ok
            ? `\n${c.green('✅ Copiado!')} Cole no seu assistente de IA e execute.\n`
            : `\n${c.yellow('⚠️  Não foi possível copiar:')} ${c.dim(found.phaseInfo.nextCmd)}\n`);
          await rl.question(c.dim('\nPressione Enter para voltar à lista...'));
        }
      } else {
        await rl.question(c.dim('Aguardando revisão humana — pressione Enter para voltar à lista...'));
      }

      // Reprint the dashboard list after returning from detail
      console.clear();
      output.write(`${c.bold('Minhas Funcionalidades')}\n`);
      output.write(`${c.dim(`${totalCount} funcionalidade(s) · ${detectedKey ? cmd.label : 'Claude Code'}`)}\n`);
      for (const s of indexed) {
        const num    = c.cyan(`${s.num}.`);
        const name   = c.bold(s.featureName ?? s.specName);
        const slug   = c.dim(`[${s.specName}]`);
        const status = s.phaseInfo.label;
        const tasks  = s.taskProgress ? c.dim(` · ${s.taskProgress}`) : '';
        output.write(`\n   ${num} ${name} ${slug}\n`);
        output.write(`      ${status}${tasks}\n`);
      }
      output.write('\n');
      output.write(`${c.dim(`Digite o número para ver detalhes (Enter para voltar):`)}\n`);
    }
  } else {
    await rl.question(c.dim('Pressione Enter para voltar ao menu principal...'));
  }
}

// ─── Guide ──────────────────────────────────────────────────────────────────

function printInstructions() {
  output.write(`
${c.bold('══════════════════════════════════════════════')}
${c.bold('  Guia completo — SDD + Azure DevOps')}
${c.bold('══════════════════════════════════════════════')}

O SDD organiza o trabalho em especificações (specs): a IA ajuda a
definir ${c.bold('o que')} construir — com aprovação humana — antes de escrever
qualquer linha de código. Cada passo sincroniza automaticamente com
o Azure DevOps, criando Epics, User Stories e Tasks no seu Kanban.

${c.bold('─────────────────────────────────────────────')}
${c.bold('  O que aparece no ADO a cada passo')}
${c.bold('─────────────────────────────────────────────')}

  ${c.cyan('spec-init')}          ${c.dim('→')} cria ${c.green('Epic')} no ADO
  ${c.cyan('spec-requirements')}  ${c.dim('→')} cria ${c.green('User Stories')} linkadas ao Epic
  ${c.cyan('spec-design')}        ${c.dim('→')} atualiza a descrição do Epic
  ${c.cyan('spec-tasks')}         ${c.dim('→')} cria ${c.green('Tasks')} linkadas às User Stories

  Tudo automático, sem digitar nada no ADO.

${c.bold('─────────────────────────────────────────────')}
${c.yellow('  Configuração do ADO (uma vez, pelo líder técnico)')}
${c.bold('─────────────────────────────────────────────')}

${c.bold('1.')} No seu editor/terminal de IA, ative a integração:

   ${c.cyan('<steering-custom> Integração com Azure DevOps')}

${c.bold('2.')} Preencha o arquivo gerado ${c.dim('(.sdd/steering/azure-devops.md)')}:

   ${c.dim('ado_organization: "minha-empresa"')}
   ${c.dim('ado_project:      "MeuProjeto"')}
   ${c.dim('ado_process:      "Agile"')}   ${c.dim('← ou Scrum / CMMI')}

${c.bold('3.')} Cada membro do time configura seu token pessoal:

   ${c.dim('→ Use o Assistente passo a passo (opção 2) para fazer isso com segurança.')}
   ${c.dim('⚠️  Nunca salve o token em arquivos ou no git.')}

${c.bold('─────────────────────────────────────────────')}
${c.bold('  Exemplo: Epic → Feature → User Stories')}
${c.bold('─────────────────────────────────────────────')}

  ${c.dim('Epic:')}         Plataforma de E-commerce
  ${c.dim('Feature:')}      Checkout
  ${c.dim('User Stories:')} Como cliente, quero pagar com cartão de crédito
                 Como cliente, quero ver o resumo do pedido antes de pagar
                 Como cliente, quero receber confirmação por e-mail

${c.bold('─────────────────────────────────────────────')}
${c.green('  PROJETO NOVO — fluxo completo')}
${c.bold('─────────────────────────────────────────────')}

  ${c.dim('steering')}                           ${c.dim('→ Define tech stack e padrões (recomendado)')}
  ${c.dim('spec-init')} "Checkout com cartão"   ${c.dim('→ Epic criado no ADO ✅')}
  ${c.dim('spec-requirements')} checkout-cartao  ${c.dim('→ User Stories criadas no ADO ✅')}
  ${c.dim('spec-design')} checkout-cartao        ${c.dim('→ Epic atualizado no ADO ✅')}
  ${c.dim('spec-tasks')} checkout-cartao         ${c.dim('→ Tasks criadas no ADO ✅ · Kanban pronto')}
  ${c.dim('spec-impl')} checkout-cartao          ${c.dim('→ Implementação com TDD')}
  ${c.dim('validate-impl')} checkout-cartao      ${c.dim('→ Opcional: valida cobertura de requisitos')}

${c.bold('─────────────────────────────────────────────')}
${c.cyan('  ATALHO — spec-quick (Claude Code com Agentes)')}
${c.bold('─────────────────────────────────────────────')}

  ${c.dim('spec-quick')} "Checkout com cartão"
  ${c.dim('  → Executa spec-init + spec-requirements + spec-design + spec-tasks')}
  ${c.dim('  → Em seguida: spec-impl checkout-com-cartao')}
  ${c.dim('  Use')} ${c.cyan('--auto')} ${c.dim('para rodar tudo sem pausas: spec-quick "Feature" --auto')}

${c.bold('─────────────────────────────────────────────')}
${c.yellow('  PROJETO EXISTENTE — melhorando o que já existe')}
${c.bold('─────────────────────────────────────────────')}

  ${c.dim('steering')}                           ${c.dim('→ IA aprende o projeto (faça uma vez)')}
  ${c.dim('spec-init')} "Checkout com Pix"       ${c.dim('→ Epic criado no ADO ✅')}
  ${c.dim('spec-requirements')} checkout-pix     ${c.dim('→ User Stories criadas no ADO ✅')}
  ${c.dim('validate-gap')} checkout-pix          ${c.dim('→ Analisa código atual vs requisitos')}
  ${c.dim('spec-design')} checkout-pix           ${c.dim('→ Epic atualizado no ADO ✅')}
  ${c.dim('validate-design')} checkout-pix       ${c.dim('→ Opcional: valida integração')}
  ${c.dim('spec-tasks')} checkout-pix            ${c.dim('→ Tasks criadas no ADO ✅ · Kanban pronto')}
  ${c.dim('spec-impl')} checkout-pix             ${c.dim('→ Implementação com TDD')}

${c.bold('─────────────────────────────────────────────')}
${c.bold('  O que o Kanban mostra após os passos')}
${c.bold('─────────────────────────────────────────────')}

  ${c.green('Epic')}: Checkout com cartão de crédito
  ${c.dim('├──')} ${c.blue('User Story')}: Formulário de pagamento
  ${c.dim('│   ├──')} Task: Criar componente de input do cartão
  ${c.dim('│   └──')} Task: Integrar com gateway de pagamento
  ${c.dim('└──')} ${c.blue('User Story')}: Confirmação por e-mail
  ${c.dim('    ├──')} Task: Integrar serviço de e-mail
  ${c.dim('    └──')} Task: Criar template de confirmação

  Tudo criado automaticamente, sem digitar nada no ADO.

${c.bold('─────────────────────────────────────────────')}
${c.bold('  Dicas para gerentes de projeto')}
${c.bold('─────────────────────────────────────────────')}

${c.yellow('❯')} O PM pode rodar ${c.cyan('spec-init')} e ${c.cyan('spec-requirements')} sem mexer no código.
  ${c.dim('O time técnico assume a partir do spec-design.')}

${c.yellow('❯')} Use o ${c.bold('Assistente passo a passo')} (opção 2 do menu) para ser guiado
  ${c.dim('comando por comando com os termos certos para sua ferramenta.')}

${c.yellow('❯')} Cada spec fica salva em ${c.cyan('.sdd/')} — revisável a qualquer hora.

`);
}

// ─── Header & main loop ─────────────────────────────────────────────────────

function printHeader() {
  console.clear();
  output.write(`${c.bold('SDD-FP')}\n`);
  output.write(`${c.dim('Spec-Driven Development — configure sua IA para escrever software com qualidade.')}\n\n`);
  output.write(`${c.dim('Antes de escrever código, a IA ajuda sua equipe a definir o')} ${c.bold('QUE')} ${c.dim('construir.')}\n`);
  output.write(`${c.dim('Cada feature começa com um plano revisado por humanos e termina com código')}\n`);
  output.write(`${c.dim('testado — com Epic, User Stories e Tasks criados automaticamente no ADO.')}\n`);
}

async function checkForUpdate(currentVersion) {
  try {
    const res = await fetch('https://registry.npmjs.org/sdd-fp/latest', {
      signal: AbortSignal.timeout(3000),
    });
    if (res.ok) {
      const data = await res.json();
      return data.version && data.version !== currentVersion ? data.version : null;
    }
  } catch { /* offline or timeout — silently skip */ }
  return null;
}

export async function runTui(runCli, execOpts, version = '?') {
  const isInteractive = input.isTTY && output.isTTY;

  if (process.argv.slice(2).length > 0 || !isInteractive) {
    return runCli(process.argv.slice(2), { platform: process.platform, env: process.env }, undefined, {}, execOpts);
  }

  let justInstalled = false;
  let newerVersion = null;
  // Fire update check in background — result used on first menu render
  checkForUpdate(version).then(v => { newerVersion = v; }).catch(() => {});

  while (true) {
    printHeader();
    if (newerVersion) {
      output.write(`\n${c.yellow(`  ⬆  Nova versão disponível: ${c.bold(newerVersion)}`)}\n`);
      output.write(`   ${c.dim(`Execute: npm install -g sdd-fp@${newerVersion}`)}\n`);
    }

    const rl = readline.createInterface({ input, output });

    if (justInstalled) {
      output.write(`\n${c.green('✅ SDD instalado com sucesso!')}\n`);
      output.write(`${c.dim('Use o Assistente passo a passo para começar sua primeira feature.')}\n`);
    }

    let action;
    try {
      // showBack=false: main menu uses "Sair" instead of ← Voltar
      action = await select(rl, 'O que você quer fazer?', MAIN_MENU, justInstalled ? 1 : 0, false);
    } catch (err) {
      rl.close();
      throw err;
    }
    justInstalled = false;

    if (action === 'exit') {
      output.write('\nAté logo!\n\n');
      rl.close();
      return 0;
    }

    if (action === 'web') {
      const { runWeb } = await import('./web.js');
      rl.close();
      runWeb({ cwd: process.cwd() });
      return 0;
    }

    if (action === 'dashboard') {
      await runDashboard(rl);
      rl.close();
      continue;
    }

    if (action === 'ado') {
      const detectedKey = await detectInstalledAgent();
      const adoCmd = (AGENT_COMMANDS[detectedKey] ?? AGENT_COMMANDS['claude-code']).steeringAdo;
      try {
        await setupAdoToken(rl, adoCmd);
      } catch (err) {
        if (!(err instanceof GoBack)) { rl.close(); throw err; }
      }
      await rl.question(c.dim('\nPressione Enter para voltar ao menu principal...'));
      rl.close();
      continue;
    }

    if (action === 'instructions') {
      printInstructions();
      await rl.question(c.dim('Pressione Enter para voltar ao menu principal...'));
      rl.close();
      continue;
    }

    if (action === 'wizard') {
      let keepPlanning = true;
      while (keepPlanning) {
        let completed = false;
        try {
          await runWizard(rl);
          completed = true;
        } catch (err) {
          if (!(err instanceof GoBack)) { rl.close(); throw err; }
        }
        if (!completed) { keepPlanning = false; break; }
        const again = (await rl.question(
          `\n${c.dim('Planejar outra funcionalidade? [s/N]: ')}`,
        )).trim().toLowerCase();
        keepPlanning = again === 's' || again === 'sim';
        if (keepPlanning) console.clear();
      }
      await rl.question(c.dim('Pressione Enter para voltar ao menu principal...'));
      rl.close();
      continue;
    }

    // install flow
    let installResult;
    try {
      installResult = await runInstall(rl);
    } catch (err) {
      if (err instanceof GoBack) {
        rl.close();
        continue;  // GoBack from agent selection → back to main menu
      }
      rl.close();
      throw err;
    }

    rl.close();

    output.write(`\n${c.dim('Instalando SDD...')}\n\n`);

    const argv = ['--agent', installResult.agent, '--overwrite', installResult.overwrite];
    const exitCode = await runCli(argv, { platform: process.platform, env: process.env }, undefined, {}, execOpts);

    if (exitCode !== 0) return exitCode;

    output.write(`${c.dim('Configurando Chrome DevTools MCP...')}\n`);
    try {
      await writeMcpSettings(process.cwd());
      output.write(`${c.green('✓')} Chrome DevTools MCP adicionado ao ${c.dim('.claude/settings.json')}\n\n`);
    } catch (e) {
      output.write(`${c.yellow('⚠')} Não foi possível configurar MCP: ${e.message}\n\n`);
    }

    justInstalled = true;
  }
}
