/**
 * Issue Command - Unified JSONL storage with CLI & API compatibility
 * Storage: issues.jsonl + solutions/{issue-id}.jsonl + queue.json
 * Commands: init, list, status, task, bind, queue, next, done, retry
 */

import chalk from 'chalk';
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
import { join, resolve } from 'path';

// Handle EPIPE errors gracefully
process.stdout.on('error', (err: NodeJS.ErrnoException) => {
  if (err.code === 'EPIPE') {
    process.exit(0);
  }
  throw err;
});

// ============ Interfaces ============

interface Issue {
  id: string;
  title: string;
  status: 'registered' | 'planning' | 'planned' | 'queued' | 'executing' | 'completed' | 'failed' | 'paused';
  priority: number;
  context: string;
  bound_solution_id: string | null;
  solution_count: number;
  source?: string;
  source_url?: string;
  labels?: string[];
  // Agent workflow fields
  affected_components?: string[];
  lifecycle_requirements?: {
    test_strategy?: 'unit' | 'integration' | 'e2e' | 'auto';
    regression_scope?: 'full' | 'related' | 'affected';
    commit_strategy?: 'per-task' | 'atomic' | 'squash';
  };
  problem_statement?: string;
  expected_behavior?: string;
  actual_behavior?: string;
  reproduction_steps?: string[];
  // Timestamps
  created_at: string;
  updated_at: string;
  planned_at?: string;
  queued_at?: string;
  completed_at?: string;
}

interface TaskTest {
  unit?: string[];              // Unit test requirements
  integration?: string[];       // Integration test requirements
  commands?: string[];          // Test commands to run
  coverage_target?: number;     // Minimum coverage % (optional)
}

interface TaskAcceptance {
  criteria: string[];           // Acceptance criteria (testable)
  verification: string[];       // How to verify each criterion
  manual_checks?: string[];     // Manual verification steps if needed
}

interface TaskCommit {
  type: 'feat' | 'fix' | 'refactor' | 'test' | 'docs' | 'chore';
  scope: string;                // Commit scope (e.g., "auth", "api")
  message_template: string;     // Commit message template
  breaking?: boolean;           // Breaking change flag
}

interface SolutionTask {
  id: string;
  title: string;
  scope: string;
  action: string;
  description?: string;
  modification_points?: { file: string; target: string; change: string }[];

  // Lifecycle phases (closed-loop)
  implementation: string[];     // Implementation steps
  test: TaskTest;               // Test requirements
  regression: string[];         // Regression check points
  acceptance: TaskAcceptance;   // Acceptance criteria & verification
  commit: TaskCommit;           // Commit specification

  depends_on: string[];
  estimated_minutes?: number;
  executor: 'codex' | 'gemini' | 'agent' | 'auto';

  // Lifecycle status tracking
  lifecycle_status?: {
    implemented: boolean;
    tested: boolean;
    regression_passed: boolean;
    accepted: boolean;
    committed: boolean;
  };
  status?: string;
  priority?: number;
}

interface Solution {
  id: string;
  description?: string;
  tasks: SolutionTask[];
  exploration_context?: Record<string, any>;
  analysis?: { risk?: string; impact?: string; complexity?: string };
  score?: number;
  is_bound: boolean;
  created_at: string;
  bound_at?: string;
}

interface QueueItem {
  item_id: string;               // Task item ID in queue: T-1, T-2, ... (formerly queue_id)
  issue_id: string;
  solution_id: string;
  task_id: string;
  title?: string;
  status: 'pending' | 'ready' | 'executing' | 'completed' | 'failed' | 'blocked';
  execution_order: number;
  execution_group: string;
  depends_on: string[];
  semantic_priority: number;
  assigned_executor: 'codex' | 'gemini' | 'agent';
  started_at?: string;
  completed_at?: string;
  result?: Record<string, any>;
  failure_reason?: string;
}

interface QueueConflict {
  type: 'file_conflict' | 'dependency_conflict' | 'resource_conflict';
  tasks: string[];               // Item IDs involved in conflict
  file?: string;                 // Conflicting file path
  resolution: 'sequential' | 'merge' | 'manual';
  resolution_order?: string[];
  rationale?: string;
  resolved: boolean;
}

interface ExecutionGroup {
  id: string;                    // Group ID: P1, S1, etc.
  type: 'parallel' | 'sequential';
  task_count: number;
  tasks: string[];               // Item IDs in this group
}

interface Queue {
  id: string;                    // Queue unique ID: QUE-YYYYMMDD-HHMMSS (derived from filename)
  name?: string;                 // Optional queue name
  status: 'active' | 'completed' | 'archived' | 'failed';
  issue_ids: string[];           // Issues in this queue
  tasks: QueueItem[];            // Task items (formerly 'queue')
  conflicts: QueueConflict[];
  execution_groups?: ExecutionGroup[];
  _metadata: {
    version: string;
    total_tasks: number;
    pending_count: number;
    executing_count: number;
    completed_count: number;
    failed_count: number;
    updated_at: string;
  };
}

interface QueueIndex {
  active_queue_id: string | null;
  active_item_id: string | null;
  queues: {
    id: string;
    status: string;
    issue_ids: string[];
    total_tasks: number;
    completed_tasks: number;
    created_at: string;
    completed_at?: string;
  }[];
}

interface IssueOptions {
  status?: string;
  title?: string;
  description?: string;
  executor?: string;
  priority?: string;
  solution?: string;
  result?: string;
  reason?: string;
  json?: boolean;
  force?: boolean;
  fail?: boolean;
  ids?: boolean;        // List only IDs (one per line)
}

const ISSUES_DIR = '.workflow/issues';

// ============ Storage Layer (JSONL) ============

