/**
 * Plan Decomposer
 *
 * Decomposes an approved spec into ordered, testable steps with dependency tracking.
 * Steps can be executed sequentially or in parallel based on dependencies.
 *
 * Flow: Spec -> [Step, Step, ...] (Plan)
 */

import { logForDebugging } from '../../utils/debug.js'
import type { FlowSpec, FlowPlan, PlanStep, FlowId } from './types.js'
import { createPlanId, FLOW_DEFAULTS } from './types.js'

/**
 * Decompose an approved spec into a plan with ordered steps.
 *
 * In production, this would use a subagent to analyze the spec against
 * the codebase and generate precise steps. This implementation provides
 * the structural framework.
 */
export function decomposeSpec(
  flowId: FlowId,
  spec: FlowSpec,
): FlowPlan {
  const planId = createPlanId(flowId)
  const steps = generateSteps(spec)

  // Build dependency graph and parallel groups
  const parallelGroups = computeParallelGroups(steps)

  const plan: FlowPlan = {
    id: planId,
    flowId,
    specId: spec.id,
    steps,
    parallelGroups,
    createdAt: Date.now(),
  }

  logForDebugging(`Plan decomposed: ${planId} with ${steps.length} steps, ${parallelGroups.length} parallel groups`)
  return plan
}

/**
 * Generate steps from a spec.
 *
 * The step generation follows a consistent pattern:
 * 1. Setup/preparation (create files, install deps)
 * 2. Core implementation (write the main logic)
 * 3. Integration (connect to existing code)
 * 4. Testing (write tests)
 * 5. Verification (run full suite, lint, type-check)
 */
function generateSteps(spec: FlowSpec): PlanStep[] {
  const steps: PlanStep[] = []
  let index = 0

  // Step: Setup
  if (spec.type === 'feature' && spec.scope.estimatedFiles > 3) {
    steps.push(createStep({
      index: index++,
      description: `Set up project structure for: ${spec.intent}`,
      files: [],
      passCriteria: ['New files/directories created', 'Dependencies installed if needed'],
      dependsOn: [],
    }))
  }

  // Step: Core implementation
  const coreFiles = inferCoreFiles(spec)
  steps.push(createStep({
    index: index++,
    description: `Implement core logic for: ${spec.intent}`,
    files: coreFiles,
    passCriteria: [
      'Core functionality implemented',
      'Type-checks pass',
      'Handles basic happy path',
    ],
    dependsOn: index > 1 ? [0] : [],
  }))

  // Step: Edge cases and error handling
  if (spec.edgeCases.length > 0) {
    steps.push(createStep({
      index: index++,
      description: `Handle edge cases: ${spec.edgeCases.join(', ')}`,
      files: coreFiles,
      passCriteria: spec.edgeCases.map(edge => `Handles: ${edge}`),
      dependsOn: [index - 1],
    }))
  }

  // Step: Integration
  if (spec.scope.estimatedFiles > 2) {
    steps.push(createStep({
      index: index++,
      description: `Integrate with existing codebase`,
      files: spec.affectedFiles.length > 0 ? spec.affectedFiles : [],
      passCriteria: [
        'Integrated with existing modules',
        'No import/export errors',
        'Existing functionality not broken',
      ],
      dependsOn: [index - 1],
    }))
  }

  // Step: Tests
  steps.push(createStep({
    index: index++,
    description: `Write tests for: ${spec.intent}`,
    files: inferTestFiles(spec, coreFiles),
    passCriteria: [
      'Test file(s) created',
      'Tests cover happy path',
      'Tests cover edge cases',
      'All new tests pass',
    ],
    dependsOn: [index - 1],
  }))

  // Step: Full verification
  steps.push(createStep({
    index: index,
    description: `Final verification: run all tests, lint, type-check`,
    files: [],
    passCriteria: [
      'Full test suite passes',
      'Lint passes',
      'Type-check passes',
      ...spec.acceptanceCriteria.map(c => c.description),
    ],
    dependsOn: [index - 1],
  }))

  return steps
}

/**
 * Create a plan step with defaults.
 */
