/**
 * Decomposition Merger - Natural Deduplication Through Context Refinement
 *
 * This merger implements sequential context passing where each decomposer
 * naturally refines the previous output instead of creating duplicates.
 *
 * Key principle: NO explicit deduplication rules. The refinement happens
 * naturally as context flows through the chain:
 *   Architecture → Security → Performance → Testing
 *
 * Each stage receives the previous output and refines it, adding constraints
 * and additional micro-tasks as needed.
 *
 * @module decomposition-merger
 * @version 1.0.0
 */

// =============================================
// Type Definitions
// =============================================

export interface MicroTaskConstraint {
  perspective: "architecture" | "security" | "performance" | "testing";
  description: string;
  rationale: string;
  additionalContext?: string;
}

export interface RefinedMicroTask {
  id: string;
  title: string;
  description: string;
  priority: "critical" | "high" | "medium" | "low";

  // Constraints accumulated from all perspectives
  constraints: {
    architecture?: MicroTaskConstraint;
    security?: MicroTaskConstraint;
    performance?: MicroTaskConstraint;
    testing?: MicroTaskConstraint;
  };

  dependencies: string[];
  estimatedEffort: "small" | "medium" | "large";

  // Track refinement history
  refinementHistory: Array<{
    stage: "architecture" | "security" | "performance" | "testing";
    change: string;
    timestamp: number;
  }>;
}

export interface DecompositionContext {
  taskId: string;
  originalTask: string;
  currentMicroTasks: RefinedMicroTask[];
  currentStage: "architecture" | "security" | "performance" | "testing";
  previousRecommendations: string[];
}

export interface MergedDecomposition {
  taskId: string;
  originalTask: string;
  microTasks: RefinedMicroTask[];

  // Quality metrics
  metrics: {
    totalTasks: number;
    constraintCompleteness: number; // % of tasks with all 4 constraint types
    avgConstraintsPerTask: number;
    refinementDepth: number; // Average refinements per task
  };

  // Aggregated recommendations
  recommendations: {
    architecture: string[];
    security: string[];
    performance: string[];
    testing: string[];
  };
}

// =============================================
// Sequential Merger - Natural Refinement
// =============================================

/**
 * Merge decompositions sequentially through natural context refinement.
 *
 * This is the core algorithm that demonstrates sequential > parallel:
 * - Architecture creates initial structure
 * - Security refines with security constraints (no duplication)
 * - Performance refines with performance constraints (no duplication)
 * - Testing refines with test requirements (no duplication)
 *
 * Result: 12-16 high-quality tasks instead of 40+ duplicate tasks.
 */
export function mergeSequentialDecompositions(
  architectureOutput: any,
  securityOutput: any,
  performanceOutput: any,
  testingOutput: any
): MergedDecomposition {
  const startTime = Date.now();

  console.log("[merger] Starting sequential context refinement");
  console.log(`  Architecture tasks: ${architectureOutput.microTasks.length}`);
  console.log(`  Security tasks: ${securityOutput.microTasks.length}`);
  console.log(`  Performance tasks: ${performanceOutput.microTasks.length}`);
  console.log(`  Testing tasks: ${testingOutput.microTasks.length}`);

  // Stage 1: Initialize with architecture decomposition
  let refinedTasks = initializeFromArchitecture(architectureOutput);
  console.log(`  [Stage 1] Architecture baseline: ${refinedTasks.length} tasks`);

  // P0 Fix: Task 5 - Task Count Validation
  if (refinedTasks.length === 0) {
    throw new Error(
      "[merger] Architecture decomposer returned 0 tasks - cannot proceed with refinement.\n" +
        `This is a critical failure in baseline decomposition. ` +
        `The architecture stage must produce at least 1 task.\n` +
        `Common causes: API error, malformed prompt, empty task description, or quota exceeded.\n` +
        `Check architecture decomposer logs for details.`
    );
  }

  if (refinedTasks.length > 50) {
    console.warn(
      `[merger] ⚠️  Architecture decomposition produced ${refinedTasks.length} tasks - ` +
        `higher than expected (target 12-16 after refinement).\n` +
        `This may indicate over-decomposition. Consider refining the task description ` +
        `or adjusting the architecture prompt to produce more focused output.`
    );
  }

  // Stage 2: Refine with security constraints
  refinedTasks = refineWithSecurityConstraints(refinedTasks, securityOutput);
  console.log(`  [Stage 2] After security refinement: ${refinedTasks.length} tasks`);

  // Stage 3: Refine with performance constraints
  refinedTasks = refineWithPerformanceConstraints(refinedTasks, performanceOutput);
  console.log(`  [Stage 3] After performance refinement: ${refinedTasks.length} tasks`);

  // Stage 4: Refine with testing requirements
  refinedTasks = refineWithTestingConstraints(refinedTasks, testingOutput);
  console.log(`  [Stage 4] After testing refinement: ${refinedTasks.length} tasks`);

  // Calculate quality metrics
  const metrics = calculateQualityMetrics(refinedTasks);

  const result: MergedDecomposition = {
    taskId: architectureOutput.taskId,
    originalTask: architectureOutput.originalTask,
    microTasks: refinedTasks,
    metrics,
    recommendations: {
      architecture: architectureOutput.recommendations || [],
      security: securityOutput.securityRecommendations || [],
      performance: performanceOutput.performanceRecommendations || [],
      testing: testingOutput.testingRecommendations || [],
    },
  };

  const duration = Date.now() - startTime;
  console.log(`[merger] Sequential refinement complete in ${duration}ms`);
  console.log(`  Final task count: ${refinedTasks.length} (target: 12-16)`);
  console.log(`  Constraint completeness: ${(metrics.constraintCompleteness * 100).toFixed(1)}%`);
  console.log(`  Avg constraints per task: ${metrics.avgConstraintsPerTask.toFixed(1)}`);

  return result;
}