function getProjectRoot(): string {
  let dir = process.cwd();
  while (dir !== resolve(dir, '..')) {
    if (existsSync(join(dir, '.workflow')) || existsSync(join(dir, '.git'))) {
      return dir;
    }
    dir = resolve(dir, '..');
  }
  return process.cwd();
}

function getIssuesDir(): string {
  return join(getProjectRoot(), ISSUES_DIR);
}

function ensureIssuesDir(): void {
  const dir = getIssuesDir();
  if (!existsSync(dir)) {
    mkdirSync(dir, { recursive: true });
  }
}

// ============ Issues JSONL ============

function readIssues(): Issue[] {
  const path = join(getIssuesDir(), 'issues.jsonl');
  if (!existsSync(path)) return [];
  try {
    return readFileSync(path, 'utf-8')
      .split('\n')
      .filter(line => line.trim())
      .map(line => JSON.parse(line));
  } catch {
    return [];
  }
}

function writeIssues(issues: Issue[]): void {
  ensureIssuesDir();
  const path = join(getIssuesDir(), 'issues.jsonl');
  writeFileSync(path, issues.map(i => JSON.stringify(i)).join('\n'), 'utf-8');
}

function findIssue(issueId: string): Issue | undefined {
  return readIssues().find(i => i.id === issueId);
}

function updateIssue(issueId: string, updates: Partial<Issue>): boolean {
  const issues = readIssues();
  const idx = issues.findIndex(i => i.id === issueId);
  if (idx === -1) return false;
  issues[idx] = { ...issues[idx], ...updates, updated_at: new Date().toISOString() };
  writeIssues(issues);
  return true;
}

// ============ Solutions JSONL ============

function getSolutionsPath(issueId: string): string {
  return join(getIssuesDir(), 'solutions', `${issueId}.jsonl`);
}

function readSolutions(issueId: string): Solution[] {
  const path = getSolutionsPath(issueId);
  if (!existsSync(path)) return [];
  try {
    return readFileSync(path, 'utf-8')
      .split('\n')
      .filter(line => line.trim())
      .map(line => JSON.parse(line));
  } catch {
    return [];
  }
}

function writeSolutions(issueId: string, solutions: Solution[]): void {
  const dir = join(getIssuesDir(), 'solutions');
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
  writeFileSync(getSolutionsPath(issueId), solutions.map(s => JSON.stringify(s)).join('\n'), 'utf-8');
}

function findSolution(issueId: string, solutionId: string): Solution | undefined {
  return readSolutions(issueId).find(s => s.id === solutionId);
}

function getBoundSolution(issueId: string): Solution | undefined {
  return readSolutions(issueId).find(s => s.is_bound);
}

function generateSolutionId(): string {
  const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
  return `SOL-${ts}`;
}

// ============ Queue Management (Multi-Queue) ============

function getQueuesDir(): string {
  return join(getIssuesDir(), 'queues');
}

function ensureQueuesDir(): void {
  const dir = getQueuesDir();
  if (!existsSync(dir)) {
    mkdirSync(dir, { recursive: true });
  }
}

function readQueueIndex(): QueueIndex {
  const path = join(getQueuesDir(), 'index.json');
  if (!existsSync(path)) {
    return { active_queue_id: null, active_item_id: null, queues: [] };
  }
  return JSON.parse(readFileSync(path, 'utf-8'));
}

function writeQueueIndex(index: QueueIndex): void {
  ensureQueuesDir();
  writeFileSync(join(getQueuesDir(), 'index.json'), JSON.stringify(index, null, 2), 'utf-8');
}

function generateQueueFileId(): string {
  const now = new Date();
  const ts = now.toISOString().replace(/[-:T]/g, '').slice(0, 14);
  return `QUE-${ts}`;
}

function readQueue(queueId?: string): Queue | null {
  const index = readQueueIndex();
  const targetId = queueId || index.active_queue_id;

  if (!targetId) return null;

  const path = join(getQueuesDir(), `${targetId}.json`);
  if (!existsSync(path)) return null;

  return JSON.parse(readFileSync(path, 'utf-8'));
}

function readActiveQueue(): Queue {
  const queue = readQueue();
  if (queue) return queue;

  // Return empty queue structure if no active queue
  return createEmptyQueue();
}

function createEmptyQueue(): Queue {
  return {
    id: generateQueueFileId(),
    status: 'active',
    issue_ids: [],
    tasks: [],
    conflicts: [],
    _metadata: {
      version: '2.1',
      total_tasks: 0,
      pending_count: 0,
      executing_count: 0,
      completed_count: 0,
      failed_count: 0,
      updated_at: new Date().toISOString()
    }
  };
}

function writeQueue(queue: Queue): void {
  ensureQueuesDir();

  // Update metadata counts
  queue._metadata.total_tasks = queue.tasks.length;
  queue._metadata.pending_count = queue.tasks.filter(q => q.status === 'pending').length;
  queue._metadata.executing_count = queue.tasks.filter(q => q.status === 'executing').length;
  queue._metadata.completed_count = queue.tasks.filter(q => q.status === 'completed').length;
  queue._metadata.failed_count = queue.tasks.filter(q => q.status === 'failed').length;
  queue._metadata.updated_at = new Date().toISOString();

  // Write queue file
  const path = join(getQueuesDir(), `${queue.id}.json`);
  writeFileSync(path, JSON.stringify(queue, null, 2), 'utf-8');

  // Update index
  const index = readQueueIndex();
  const existingIdx = index.queues.findIndex(q => q.id === queue.id);

  const indexEntry = {
    id: queue.id,
    status: queue.status,
    issue_ids: queue.issue_ids,
    total_tasks: queue._metadata.total_tasks,
    completed_tasks: queue._metadata.completed_count,
    created_at: queue.id.replace('QUE-', '').replace(/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/, '$1-$2-$3T$4:$5:$6Z'), // Derive from ID
    completed_at: queue.status === 'completed' ? new Date().toISOString() : undefined
  };

  if (existingIdx >= 0) {
    index.queues[existingIdx] = indexEntry;
  } else {
    index.queues.unshift(indexEntry);
  }

  if (queue.status === 'active') {
    index.active_queue_id = queue.id;
  }

  writeQueueIndex(index);
}

