#!/usr/bin/env node

/**
 * MORPH-SPEC Task Manager
 *
 * Manages task completion, dependencies, checkpoints, and progress tracking.
 * Part of MORPH-SPEC 3.0 - Event-driven state management.
 */

const fs = require('fs').promises;
const fsSync = require('fs');
const path = require('path');

// Simple ANSI color helpers (chalk v5 is ESM-only, can't require in CJS)
const chalk = {
  red: (s) => `\x1b[31m${s}\x1b[0m`,
  green: (s) => `\x1b[32m${s}\x1b[0m`,
  yellow: (s) => `\x1b[33m${s}\x1b[0m`,
  blue: (s) => `\x1b[34m${s}\x1b[0m`,
  magenta: (s) => `\x1b[35m${s}\x1b[0m`,
  cyan: (s) => `\x1b[36m${s}\x1b[0m`,
  gray: (s) => `\x1b[90m${s}\x1b[0m`,
  bold: (s) => `\x1b[1m${s}\x1b[0m`,
};

// ============================================================================
// v3 Schema Helpers
// ============================================================================

/**
 * Parse tasks.md to extract task stubs for v3 state format.
 * Looks for headings like: ### T001 — Task title
 */
async function parseTasksMd(featureName) {
  const tasksPath = path.join(process.cwd(), `.morph/project/outputs/${featureName}/tasks.md`);
  let content = '';
  try {
    content = await fs.readFile(tasksPath, 'utf-8');
  } catch {
    return [];
  }

  const tasks = [];
  const headingRe = /^###\s+(T\d+)\s+[—–-]\s+(.+)$/gm;
  let match;
  while ((match = headingRe.exec(content)) !== null) {
    tasks.push({
      id: match[1],
      title: match[2].trim(),
      status: 'pending',
      dependencies: [],
      files: [],
      checkpoint: null
    });
  }
  return tasks;
}

/**
 * Ensure feature.taskList exists (array of individual task objects).
 * In v3 state, feature.tasks is a counter object {total, completed, ...}.
 * Individual task objects live in feature.taskList.
 */
async function ensureTaskList(feature, featureName) {
  if (Array.isArray(feature.tasks)) {
    // v2 format: tasks IS the array
    return feature.tasks;
  }
  // v3 format: use taskList or build from tasks.md
  if (!feature.taskList || feature.taskList.length === 0) {
    feature.taskList = await parseTasksMd(featureName);
  }
  return feature.taskList;
}

/**
 * After modifying taskList, sync counts back to feature.tasks counter.
 */
function syncCounters(feature) {
  if (Array.isArray(feature.tasks)) return; // v2, nothing to sync
  const list = feature.taskList || [];
  feature.tasks.completed = list.filter(t => t.status === 'completed').length;
  feature.tasks.inProgress = list.filter(t => t.status === 'in_progress').length;
  feature.tasks.pending = list.filter(t => t.status === 'pending').length;
  // Don't touch feature.tasks.total — it is the authoritative count
}

class TaskManager {
  constructor(statePath = '.morph/state.json') {
    this.statePath = statePath;
  }

  /**
   * Load state.json
   */
  async loadState() {
    try {
      const content = await fs.readFile(this.statePath, 'utf-8');
      return JSON.parse(content);
    } catch (error) {
      if (error.code === 'ENOENT') {
        throw new Error(`State file not found: ${this.statePath}. Run 'npx morph-spec state init' first.`);
      }
      throw error;
    }
  }

  /**
   * Save state.json
   */
  async saveState(state) {
    // v3 state uses metadata.lastUpdated, v2 used project.updatedAt
    if (state.metadata) {
      state.metadata.lastUpdated = new Date().toISOString();
    } else if (state.project) {
      state.project.updatedAt = new Date().toISOString();
    }
    await fs.writeFile(this.statePath, JSON.stringify(state, null, 2), 'utf-8');
  }