function createStep(params: {
  index: number
  description: string
  files: string[]
  passCriteria: string[]
  dependsOn: number[]
}): PlanStep {
  return {
    index: params.index,
    description: params.description,
    files: params.files,
    passCriteria: params.passCriteria,
    dependsOn: params.dependsOn,
    status: 'pending',
    iterations: 0,
    maxIterations: FLOW_DEFAULTS.maxIterationsPerStep,
    changedFiles: [],
  }
}

/**
 * Infer core implementation files from the spec.
 */
function inferCoreFiles(spec: FlowSpec): string[] {
  // In production, subagent would analyze codebase to determine exact files
  // For now, return the spec's affected files or empty (AI will determine)
  if (spec.affectedFiles.length > 0) {
    return spec.affectedFiles
  }

  // Heuristic based on intent
  const lower = spec.intent.toLowerCase()
  const files: string[] = []

  if (/\b(route|endpoint|api)\b/.test(lower)) {
    files.push('src/routes/')
  }
  if (/\b(service|logic|handler)\b/.test(lower)) {
    files.push('src/services/')
  }
  if (/\b(model|schema|type)\b/.test(lower)) {
    files.push('src/models/')
  }
  if (/\b(middleware|auth|guard)\b/.test(lower)) {
    files.push('src/middleware/')
  }

  return files
}

/**
 * Infer test file paths from spec and core files.
 */
function inferTestFiles(spec: FlowSpec, coreFiles: string[]): string[] {
  return coreFiles
    .filter(f => !f.includes('test') && !f.includes('spec'))
    .map(f => {
      // Replace src/ with test/ or append .test
      if (f.startsWith('src/')) {
        return f.replace('src/', 'test/')
      }
      return `${f}.test`
    })
}

/**
 * Compute which steps can run in parallel.
 *
 * Steps with no dependencies between them can run concurrently.
 * Returns groups of step indices that can be parallelized.
 */
function computeParallelGroups(steps: PlanStep[]): number[][] {
  const groups: number[][] = []
  const assigned = new Set<number>()

  // Greedy algorithm: group steps that have the same dependencies
  for (let i = 0; i < steps.length; i++) {
    if (assigned.has(i)) continue

    const group = [i]
    assigned.add(i)

    // Find other steps with same dependencies that haven't been assigned
    for (let j = i + 1; j < steps.length; j++) {
      if (assigned.has(j)) continue

      const depsI = steps[i]?.dependsOn ?? []
      const depsJ = steps[j]?.dependsOn ?? []

      // Steps can be parallel if neither depends on the other
      // and they share the same dependency set
      if (depsI.length === depsJ.length && !depsI.some(d => j === d) && !depsJ.some(d => i === d)) {
        // Check if they're truly independent (neither depends on the other's group)
        const isIndependent = !group.some(g => (steps[j]?.dependsOn ?? []).includes(g))
        if (isIndependent && depsI.join(',') === depsJ.join(',')) {
          group.push(j)
          assigned.add(j)
        }
      }
    }

    groups.push(group)
  }

  return groups
}

/**
 * Get the next step to execute in the plan.
 * Returns null if all steps are complete or a step needs escalation.
 */
export function getNextStep(plan: FlowPlan): PlanStep | null {
  // Find first pending step whose dependencies are all completed
  for (const step of plan.steps) {
    if (step.status !== 'pending') continue

    const depsComplete = step.dependsOn.every(depIndex => {
      const dep = plan.steps[depIndex]
      return dep?.status === 'completed'
    })

    if (depsComplete) {
      return step
    }
  }

  // Check if there are any in-progress steps
  const inProgress = plan.steps.find(s => s.status === 'in-progress' || s.status === 'verifying' || s.status === 'fixing')
  return inProgress ?? null
}

/**
 * Get the overall progress of a plan as a fraction.
 */
export function getPlanProgress(plan: FlowPlan): { completed: number; total: number; percentage: number } {
  const total = plan.steps.length
  const completed = plan.steps.filter(s => s.status === 'completed').length
  const percentage = total > 0 ? Math.round((completed / total) * 100) : 0
  return { completed, total, percentage }
}