function generateQueueItemId(queue: Queue): string {
  const maxNum = queue.tasks.reduce((max, q) => {
    const match = q.item_id.match(/^T-(\d+)$/);
    return match ? Math.max(max, parseInt(match[1])) : max;
  }, 0);
  return `T-${maxNum + 1}`;
}

// ============ Commands ============

/**
 * init - Initialize a new issue
 */
async function initAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
  if (!issueId) {
    console.error(chalk.red('Issue ID is required'));
    console.error(chalk.gray('Usage: ccw issue init <issue-id> [--title "..."]'));
    process.exit(1);
  }

  const existing = findIssue(issueId);
  if (existing && !options.force) {
    console.error(chalk.red(`Issue "${issueId}" already exists`));
    console.error(chalk.gray('Use --force to reinitialize'));
    process.exit(1);
  }

  const issues = readIssues().filter(i => i.id !== issueId);
  const newIssue: Issue = {
    id: issueId,
    title: options.title || issueId,
    status: 'registered',
    priority: options.priority ? parseInt(options.priority) : 3,
    context: options.description || '',
    bound_solution_id: null,
    solution_count: 0,
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString()
  };

  issues.push(newIssue);
  writeIssues(issues);

  console.log(chalk.green(`✓ Issue "${issueId}" initialized`));
  console.log(chalk.gray(`  Next: ccw issue task ${issueId} --title "Task title"`));
}

/**
 * list - List issues or tasks
 */
async function listAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
  if (!issueId) {
    // List all issues
    let issues = readIssues();

    // Filter by status if specified
    if (options.status) {
      const statuses = options.status.split(',').map(s => s.trim());
      issues = issues.filter(i => statuses.includes(i.status));
    }

    // IDs only mode (one per line, for scripting)
    if (options.ids) {
      issues.forEach(i => console.log(i.id));
      return;
    }

    if (options.json) {
      console.log(JSON.stringify(issues, null, 2));
      return;
    }

    if (issues.length === 0) {
      console.log(chalk.yellow('No issues found'));
      console.log(chalk.gray('Create one with: ccw issue init <issue-id>'));
      return;
    }

    console.log(chalk.bold.cyan('\nIssues\n'));
    console.log(chalk.gray('ID'.padEnd(20) + 'Status'.padEnd(15) + 'Solutions'.padEnd(12) + 'Title'));
    console.log(chalk.gray('-'.repeat(70)));

    for (const issue of issues) {
      const statusColor = {
        'registered': chalk.gray,
        'planning': chalk.blue,
        'planned': chalk.cyan,
        'queued': chalk.yellow,
        'executing': chalk.yellow,
        'completed': chalk.green,
        'failed': chalk.red,
        'paused': chalk.magenta
      }[issue.status] || chalk.white;

      const bound = issue.bound_solution_id ? `[${issue.bound_solution_id}]` : `${issue.solution_count}`;
      console.log(
        issue.id.padEnd(20) +
        statusColor(issue.status.padEnd(15)) +
        bound.padEnd(12) +
        (issue.title || '').substring(0, 30)
      );
    }
    return;
  }

  // List tasks in bound solution
  const issue = findIssue(issueId);
  if (!issue) {
    console.error(chalk.red(`Issue "${issueId}" not found`));
    process.exit(1);
  }

  const solution = getBoundSolution(issueId);
  const tasks = solution?.tasks || [];

  if (options.json) {
    console.log(JSON.stringify({ issue, solution, tasks }, null, 2));
    return;
  }

  console.log(chalk.bold.cyan(`\nIssue: ${issueId}\n`));
  console.log(`Title: ${issue.title}`);
  console.log(`Status: ${issue.status}`);
  console.log(`Bound: ${issue.bound_solution_id || 'none'}`);
  console.log();

  if (tasks.length === 0) {
    console.log(chalk.yellow('No tasks (bind a solution first)'));
    return;
  }

  console.log(chalk.gray('ID'.padEnd(8) + 'Action'.padEnd(12) + 'Scope'.padEnd(20) + 'Title'));
  console.log(chalk.gray('-'.repeat(70)));

  for (const task of tasks) {
    console.log(
      task.id.padEnd(8) +
      task.action.padEnd(12) +
      task.scope.substring(0, 18).padEnd(20) +
      task.title.substring(0, 30)
    );
  }
}

/**
 * status - Show detailed status
 */