  /**
   * Complete one or more tasks (runs validation first)
   * @param {string} featureName - Feature name
   * @param {string[]} taskIds - Task IDs to complete
   * @param {Object} options - Options
   * @param {boolean} options.skipValidation - Skip code validation
   */
  async completeTasks(featureName, taskIds, options = {}) {
    const state = await this.loadState();
    const feature = state.features[featureName];

    if (!feature) {
      throw new Error(`Feature '${featureName}' not found in state.json`);
    }

    const taskList = await ensureTaskList(feature, featureName);
    const results = [];
    const tasksToComplete = [];

    for (const taskId of taskIds) {
      const task = taskList.find(t => t.id === taskId);

      if (!task) {
        console.error(chalk.red(`❌ Task ${taskId} not found`));
        continue;
      }

      if (task.status === 'completed') {
        console.log(chalk.yellow(`⚠️  Task ${taskId} already completed`));
        continue;
      }

      // Validate dependencies
      const missingDeps = this.checkDependencies(task, taskList);
      if (missingDeps.length > 0) {
        console.error(chalk.red(`❌ Cannot complete ${taskId}: missing dependencies: ${missingDeps.join(', ')}`));
        continue;
      }

      tasksToComplete.push(task);
    }

    // Run validation BEFORE marking tasks as complete
    if (tasksToComplete.length > 0 && !options.skipValidation) {
      const validationPassed = await this.runValidation(featureName);
      if (!validationPassed) {
        console.error(chalk.red('\n❌ Validation failed — tasks NOT marked as complete'));
        console.log(chalk.gray('   Fix the issues above, then run task done again'));
        console.log(chalk.gray('   Or use --skip-validation to bypass (not recommended)\n'));
        process.exit(1);
      }
    }

    // Mark tasks as completed (only after validation passes)
    for (const task of tasksToComplete) {
      task.status = 'completed';
      task.completedAt = new Date().toISOString();
      task.completedBy = 'claude';

      results.push(task);

      console.log(chalk.green(`✅ Task ${task.id} completed!`));

      // Register checkpoint if task has one
      if (task.checkpoint) {
        this.registerCheckpoint(feature, task);
      }
    }

    // Sync v3 counters then compute progress
    syncCounters(feature);
    feature.progress = this.calculateProgress(taskList);

    // Auto-checkpoint every 3 tasks
    const recentCompleted = this.getRecentCompleted(taskList, 3);
    if (recentCompleted.length === 3) {
      const lastCheckpoint = feature.checkpoints[feature.checkpoints.length - 1];
      const shouldAutoCheckpoint = !lastCheckpoint ||
        !recentCompleted.every(t =>
          lastCheckpoint.tasksCompleted.includes(t.id)
        );

      if (shouldAutoCheckpoint) {
        await this.autoCheckpoint(feature, recentCompleted, featureName);
      }
    }

    // Save state
    await this.saveState(state);

    // Run TaskCompleted agent-teams hook for each completed task (non-blocking)
    for (const task of results) {
      try {
        const { executeHook, formatHookResults } = await import('../src/lib/hooks/hook-executor.js');
        const hookResult = await executeHook(process.cwd(), featureName, 'TaskCompleted', {
          taskId: task.id
        });
        if (!hookResult.passed && hookResult.errors.length > 0) {
          console.log(formatHookResults(hookResult, 'TaskCompleted'));
        }
      } catch {
        // Hook executor unavailable — non-blocking
      }
    }

    // Auto-generate metadata.json (for quick LLM access)
    await this.generateMetadata(featureName, feature);

    // Display progress
    this.displayProgress(feature);

    // Suggest next task
    const nextTask = this.getNextTask(taskList);
    if (nextTask) {
      console.log(chalk.cyan(`\n⏭️  Next: ${nextTask.id} - ${nextTask.title}`));
      if (nextTask.dependencies && nextTask.dependencies.length > 0) {
        console.log(chalk.gray(`   Dependencies: ${nextTask.dependencies.join(', ')}`));
      }
    } else {
      console.log(chalk.green('\n🎉 All tasks completed!'));
    }

    return results;
  }