// =============================================
// Stage 1: Architecture Baseline
// =============================================

function initializeFromArchitecture(architectureOutput: any): RefinedMicroTask[] {
  return architectureOutput.microTasks.map((task: any) => ({
    id: task.id,
    title: task.title,
    description: task.description,
    priority: task.priority || "medium",
    constraints: {
      architecture: {
        perspective: "architecture" as const,
        description: task.rationale || task.description,
        rationale: task.rationale || "Architecture baseline",
      },
    },
    dependencies: task.dependencies || [],
    estimatedEffort: task.estimatedEffort || "medium",
    refinementHistory: [
      {
        stage: "architecture" as const,
        change: "Initial architecture decomposition",
        timestamp: Date.now(),
      },
    ],
  }));
}

// =============================================
// Stage 2: Security Refinement
// =============================================

function refineWithSecurityConstraints(
  tasks: RefinedMicroTask[],
  securityOutput: any
): RefinedMicroTask[] {
  const refinedTasks = [...tasks];
  const securityTasks = securityOutput.microTasks || [];

  // Refine existing tasks with security constraints
  for (const secTask of securityTasks) {
    const matchingTask = findMatchingTask(refinedTasks, secTask);

    if (matchingTask) {
      // Refine existing task
      matchingTask.constraints.security = {
        perspective: "security",
        description: secTask.description,
        rationale: secTask.rationale || "Security requirement",
        additionalContext: secTask.threatVectors?.join(", "),
      };

      matchingTask.refinementHistory.push({
        stage: "security",
        change: `Added security constraint: ${secTask.title}`,
        timestamp: Date.now(),
      });

      // Upgrade priority if security is critical
      if (secTask.priority === "critical" && matchingTask.priority !== "critical") {
        matchingTask.priority = "critical";
      }
    } else {
      // New security-specific task
      refinedTasks.push({
        id: secTask.id,
        title: secTask.title,
        description: secTask.description,
        priority: secTask.priority || "high",
        constraints: {
          security: {
            perspective: "security",
            description: secTask.description,
            rationale: secTask.rationale || "Security-specific requirement",
            additionalContext: secTask.threatVectors?.join(", "),
          },
        },
        dependencies: secTask.dependencies || [],
        estimatedEffort: secTask.estimatedEffort || "medium",
        refinementHistory: [
          {
            stage: "security",
            change: "New security-specific task",
            timestamp: Date.now(),
          },
        ],
      });
    }
  }

  return refinedTasks;
}

// =============================================
// Stage 3: Performance Refinement
// =============================================

function refineWithPerformanceConstraints(
  tasks: RefinedMicroTask[],
  performanceOutput: any
): RefinedMicroTask[] {
  const refinedTasks = [...tasks];
  const perfTasks = performanceOutput.microTasks || [];

  for (const perfTask of perfTasks) {
    const matchingTask = findMatchingTask(refinedTasks, perfTask);

    if (matchingTask) {
      // Refine existing task
      matchingTask.constraints.performance = {
        perspective: "performance",
        description: perfTask.description,
        rationale: perfTask.rationale || "Performance optimization",
        additionalContext: perfTask.metrics?.join(", "),
      };

      matchingTask.refinementHistory.push({
        stage: "performance",
        change: `Added performance constraint: ${perfTask.title}`,
        timestamp: Date.now(),
      });
    } else {
      // New performance-specific task
      refinedTasks.push({
        id: perfTask.id,
        title: perfTask.title,
        description: perfTask.description,
        priority: perfTask.priority || "medium",
        constraints: {
          performance: {
            perspective: "performance",
            description: perfTask.description,
            rationale: perfTask.rationale || "Performance-specific requirement",
            additionalContext: perfTask.metrics?.join(", "),
          },
        },
        dependencies: perfTask.dependencies || [],
        estimatedEffort: perfTask.estimatedEffort || "medium",
        refinementHistory: [
          {
            stage: "performance",
            change: "New performance-specific task",
            timestamp: Date.now(),
          },
        ],
      });
    }
  }

  return refinedTasks;
}

// =============================================
// Stage 4: Testing Refinement
// =============================================