async function statusAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
  if (!issueId) {
    // Show queue status
    const queue = readActiveQueue();
    const issues = readIssues();
    const index = readQueueIndex();

    if (options.json) {
      // Return full queue for programmatic access
      console.log(JSON.stringify(queue, null, 2));
      return;
    }

    console.log(chalk.bold.cyan('\nSystem Status\n'));
    console.log(`Issues: ${issues.length}`);
    console.log(`Queues: ${index.queues.length} (Active: ${index.active_queue_id || 'none'})`);
    console.log(`Active Queue: ${queue._metadata.total_tasks} tasks`);
    console.log(`  Pending: ${queue._metadata.pending_count}`);
    console.log(`  Executing: ${queue._metadata.executing_count}`);
    console.log(`  Completed: ${queue._metadata.completed_count}`);
    console.log(`  Failed: ${queue._metadata.failed_count}`);
    return;
  }

  const issue = findIssue(issueId);
  if (!issue) {
    console.error(chalk.red(`Issue "${issueId}" not found`));
    process.exit(1);
  }

  const solutions = readSolutions(issueId);
  const boundSol = solutions.find(s => s.is_bound);

  if (options.json) {
    console.log(JSON.stringify({ issue, solutions, bound: boundSol }, null, 2));
    return;
  }

  console.log(chalk.bold.cyan(`\nIssue: ${issueId}\n`));
  console.log(`Title: ${issue.title}`);
  console.log(`Status: ${issue.status}`);
  console.log(`Priority: ${issue.priority}`);
  console.log(`Created: ${issue.created_at}`);
  console.log(`Updated: ${issue.updated_at}`);

  if (issue.context) {
    console.log();
    console.log(chalk.bold('Context:'));
    console.log(issue.context.substring(0, 200));
  }

  console.log();
  console.log(chalk.bold(`Solutions (${solutions.length}):`));
  for (const sol of solutions) {
    const marker = sol.is_bound ? chalk.green('◉') : chalk.gray('○');
    console.log(`  ${marker} ${sol.id}: ${sol.tasks.length} tasks`);
  }
}

/**
 * task - Add or update task (simplified - mainly for manual task management)
 */
async function taskAction(issueId: string | undefined, taskId: string | undefined, options: IssueOptions): Promise<void> {
  if (!issueId) {
    console.error(chalk.red('Issue ID is required'));
    console.error(chalk.gray('Usage: ccw issue task <issue-id> [task-id] --title "..."'));
    process.exit(1);
  }

  const issue = findIssue(issueId);
  if (!issue) {
    console.error(chalk.red(`Issue "${issueId}" not found`));
    process.exit(1);
  }

  const solutions = readSolutions(issueId);
  let boundIdx = solutions.findIndex(s => s.is_bound);

  // Create default solution if none bound
  if (boundIdx === -1) {
    const newSol: Solution = {
      id: generateSolutionId(),
      description: 'Manual tasks',
      tasks: [],
      is_bound: true,
      created_at: new Date().toISOString(),
      bound_at: new Date().toISOString()
    };
    solutions.push(newSol);
    boundIdx = solutions.length - 1;
    updateIssue(issueId, { bound_solution_id: newSol.id, status: 'planned' });
  }

  const solution = solutions[boundIdx];

  if (taskId) {
    // Update existing task
    const taskIdx = solution.tasks.findIndex(t => t.id === taskId);
    if (taskIdx === -1) {
      console.error(chalk.red(`Task "${taskId}" not found`));
      process.exit(1);
    }

    if (options.title) solution.tasks[taskIdx].title = options.title;
    if (options.status) solution.tasks[taskIdx].status = options.status;
    if (options.executor) solution.tasks[taskIdx].executor = options.executor as any;

    writeSolutions(issueId, solutions);
    console.log(chalk.green(`✓ Task ${taskId} updated`));
  } else {
    // Add new task
    if (!options.title) {
      console.error(chalk.red('Task title is required (--title)'));
      process.exit(1);
    }

    const newTaskId = `T${solution.tasks.length + 1}`;
    const newTask: SolutionTask = {
      id: newTaskId,
      title: options.title,
      scope: '',
      action: 'Implement',
      description: options.description || options.title,
      implementation: [],
      test: {
        unit: [],
        commands: ['npm test']
      },
      regression: ['npm test'],
      acceptance: {
        criteria: ['Task completed successfully'],
        verification: ['Manual verification']
      },
      commit: {
        type: 'feat',
        scope: 'core',
        message_template: `feat(core): ${options.title}`
      },
      depends_on: [],
      executor: (options.executor as any) || 'auto'
    };

    solution.tasks.push(newTask);
    writeSolutions(issueId, solutions);
    console.log(chalk.green(`✓ Task ${newTaskId} added to ${issueId}`));
  }
}

/**
 * bind - Register and/or bind a solution
 */
async function bindAction(issueId: string | undefined, solutionId: string | undefined, options: IssueOptions): Promise<void> {
  if (!issueId) {
    console.error(chalk.red('Issue ID is required'));
    console.error(chalk.gray('Usage: ccw issue bind <issue-id> [solution-id] [--solution <path>]'));
    process.exit(1);
  }

  const issue = findIssue(issueId);
  if (!issue) {
    console.error(chalk.red(`Issue "${issueId}" not found`));
    process.exit(1);
  }

  let solutions = readSolutions(issueId);

  // Register new solution from file if provided
  if (options.solution) {
    try {
      const content = readFileSync(options.solution, 'utf-8');
      const data = JSON.parse(content);
      const newSol: Solution = {
        id: solutionId || generateSolutionId(),
        description: data.description || data.approach_name || 'Imported solution',
        tasks: data.tasks || [],
        exploration_context: data.exploration_context,
        analysis: data.analysis,
        score: data.score,
        is_bound: false,
        created_at: new Date().toISOString()
      };
      solutions.push(newSol);
      solutionId = newSol.id;
      console.log(chalk.green(`✓ Solution ${solutionId} registered (${newSol.tasks.length} tasks)`));
    } catch (e) {
      console.error(chalk.red(`Failed to read solution file: ${options.solution}`));
      process.exit(1);
    }
  }

  if (!solutionId) {
    // List available solutions
    if (solutions.length === 0) {
      console.log(chalk.yellow('No solutions available'));
      console.log(chalk.gray('Register one: ccw issue bind <issue-id> --solution <path>'));
      return;
    }

    console.log(chalk.bold.cyan(`\nSolutions for ${issueId}:\n`));
    for (const sol of solutions) {
      const marker = sol.is_bound ? chalk.green('◉') : chalk.gray('○');
      console.log(`  ${marker} ${sol.id}: ${sol.tasks.length} tasks - ${sol.description || ''}`);
    }
    return;
  }

  // Bind the specified solution
  const solIdx = solutions.findIndex(s => s.id === solutionId);
  if (solIdx === -1) {
    console.error(chalk.red(`Solution "${solutionId}" not found`));
    process.exit(1);
  }

  // Unbind all, bind selected
  solutions = solutions.map(s => ({ ...s, is_bound: false }));
  solutions[solIdx].is_bound = true;
  solutions[solIdx].bound_at = new Date().toISOString();

  writeSolutions(issueId, solutions);
  updateIssue(issueId, {
    bound_solution_id: solutionId,
    solution_count: solutions.length,
    status: 'planned',
    planned_at: new Date().toISOString()
  });

  console.log(chalk.green(`✓ Solution ${solutionId} bound to ${issueId}`));
}