  /**
   * Run validation for a feature using the ValidationRunner (ESM dynamic import)
   * @param {string} featureName - Feature name
   * @returns {boolean} True if validation passed
   */
  async runValidation(featureName) {
    try {
      console.log(chalk.cyan('\n🔍 Running code validation...'));

      const { runValidation, formatValidationResults } = await import('../src/lib/validators/validation-runner.js');
      const result = await runValidation('.', featureName, { verbose: true });

      formatValidationResults(result);
      return result.passed;
    } catch (error) {
      // If validation runner fails to load, warn but don't block
      console.log(chalk.yellow(`\n⚠️  Validation skipped: ${error.message}`));
      return true;
    }
  }

  /**
   * Check if task dependencies are met
   */
  checkDependencies(task, allTasks) {
    if (!task.dependencies || task.dependencies.length === 0) {
      return [];
    }

    return task.dependencies.filter(depId => {
      const dep = allTasks.find(t => t.id === depId);
      return !dep || dep.status !== 'completed';
    });
  }

  /**
   * Calculate progress
   */
  calculateProgress(tasks) {
    const total = tasks.length;
    const completed = tasks.filter(t => t.status === 'completed').length;
    const inProgress = tasks.filter(t => t.status === 'in_progress').length;
    const pending = tasks.filter(t => t.status === 'pending').length;
    const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;

    return {
      total,
      completed,
      inProgress,
      pending,
      percentage
    };
  }

  /**
   * Register checkpoint (from task flag)
   */
  registerCheckpoint(feature, task) {
    const checkpoint = {
      id: task.checkpoint,
      timestamp: new Date().toISOString(),
      tasksCompleted: [task.id],
      note: `Checkpoint: ${task.title}`
    };

    feature.checkpoints = feature.checkpoints || [];
    feature.checkpoints.push(checkpoint);

    console.log(chalk.magenta(`\n🎯 ${task.checkpoint} reached!`));
  }

  /**
   * Auto-checkpoint every 3 tasks (includes validation summary)
   */
  async autoCheckpoint(feature, tasks, featureName) {
    const tasksCompleted = feature.tasks?.completed || tasks.length;
    const checkpointNum = Math.floor(tasksCompleted / 3);

    console.log(chalk.magenta(`\n🔍 CHECKPOINT ${checkpointNum} - Running automated validation...`));

    let checkpointResult = null;
    let validationNote = '';

    // Run checkpoint hooks (new enhanced validation system)
    try {
      const { runCheckpointHooks, shouldRunCheckpoint } = await import('../src/lib/checkpoints/checkpoint-hooks.js');

      if (shouldRunCheckpoint(tasksCompleted, 3)) {
        checkpointResult = await runCheckpointHooks(featureName, checkpointNum);

        if (checkpointResult.passed) {
          validationNote = ' | ✓ All validations passed';
        } else {
          validationNote = ` | ✗ ${checkpointResult.summary.errors} errors, ${checkpointResult.summary.warnings} warnings`;

          // Check if we should block progress
          const config = await this.loadCheckpointConfig();
          if (config.checkpoints?.onFailure?.blockProgress && checkpointResult.summary.errors > 0) {
            console.log(chalk.red('\n❌ Checkpoint FAILED - Fix violations before proceeding'));
            throw new Error('Checkpoint validation failed');
          }
        }
      }
    } catch (error) {
      if (error.message === 'Checkpoint validation failed') {
        throw error; // Re-throw to block task completion
      }
      // Fallback to old validation if checkpoint-hooks not available
      console.log(chalk.yellow('⚠️  Checkpoint hooks not available, using legacy validation'));
      try {
        const { runValidation } = await import('../src/lib/validators/validation-runner.js');
        const result = await runValidation('.', featureName, { verbose: false });
        validationNote = result.passed
          ? ' | Validation: PASSED'
          : ` | Validation: ${result.errors.length} errors, ${result.warnings.length} warnings`;
      } catch {
        // No validation available
      }
    }

    // Create checkpoint record
    const checkpoint = {
      id: `CHECKPOINT_AUTO_${Date.now()}`,
      timestamp: new Date().toISOString(),
      tasksCompleted: tasks.map(t => t.id),
      note: `Auto-checkpoint: ${tasks.length} tasks completed${validationNote}`,
      validationResults: checkpointResult?.results || null
    };

    feature.checkpoints = feature.checkpoints || [];
    feature.checkpoints.push(checkpoint);

    if (checkpointResult?.passed) {
      console.log(chalk.green(`\n✅ Checkpoint ${checkpointNum} PASSED`));
    } else if (checkpointResult) {
      console.log(chalk.yellow(`\n⚠️  Checkpoint ${checkpointNum} completed with warnings`));
    } else {
      console.log(chalk.magenta(`\n🎉 Auto-checkpoint ${checkpointNum} reached!`));
    }
  }