function refineWithTestingConstraints(
  tasks: RefinedMicroTask[],
  testingOutput: any
): RefinedMicroTask[] {
  const refinedTasks = [...tasks];
  const testTasks = testingOutput.microTasks || [];

  for (const testTask of testTasks) {
    const matchingTask = findMatchingTask(refinedTasks, testTask);

    if (matchingTask) {
      // Refine existing task
      matchingTask.constraints.testing = {
        perspective: "testing",
        description: testTask.description,
        rationale: testTask.rationale || "Test coverage requirement",
        additionalContext: testTask.testTypes?.join(", "),
      };

      matchingTask.refinementHistory.push({
        stage: "testing",
        change: `Added testing constraint: ${testTask.title}`,
        timestamp: Date.now(),
      });
    } else {
      // New testing-specific task
      refinedTasks.push({
        id: testTask.id,
        title: testTask.title,
        description: testTask.description,
        priority: testTask.priority || "medium",
        constraints: {
          testing: {
            perspective: "testing",
            description: testTask.description,
            rationale: testTask.rationale || "Testing-specific requirement",
            additionalContext: testTask.testTypes?.join(", "),
          },
        },
        dependencies: testTask.dependencies || [],
        estimatedEffort: testTask.estimatedEffort || "medium",
        refinementHistory: [
          {
            stage: "testing",
            change: "New testing-specific task",
            timestamp: Date.now(),
          },
        ],
      });
    }
  }

  return refinedTasks;
}

// =============================================
// Task Matching Logic
// =============================================

/**
 * Find matching task based on title similarity and scope overlap.
 *
 * This is the key to natural deduplication: we identify when a task
 * from a later stage is refining an earlier task vs. introducing
 * a completely new requirement.
 */
// P0 Fix: Task 2 - Merger Error Handling
function findMatchingTask(
  existingTasks: RefinedMicroTask[],
  newTask: any
): RefinedMicroTask | undefined {
  // P0 Fix: Validate inputs
  if (!Array.isArray(existingTasks)) {
    throw new Error(
      "[merger] findMatchingTask: Invalid input - existingTasks must be an array.\n" +
        `Received type: ${typeof existingTasks}. This indicates a corrupted refinement state.`
    );
  }

  if (!newTask || typeof newTask !== "object") {
    throw new Error(
      "[merger] findMatchingTask: Invalid input - newTask must be an object.\n" +
        `Received: ${JSON.stringify(newTask)} (type: ${typeof newTask}). ` +
        `This indicates malformed decomposer output.`
    );
  }

  if (!newTask.title || typeof newTask.title !== "string") {
    throw new Error(
      "[merger] findMatchingTask: newTask missing title field.\n" +
        `newTask: ${JSON.stringify(newTask)}. This indicates invalid task structure from decomposer.`
    );
  }

  // Exact title match
  const exactMatch = existingTasks.find((t) => t.title === newTask.title);
  if (exactMatch) return exactMatch;

  // Fuzzy title match (contains key words)
  const newTitleWords = extractKeyWords(newTask.title);
  const fuzzyMatch = existingTasks.find((t) => {
    const existingWords = extractKeyWords(t.title);
    const overlap = newTitleWords.filter((w) => existingWords.includes(w));
    return overlap.length >= 2; // At least 2 common words
  });

  return fuzzyMatch;
}

// P0 Fix: Task 2 - Merger Error Handling
function extractKeyWords(title: string): string[] {
  // P0 Fix: Validate input
  if (typeof title !== "string") {
    throw new Error(
      `[merger] extractKeyWords: Invalid input - title must be a string.\n` +
        `Received type: ${typeof title}, value: ${JSON.stringify(title)}. ` +
        `This indicates a task with invalid title field.`
    );
  }

  if (title.length === 0) {
    console.warn("[merger] extractKeyWords: Empty title string - returning empty keywords");
    return [];
  }

  const stopWords = ["the", "a", "an", "and", "or", "but", "with", "for"];
  return title
    .toLowerCase()
    .split(/\s+/)
    .filter((w) => w.length > 3 && !stopWords.includes(w));
}

// =============================================
// Quality Metrics
// =============================================

function calculateQualityMetrics(tasks: RefinedMicroTask[]): MergedDecomposition["metrics"] {
  const totalTasks = tasks.length;

  // Constraint completeness: % of tasks with all 4 constraint types
  const tasksWithAllConstraints = tasks.filter(
    (t) =>
      t.constraints.architecture &&
      t.constraints.security &&
      t.constraints.performance &&
      t.constraints.testing
  ).length;

  const constraintCompleteness = tasksWithAllConstraints / totalTasks;

  // Average constraints per task
  const totalConstraints = tasks.reduce((sum, t) => {
    return (
      sum +
      (t.constraints.architecture ? 1 : 0) +
      (t.constraints.security ? 1 : 0) +
      (t.constraints.performance ? 1 : 0) +
      (t.constraints.testing ? 1 : 0)
    );
  }, 0);

  const avgConstraintsPerTask = totalConstraints / totalTasks;

  // Refinement depth: average refinements per task
  const totalRefinements = tasks.reduce((sum, t) => sum + t.refinementHistory.length, 0);
  const refinementDepth = totalRefinements / totalTasks;

  return {
    totalTasks,
    constraintCompleteness,
    avgConstraintsPerTask,
    refinementDepth,
  };
}