/**
 * queue - Queue management (list / add / history)
 */
async function queueAction(subAction: string | undefined, issueId: string | undefined, options: IssueOptions): Promise<void> {
  // List all queues (history)
  if (subAction === 'list' || subAction === 'history') {
    const index = readQueueIndex();

    if (options.json) {
      console.log(JSON.stringify(index, null, 2));
      return;
    }

    console.log(chalk.bold.cyan('\nQueue History\n'));
    console.log(chalk.gray(`Active: ${index.active_queue_id || 'none'}`));
    console.log();

    if (index.queues.length === 0) {
      console.log(chalk.yellow('No queues found'));
      console.log(chalk.gray('Create one: ccw issue queue add <issue-id>'));
      return;
    }

    console.log(chalk.gray('ID'.padEnd(22) + 'Status'.padEnd(12) + 'Tasks'.padEnd(10) + 'Issues'));
    console.log(chalk.gray('-'.repeat(70)));

    for (const q of index.queues) {
      const statusColor = {
        'active': chalk.green,
        'completed': chalk.cyan,
        'archived': chalk.gray,
        'failed': chalk.red
      }[q.status] || chalk.white;

      const marker = q.id === index.active_queue_id ? '→ ' : '  ';
      console.log(
        marker +
        q.id.padEnd(20) +
        statusColor(q.status.padEnd(12)) +
        `${q.completed_tasks}/${q.total_tasks}`.padEnd(10) +
        q.issue_ids.join(', ')
      );
    }
    return;
  }

  // Switch active queue
  if (subAction === 'switch' && issueId) {
    const queueId = issueId; // issueId is actually queue ID here
    const targetQueue = readQueue(queueId);

    if (!targetQueue) {
      console.error(chalk.red(`Queue "${queueId}" not found`));
      process.exit(1);
    }

    const index = readQueueIndex();
    index.active_queue_id = queueId;
    writeQueueIndex(index);

    console.log(chalk.green(`✓ Switched to queue ${queueId}`));
    return;
  }

  // DAG - Return dependency graph for parallel execution planning
  if (subAction === 'dag') {
    const queue = readActiveQueue();

    if (!queue.id || queue.tasks.length === 0) {
      console.log(JSON.stringify({ error: 'No active queue', nodes: [], edges: [], groups: [] }));
      return;
    }

    // Build DAG nodes
    const completedIds = new Set(queue.tasks.filter(t => t.status === 'completed').map(t => t.item_id));
    const failedIds = new Set(queue.tasks.filter(t => t.status === 'failed').map(t => t.item_id));

    const nodes = queue.tasks.map(task => ({
      id: task.item_id,
      issue_id: task.issue_id,
      task_id: task.task_id,
      status: task.status,
      executor: task.assigned_executor,
      priority: task.semantic_priority,
      depends_on: task.depends_on,
      // Calculate if ready (dependencies satisfied)
      ready: task.status === 'pending' && task.depends_on.every(d => completedIds.has(d)),
      blocked_by: task.depends_on.filter(d => !completedIds.has(d) && !failedIds.has(d))
    }));

    // Build edges for visualization
    const edges = queue.tasks.flatMap(task =>
      task.depends_on.map(dep => ({ from: dep, to: task.item_id }))
    );

    // Group ready tasks by execution_group for parallel execution
    const readyTasks = nodes.filter(n => n.ready || n.status === 'executing');
    const groups: Record<string, string[]> = {};

    for (const task of queue.tasks) {
      if (readyTasks.some(r => r.id === task.item_id)) {
        const group = task.execution_group || 'P1';
        if (!groups[group]) groups[group] = [];
        groups[group].push(task.item_id);
      }
    }

    // Calculate parallel batches (tasks with no dependencies on each other)
    const parallelBatches: string[][] = [];
    const remainingReady = new Set(readyTasks.map(t => t.id));

    while (remainingReady.size > 0) {
      const batch: string[] = [];
      const batchFiles = new Set<string>();

      for (const taskId of remainingReady) {
        const task = queue.tasks.find(t => t.item_id === taskId);
        if (!task) continue;

        // Check for file conflicts with already-batched tasks
        const solution = findSolution(task.issue_id, task.solution_id);
        const taskDef = solution?.tasks.find(t => t.id === task.task_id);
        const taskFiles = taskDef?.modification_points?.map(mp => mp.file) || [];

        const hasConflict = taskFiles.some(f => batchFiles.has(f));

        if (!hasConflict) {
          batch.push(taskId);
          taskFiles.forEach(f => batchFiles.add(f));
        }
      }

      if (batch.length === 0) {
        // Fallback: take one at a time if all conflict
        const first = Array.from(remainingReady)[0];
        batch.push(first);
      }

      parallelBatches.push(batch);
      batch.forEach(id => remainingReady.delete(id));
    }

    console.log(JSON.stringify({
      queue_id: queue.id,
      total: nodes.length,
      ready_count: readyTasks.length,
      completed_count: completedIds.size,
      nodes,
      edges,
      groups: Object.entries(groups).map(([id, tasks]) => ({ id, tasks })),
      parallel_batches: parallelBatches,
      _summary: {
        can_parallel: parallelBatches[0]?.length || 0,
        batches_needed: parallelBatches.length
      }
    }, null, 2));
    return;
  }

  // Archive current queue
  if (subAction === 'archive') {
    const queue = readActiveQueue();
    if (!queue.id || queue.tasks.length === 0) {
      console.log(chalk.yellow('No active queue to archive'));
      return;
    }

    queue.status = 'archived';
    writeQueue(queue);

    const index = readQueueIndex();
    index.active_queue_id = null;
    writeQueueIndex(index);

    console.log(chalk.green(`✓ Archived queue ${queue.id}`));
    return;
  }

  // Delete queue from history
  if ((subAction === 'clear' || subAction === 'delete') && issueId) {
    const queueId = issueId; // issueId is actually queue ID here
    const queuePath = join(getQueuesDir(), `${queueId}.json`);

    if (!existsSync(queuePath)) {
      console.error(chalk.red(`Queue "${queueId}" not found`));
      process.exit(1);
    }

    // Remove from index
    const index = readQueueIndex();
    index.queues = index.queues.filter(q => q.id !== queueId);
    if (index.active_queue_id === queueId) {
      index.active_queue_id = null;
    }
    writeQueueIndex(index);

    // Delete queue file
    unlinkSync(queuePath);

    console.log(chalk.green(`✓ Deleted queue ${queueId}`));
    return;
  }

  // Add issue tasks to queue
  if (subAction === 'add' && issueId) {
    const issue = findIssue(issueId);
    if (!issue) {
      console.error(chalk.red(`Issue "${issueId}" not found`));
      process.exit(1);
    }

    const solution = getBoundSolution(issueId);
    if (!solution) {
      console.error(chalk.red(`No bound solution for "${issueId}"`));
      console.error(chalk.gray('First bind a solution: ccw issue bind <issue-id> <solution-id>'));
      process.exit(1);
    }

    // Get or create active queue (create new if current is completed/archived)
    let queue = readActiveQueue();
    const isNewQueue = queue.tasks.length === 0 || queue.status !== 'active';

    if (queue.status !== 'active') {
      // Create new queue if current is not active
      queue = createEmptyQueue();
    }

    // Add issue to queue's issue list
    if (!queue.issue_ids.includes(issueId)) {
      queue.issue_ids.push(issueId);
    }

    let added = 0;
    for (const task of solution.tasks) {
      const exists = queue.tasks.some(q => q.issue_id === issueId && q.task_id === task.id);
      if (exists) continue;

      queue.tasks.push({
        item_id: generateQueueItemId(queue),
        issue_id: issueId,
        solution_id: solution.id,
        task_id: task.id,
        status: 'pending',
        execution_order: queue.tasks.length + 1,
        execution_group: 'P1',
        depends_on: task.depends_on.map(dep => {
          const depItem = queue.tasks.find(q => q.task_id === dep && q.issue_id === issueId);
          return depItem?.item_id || dep;
        }),
        semantic_priority: 0.5,
        assigned_executor: task.executor === 'auto' ? 'codex' : task.executor as any
      });
      added++;
    }

    writeQueue(queue);
    updateIssue(issueId, { status: 'queued', queued_at: new Date().toISOString() });

    if (isNewQueue) {
      console.log(chalk.green(`✓ Created queue ${queue.id}`));
    }
    console.log(chalk.green(`✓ Added ${added} tasks from ${solution.id}`));
    return;
  }

  // Show current queue
  const queue = readActiveQueue();

  if (options.json) {
    console.log(JSON.stringify(queue, null, 2));
    return;
  }

  console.log(chalk.bold.cyan('\nActive Queue\n'));

  if (!queue.id || queue.tasks.length === 0) {
    console.log(chalk.yellow('No active queue'));
    console.log(chalk.gray('Create one: ccw issue queue add <issue-id>'));
    console.log(chalk.gray('Or list history: ccw issue queue list'));
    return;
  }

  console.log(chalk.gray(`Queue: ${queue.id}`));
  console.log(chalk.gray(`Issues: ${queue.issue_ids.join(', ')}`));
  console.log(chalk.gray(`Total: ${queue._metadata.total_tasks} | Pending: ${queue._metadata.pending_count} | Executing: ${queue._metadata.executing_count} | Completed: ${queue._metadata.completed_count}`));
  console.log();

  console.log(chalk.gray('QueueID'.padEnd(10) + 'Issue'.padEnd(15) + 'Task'.padEnd(8) + 'Status'.padEnd(12) + 'Executor'));
  console.log(chalk.gray('-'.repeat(60)));

  for (const item of queue.tasks) {
    const statusColor = {
      'pending': chalk.gray,
      'ready': chalk.cyan,
      'executing': chalk.yellow,
      'completed': chalk.green,
      'failed': chalk.red,
      'blocked': chalk.magenta
    }[item.status] || chalk.white;

    console.log(
      item.item_id.padEnd(10) +
      item.issue_id.substring(0, 13).padEnd(15) +
      item.task_id.padEnd(8) +
      statusColor(item.status.padEnd(12)) +
      item.assigned_executor
    );
  }
}