  /**
   * Load checkpoint configuration from llm-interaction.json
   */
  async loadCheckpointConfig() {
    try {
      const configPath = path.join(process.cwd(), '.morph/config/llm-interaction.json');
      const content = await fs.readFile(configPath, 'utf-8');
      return JSON.parse(content);
    } catch {
      // Return defaults if config doesn't exist
      return {
        checkpoints: {
          frequency: 3,
          autoValidate: true,
          onFailure: {
            blockProgress: false
          }
        }
      };
    }
  }

  /**
   * Auto-generate metadata.json after task completion
   */
  async generateMetadata(featureName, feature) {
    try {
      const config = await this.loadCheckpointConfig();

      // Check if auto-generation is enabled
      if (!config.metadata?.autoGenerate) {
        return;
      }

      const { extractFeatureMetadata } = await import('../src/lib/generators/metadata-extractor.js');
      const metadata = extractFeatureMetadata(feature);

      const outputPath = path.join(
        process.cwd(),
        `.morph/project/outputs/${featureName}/metadata.json`
      );

      // Ensure directory exists
      const outputDir = path.dirname(outputPath);
      await fs.mkdir(outputDir, { recursive: true });

      // Write metadata
      await fs.writeFile(outputPath, JSON.stringify(metadata, null, 2), 'utf-8');

      console.log(chalk.gray(`   📊 Metadata updated: ${path.relative(process.cwd(), outputPath)}`));
    } catch (error) {
      // Don't block task completion if metadata generation fails
      console.log(chalk.yellow(`   ⚠️  Metadata generation failed: ${error.message}`));
    }
  }

  /**
   * Get recently completed tasks (last N)
   */
  getRecentCompleted(tasks, count) {
    const completed = tasks
      .filter(t => t.status === 'completed' && t.completedAt)
      .sort((a, b) => new Date(b.completedAt) - new Date(a.completedAt));

    return completed.slice(0, count);
  }

  /**
   * Get next pending task (based on dependencies)
   */
  getNextTask(tasks) {
    const pending = tasks.filter(t => t.status === 'pending');

    // Find first task with all dependencies completed
    for (const task of pending) {
      const missingDeps = this.checkDependencies(task, tasks);
      if (missingDeps.length === 0) {
        return task;
      }
    }

    return null;
  }

  /**
   * Display progress
   */
  displayProgress(feature) {
    const { total, completed, inProgress, pending, percentage } = feature.progress;

    console.log(chalk.bold(`\n📊 Progress: ${percentage}% (${completed}/${total})`));
    console.log(chalk.gray(`   Completed: ${completed} | In Progress: ${inProgress} | Pending: ${pending}`));

    // Progress bar
    const barLength = 30;
    const filledLength = Math.round((percentage / 100) * barLength);
    const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
    console.log(chalk.cyan(`   [${bar}] ${percentage}%`));
  }