/**
 * next - Get next ready task for execution (JSON output)
 * Accepts optional item_id to fetch a specific task directly
 */
async function nextAction(itemId: string | undefined, options: IssueOptions): Promise<void> {
  const queue = readActiveQueue();
  let nextItem: typeof queue.tasks[0] | undefined;
  let isResume = false;

  // If specific item_id provided, fetch that task directly
  if (itemId) {
    nextItem = queue.tasks.find(t => t.item_id === itemId);
    if (!nextItem) {
      console.log(JSON.stringify({ status: 'error', message: `Task ${itemId} not found` }));
      return;
    }
    if (nextItem.status === 'completed') {
      console.log(JSON.stringify({ status: 'completed', message: `Task ${itemId} already completed` }));
      return;
    }
    if (nextItem.status === 'failed') {
      console.log(JSON.stringify({ status: 'failed', message: `Task ${itemId} failed, use retry to reset` }));
      return;
    }
    isResume = nextItem.status === 'executing';
  } else {
    // Auto-select: Priority 1 - executing, Priority 2 - ready pending
    const executingTasks = queue.tasks.filter(item => item.status === 'executing');
    const pendingTasks = queue.tasks.filter(item => {
      if (item.status !== 'pending') return false;
      return item.depends_on.every(depId => {
        const dep = queue.tasks.find(q => q.item_id === depId);
        return !dep || dep.status === 'completed';
      });
    });

    const readyTasks = [...executingTasks, ...pendingTasks];

    if (readyTasks.length === 0) {
      console.log(JSON.stringify({
        status: 'empty',
        message: 'No ready tasks',
        queue_status: queue._metadata
      }, null, 2));
      return;
    }

    readyTasks.sort((a, b) => a.execution_order - b.execution_order);
    nextItem = readyTasks[0];
    isResume = nextItem.status === 'executing';
  }

  // Load task definition
  const solution = findSolution(nextItem.issue_id, nextItem.solution_id);
  const taskDef = solution?.tasks.find(t => t.id === nextItem.task_id);

  if (!taskDef) {
    console.log(JSON.stringify({ status: 'error', message: 'Task definition not found' }));
    process.exit(1);
  }

  // Only update status if not already executing (new task)
  if (!isResume) {
    const idx = queue.tasks.findIndex(q => q.item_id === nextItem.item_id);
    queue.tasks[idx].status = 'executing';
    queue.tasks[idx].started_at = new Date().toISOString();
    writeQueue(queue);
    updateIssue(nextItem.issue_id, { status: 'executing' });
  }

  // Calculate queue stats for context
  const stats = {
    total: queue.tasks.length,
    completed: queue.tasks.filter(q => q.status === 'completed').length,
    failed: queue.tasks.filter(q => q.status === 'failed').length,
    executing: queue.tasks.filter(q => q.status === 'executing').length,
    pending: queue.tasks.filter(q => q.status === 'pending').length
  };
  const remaining = stats.pending + stats.executing;

  console.log(JSON.stringify({
    item_id: nextItem.item_id,
    issue_id: nextItem.issue_id,
    solution_id: nextItem.solution_id,
    task: taskDef,
    context: solution?.exploration_context || {},
    resumed: isResume,
    resume_note: isResume ? `Resuming interrupted task (started: ${nextItem.started_at})` : undefined,
    execution_hints: {
      executor: nextItem.assigned_executor,
      estimated_minutes: taskDef.estimated_minutes || 30
    },
    queue_progress: {
      completed: stats.completed,
      remaining: remaining,
      total: stats.total,
      progress: `${stats.completed}/${stats.total}`
    }
  }, null, 2));
}

/**
 * done - Mark task completed or failed
 */
async function doneAction(queueId: string | undefined, options: IssueOptions): Promise<void> {
  if (!queueId) {
    console.error(chalk.red('Queue ID is required'));
    console.error(chalk.gray('Usage: ccw issue done <queue-id> [--fail] [--reason "..."]'));
    process.exit(1);
  }

  const queue = readActiveQueue();
  const idx = queue.tasks.findIndex(q => q.item_id === queueId);

  if (idx === -1) {
    console.error(chalk.red(`Queue item "${queueId}" not found`));
    process.exit(1);
  }

  const isFail = options.fail;
  queue.tasks[idx].status = isFail ? 'failed' : 'completed';
  queue.tasks[idx].completed_at = new Date().toISOString();

  if (isFail) {
    queue.tasks[idx].failure_reason = options.reason || 'Unknown failure';
  } else if (options.result) {
    try {
      queue.tasks[idx].result = JSON.parse(options.result);
    } catch {
      console.warn(chalk.yellow('Warning: Could not parse result JSON'));
    }
  }

  // Check if all issue tasks are complete
  const issueId = queue.tasks[idx].issue_id;
  const issueTasks = queue.tasks.filter(q => q.issue_id === issueId);
  const allIssueComplete = issueTasks.every(q => q.status === 'completed');
  const anyIssueFailed = issueTasks.some(q => q.status === 'failed');

  if (allIssueComplete) {
    updateIssue(issueId, { status: 'completed', completed_at: new Date().toISOString() });
    console.log(chalk.green(`✓ ${queueId} completed`));
    console.log(chalk.green(`✓ Issue ${issueId} completed (all tasks done)`));
  } else if (anyIssueFailed) {
    updateIssue(issueId, { status: 'failed' });
    console.log(chalk.red(`✗ ${queueId} failed`));
  } else {
    console.log(isFail ? chalk.red(`✗ ${queueId} failed`) : chalk.green(`✓ ${queueId} completed`));
  }

  // Check if entire queue is complete
  const allQueueComplete = queue.tasks.every(q => q.status === 'completed');
  const anyQueueFailed = queue.tasks.some(q => q.status === 'failed');

  if (allQueueComplete) {
    queue.status = 'completed';
    console.log(chalk.green(`\n✓ Queue ${queue.id} completed (all tasks done)`));
  } else if (anyQueueFailed && queue.tasks.every(q => q.status === 'completed' || q.status === 'failed')) {
    queue.status = 'failed';
    console.log(chalk.yellow(`\n⚠ Queue ${queue.id} has failed tasks`));
  }

  writeQueue(queue);
}