  /**
   * Start a task (mark as in_progress)
   */
  async startTask(featureName, taskId) {
    const state = await this.loadState();
    const feature = state.features[featureName];

    if (!feature) {
      throw new Error(`Feature '${featureName}' not found`);
    }

    const taskList = await ensureTaskList(feature, featureName);
    const task = taskList.find(t => t.id === taskId);

    if (!task) {
      throw new Error(`Task ${taskId} not found`);
    }

    if (task.status === 'completed') {
      console.log(chalk.yellow(`⚠️  Task ${taskId} already completed`));
      return;
    }

    // Validate dependencies
    const missingDeps = this.checkDependencies(task, taskList);
    if (missingDeps.length > 0) {
      throw new Error(`Cannot start ${taskId}: missing dependencies: ${missingDeps.join(', ')}`);
    }

    task.status = 'in_progress';
    task.startedAt = new Date().toISOString();
    syncCounters(feature);

    await this.saveState(state);

    console.log(chalk.blue(`▶️  Task ${taskId} started: ${task.title}`));
  }

  /**
   * Get next task suggestion
   */
  async getNext(featureName) {
    const state = await this.loadState();
    const feature = state.features[featureName];

    if (!feature) {
      throw new Error(`Feature '${featureName}' not found`);
    }

    const taskList = await ensureTaskList(feature, featureName);
    const nextTask = this.getNextTask(taskList);

    if (nextTask) {
      console.log(chalk.cyan(`\n⏭️  Next task: ${nextTask.id} - ${nextTask.title}`));
      if (nextTask.dependencies && nextTask.dependencies.length > 0) {
        console.log(chalk.gray(`   Dependencies: ${nextTask.dependencies.join(', ')}`));
      }
      if (nextTask.files && nextTask.files.length > 0) {
        console.log(chalk.gray(`   Files:`));
        nextTask.files.forEach(f => console.log(chalk.gray(`     - ${f}`)));
      }
    } else {
      console.log(chalk.green('🎉 All tasks completed!'));
    }
  }
}

// CLI
async function main() {
  const args = process.argv.slice(2);
  const command = args[0];

  const manager = new TaskManager();

  try {
    switch (command) {
      case 'done':
      case 'complete': {
        const skipValidation = args.includes('--skip-validation');
        const filteredArgs = args.filter(a => a !== '--skip-validation');
        const featureName = filteredArgs[1];
        const taskIds = filteredArgs.slice(2);

        if (!featureName || taskIds.length === 0) {
          console.error(chalk.red('Usage: npx morph-spec task done <feature> <task-id> [task-id...] [--skip-validation]'));
          process.exit(1);
        }

        await manager.completeTasks(featureName, taskIds, { skipValidation });
        break;
      }

      case 'start': {
        const featureName = args[1];
        const taskId = args[2];

        if (!featureName || !taskId) {
          console.error(chalk.red('Usage: npx morph-spec task start <feature> <task-id>'));
          process.exit(1);
        }

        await manager.startTask(featureName, taskId);
        break;
      }

      case 'next': {
        const featureName = args[1];

        if (!featureName) {
          console.error(chalk.red('Usage: npx morph-spec task next <feature>'));
          process.exit(1);
        }

        await manager.getNext(featureName);
        break;
      }

      default:
        console.error(chalk.red(`Unknown command: ${command}`));
        console.log(chalk.gray('\nAvailable commands:'));
        console.log(chalk.gray('  done <feature> <task-id...>  - Mark tasks as completed'));
        console.log(chalk.gray('  start <feature> <task-id>    - Start a task (mark as in_progress)'));
        console.log(chalk.gray('  next <feature>               - Show next suggested task'));
        process.exit(1);
    }
  } catch (error) {
    console.error(chalk.red(`\n❌ Error: ${error.message}`));
    process.exit(1);
  }
}

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

module.exports = TaskManager;