/**
 * retry - Reset failed tasks to pending for re-execution
 */
async function retryAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
  const queue = readActiveQueue();

  if (!queue.id || queue.tasks.length === 0) {
    console.log(chalk.yellow('No active queue'));
    return;
  }

  let updated = 0;

  for (const item of queue.tasks) {
    // Retry failed tasks only
    if (item.status === 'failed') {
      if (!issueId || item.issue_id === issueId) {
        item.status = 'pending';
        item.failure_reason = undefined;
        item.started_at = undefined;
        item.completed_at = undefined;
        updated++;
      }
    }
  }

  if (updated === 0) {
    console.log(chalk.yellow('No failed tasks to retry'));
    console.log(chalk.gray('Note: Interrupted (executing) tasks are auto-resumed by "ccw issue next"'));
    return;
  }

  // Reset queue status if it was failed
  if (queue.status === 'failed') {
    queue.status = 'active';
  }

  writeQueue(queue);

  if (issueId) {
    updateIssue(issueId, { status: 'queued' });
  }

  console.log(chalk.green(`✓ Reset ${updated} task(s) to pending`));
}

// ============ Main Entry ============

export async function issueCommand(
  subcommand: string,
  args: string | string[],
  options: IssueOptions
): Promise<void> {
  const argsArray = Array.isArray(args) ? args : (args ? [args] : []);

  switch (subcommand) {
    case 'init':
      await initAction(argsArray[0], options);
      break;
    case 'list':
      await listAction(argsArray[0], options);
      break;
    case 'status':
      await statusAction(argsArray[0], options);
      break;
    case 'task':
      await taskAction(argsArray[0], argsArray[1], options);
      break;
    case 'bind':
      await bindAction(argsArray[0], argsArray[1], options);
      break;
    case 'queue':
      await queueAction(argsArray[0], argsArray[1], options);
      break;
    case 'next':
      await nextAction(argsArray[0], options);
      break;
    case 'done':
      await doneAction(argsArray[0], options);
      break;
    case 'retry':
      await retryAction(argsArray[0], options);
      break;
    // Legacy aliases
    case 'register':
      console.log(chalk.yellow('Deprecated: use "ccw issue bind <issue-id> --solution <path>"'));
      await bindAction(argsArray[0], undefined, options);
      break;
    case 'complete':
      await doneAction(argsArray[0], options);
      break;
    case 'fail':
      await doneAction(argsArray[0], { ...options, fail: true });
      break;
    default:
      console.log(chalk.bold.cyan('\nCCW Issue Management (v3.0 - Multi-Queue + Lifecycle)\n'));
      console.log(chalk.bold('Core Commands:'));
      console.log(chalk.gray('  init <issue-id>                    Initialize new issue'));
      console.log(chalk.gray('  list [issue-id]                    List issues or tasks'));
      console.log(chalk.gray('  status [issue-id]                  Show detailed status'));
      console.log(chalk.gray('  task <issue-id> [task-id]          Add or update task'));
      console.log(chalk.gray('  bind <issue-id> [sol-id]           Bind solution (--solution <path> to register)'));
      console.log();
      console.log(chalk.bold('Queue Commands:'));
      console.log(chalk.gray('  queue                              Show active queue'));
      console.log(chalk.gray('  queue list                         List all queues (history)'));
      console.log(chalk.gray('  queue add <issue-id>               Add issue to active queue (or create new)'));
      console.log(chalk.gray('  queue switch <queue-id>            Switch active queue'));
      console.log(chalk.gray('  queue dag                          Get dependency graph (JSON) for parallel execution'));
      console.log(chalk.gray('  queue archive                      Archive current queue'));
      console.log(chalk.gray('  queue delete <queue-id>            Delete queue from history'));
      console.log(chalk.gray('  retry [issue-id]                   Retry failed tasks'));
      console.log();
      console.log(chalk.bold('Execution Endpoints:'));
      console.log(chalk.gray('  next [item-id]                     Get task by ID or next ready task (JSON)'));
      console.log(chalk.gray('  done <item-id>                     Mark task completed'));
      console.log(chalk.gray('  done <item-id> --fail              Mark task failed'));
      console.log();
      console.log(chalk.bold('Options:'));
      console.log(chalk.gray('  --title <title>                    Issue/task title'));
      console.log(chalk.gray('  --status <status>                  Filter by status (comma-separated)'));
      console.log(chalk.gray('  --ids                              List only IDs (one per line)'));
      console.log(chalk.gray('  --solution <path>                  Solution JSON file'));
      console.log(chalk.gray('  --result <json>                    Execution result'));
      console.log(chalk.gray('  --reason <text>                    Failure reason'));
      console.log(chalk.gray('  --json                             JSON output'));
      console.log(chalk.gray('  --force                            Force operation'));
      console.log();
      console.log(chalk.bold('Storage:'));
      console.log(chalk.gray('  .workflow/issues/issues.jsonl      All issues'));
      console.log(chalk.gray('  .workflow/issues/solutions/*.jsonl Solutions per issue'));
      console.log(chalk.gray('  .workflow/issues/queues/           Queue files (multi-queue)'));
      console.log(chalk.gray('  .workflow/issues/queues/index.json Queue index'));
  }
}
