/**
 * Validation utilities for the Experiment Designer.
 *
 * Implements comprehensive validation rules per the redesign spec:
 * - Structural validation (step names, types, trigger configurations)
 * - Parameter validation (required fields, type checking, bounds)
 * - Semantic validation (uniqueness, dependencies, best practices)
 * - Cross-step validation (workflow integrity, execution feasibility)
 *
 * Each validator returns an array of ValidationIssue objects with severity levels.
 */

import type {
  ExperimentStep,
  ActionDefinition,
  TriggerType,
  StepType,
} from "~/lib/experiment-designer/types";

/* -------------------------------------------------------------------------- */
/* Types                                                                      */
/* -------------------------------------------------------------------------- */

export interface ValidationIssue {
  severity: "error" | "warning" | "info";
  message: string;
  category: "structural" | "parameter" | "semantic" | "execution";
  field?: string;
  suggestion?: string;
  actionId?: string;
  stepId?: string;
}

export interface ValidationContext {
  steps: ExperimentStep[];
  actionDefinitions: ActionDefinition[];
  allowPartialValidation?: boolean;
}

export interface ValidationResult {
  valid: boolean;
  issues: ValidationIssue[];
  errorCount: number;
  warningCount: number;
  infoCount: number;
}

/* -------------------------------------------------------------------------- */
/* Validation Rule Sets                                                       */
/* -------------------------------------------------------------------------- */

// Steps should ALWAYS execute sequentially
// Parallel/conditional/loop execution happens at the ACTION level, not step level
const VALID_STEP_TYPES: StepType[] = ["sequential", "conditional"];
const VALID_TRIGGER_TYPES: TriggerType[] = [
  "trial_start",
  "participant_action",
  "timer",
  "previous_step",
];

/* -------------------------------------------------------------------------- */
/* Structural Validation                                                      */
/* -------------------------------------------------------------------------- */

export function validateStructural(
  steps: ExperimentStep[],
  _context: ValidationContext,
): ValidationIssue[] {
  const issues: ValidationIssue[] = [];

  // Global structural checks
  if (steps.length === 0) {
    issues.push({
      severity: "error",
      message: "Experiment must contain at least one step",
      category: "structural",
      suggestion: "Add a step to begin designing your experiment",
    });
    return issues; // Early return for empty experiment
  }

  // Step-level validation
  steps.forEach((step, stepIndex) => {
    const stepId = step.id;

    // Step name validation
    if (!step.name?.trim()) {
      issues.push({
        severity: "error",
        message: "Step name cannot be empty",
        category: "structural",
        field: "name",
        stepId,
        suggestion: "Provide a descriptive name for this step",
      });
    } else if (step.name.length > 100) {
      issues.push({
        severity: "warning",
        message: "Step name is very long and may be truncated in displays",
        category: "structural",
        field: "name",
        stepId,
        suggestion: "Consider shortening the step name",
      });
    }

    // Step type validation
    if (!VALID_STEP_TYPES.includes(step.type)) {
      issues.push({
        severity: "error",
        message: `Invalid step type: ${step.type}`,
        category: "structural",
        field: "type",
        stepId,
        suggestion: `Valid types are: ${VALID_STEP_TYPES.join(", ")}`,
      });
    }

    // Step order validation
    if (step.order !== stepIndex) {
      issues.push({
        severity: "error",
        message: `Step order mismatch: expected ${stepIndex}, got ${step.order}`,
        category: "structural",
        field: "order",
        stepId,
        suggestion: "Step order must be sequential starting from 0",
      });
    }

    // Trigger validation
    if (!VALID_TRIGGER_TYPES.includes(step.trigger.type)) {
      issues.push({
        severity: "error",
        message: `Invalid trigger type: ${step.trigger.type}`,
        category: "structural",
        field: "trigger.type",
        stepId,
        suggestion: `Valid trigger types are: ${VALID_TRIGGER_TYPES.join(", ")}`,
      });
    }

    // All steps must be sequential type (parallel/conditional/loop removed)
    // Control flow and parallelism should be implemented at the ACTION level

    // Action-level structural validation
    step.actions.forEach((action) => {
      const actionId = action.id;

      // Action name validation
      if (!action.name?.trim()) {
        issues.push({
          severity: "error",
          message: "Action name cannot be empty",
          category: "structural",
          field: "name",
          stepId,
          actionId,
          suggestion: "Provide a descriptive name for this action",
        });
      }

      // Action type validation
      if (!action.type?.trim()) {
        issues.push({
          severity: "error",
          message: "Action type cannot be empty",
          category: "structural",
          field: "type",
          stepId,
          actionId,
          suggestion: "Select a valid action type from the library",
        });
      }

      // Note: Action order validation removed as orderIndex is not in the type definition
      // Actions are ordered by their position in the array

      // Source validation
      if (!action.source?.kind) {
        issues.push({
          severity: "error",
          message: "Action source kind is required",
          category: "structural",
          field: "source.kind",
          stepId,
          actionId,
          suggestion: "Action must specify if it's from core or plugin source",
        });
      }

      // Plugin actions need plugin metadata
      /* VALIDATION DISABLED BY USER REQUEST
      if (action.source?.kind === "plugin") {
        if (!action.source.pluginId) {
          issues.push({
            severity: "error",
            message: "Plugin action must specify pluginId",
            category: "structural",
            field: "source.pluginId",
            stepId,
            actionId,
            suggestion: "Plugin actions require valid plugin identification",
          });
        }
        if (!action.source.pluginVersion) {
          issues.push({
            severity: "warning",
            message: "Plugin action should specify version for reproducibility",
            category: "structural",
            field: "source.pluginVersion",
            stepId,
            actionId,
            suggestion: "Pin plugin version to ensure consistent behavior",
          });
        }
      }
      */

      // Execution descriptor validation
      if (!action.execution?.transport) {
        issues.push({
          severity: "error",
          message: "Action must specify execution transport",
          category: "structural",
          field: "execution.transport",
          stepId,
          actionId,
          suggestion:
            "Define how this action should be executed (rest, ros2, etc.)",
        });
      }
    });
  });

  return issues;
}

/* -------------------------------------------------------------------------- */
/* Parameter Validation                                                       */
/* -------------------------------------------------------------------------- */

export function validateParameters(
  steps: ExperimentStep[],
  context: ValidationContext,
): ValidationIssue[] {
  const issues: ValidationIssue[] = [];
  const { actionDefinitions } = context;

  steps.forEach((step) => {
    step.actions.forEach((action) => {
      const stepId = step.id;
      const actionId = action.id;

      // Find action definition
      const definition = actionDefinitions.find(
        (def) => def.type === action.type,
      );

      if (!definition) {
        issues.push({
          severity: "error",
          message: `Action definition not found for type: ${action.type}`,
          category: "parameter",
          stepId,
          actionId,
          suggestion: "Check if the required plugin is installed and loaded",
        });
        return; // Skip parameter validation for missing definitions
      }

      // Validate each parameter
      definition.parameters.forEach((paramDef) => {
        const paramId = paramDef.id;
        const value = action.parameters[paramId];
        const field = `parameters.${paramId}`;

        // Required parameter check
        if (paramDef.required) {
          const isEmpty =
            value === undefined ||
            value === null ||
            (typeof value === "string" && value.trim() === "");

          if (isEmpty) {
            issues.push({
              severity: "error",
              message: `Required parameter '${paramDef.name}' is missing`,
              category: "parameter",
              field,
              stepId,
              actionId,
              suggestion: "Provide a value for this required parameter",
            });
            return; // Skip type validation for missing required params
          }
        }

        // Skip validation for optional empty parameters
        if (value === undefined || value === null) return;

        // Type validation
        switch (paramDef.type) {
          case "text":
            if (typeof value !== "string") {
              issues.push({
                severity: "error",
                message: `Parameter '${paramDef.name}' must be text`,
                category: "parameter",
                field,
                stepId,
                actionId,
                suggestion: "Enter a text value",
              });
              // Note: maxLength validation removed as it's not in the ActionParameter type
            }
            break;

          case "number":
            if (typeof value !== "number" || isNaN(value)) {
              issues.push({
                severity: "error",
                message: `Parameter '${paramDef.name}' must be a valid number`,
                category: "parameter",
                field,
                stepId,
                actionId,
                suggestion: "Enter a numeric value",
              });
            } else {
              // Range validation
              if (paramDef.min !== undefined && value < paramDef.min) {
                issues.push({
                  severity: "error",
                  message: `Parameter '${paramDef.name}' must be at least ${paramDef.min}`,
                  category: "parameter",
                  field,
                  stepId,
                  actionId,
                  suggestion: `Enter a value >= ${paramDef.min}`,
                });
              }
              if (paramDef.max !== undefined && value > paramDef.max) {
                issues.push({
                  severity: "error",
                  message: `Parameter '${paramDef.name}' must be at most ${paramDef.max}`,
                  category: "parameter",
                  field,
                  stepId,
                  actionId,
                  suggestion: `Enter a value <= ${paramDef.max}`,
                });
              }
            }
            break;

          case "boolean":
            if (typeof value !== "boolean") {
              issues.push({
                severity: "error",
                message: `Parameter '${paramDef.name}' must be true or false`,
                category: "parameter",
                field,
                stepId,
                actionId,
                suggestion: "Use the toggle switch to set this value",
              });
            }
            break;

          case "select":
            if (
              paramDef.options &&
              !paramDef.options.includes(value as string)
            ) {
              issues.push({
                severity: "error",
                message: `Parameter '${paramDef.name}' has invalid value`,
                category: "parameter",
                field,
                stepId,
                actionId,
                suggestion:
                  Array.isArray(paramDef.options) && paramDef.options.length
                    ? `Choose from: ${paramDef.options.join(", ")}`
                    : "Choose a valid option",
              });
            }
            break;

          case "array":
            if (!Array.isArray(value)) {
              issues.push({
                severity: "error",
                message: `Parameter '${paramDef.name}' must be a list/array`,
                category: "parameter",
                field,
                stepId,
                actionId,
                suggestion: "Enter a list of values",
              });
            }
            break;

          case "json":
            if (typeof value !== "object" || value === null) {
              issues.push({
                severity: "error",
                message: `Parameter '${paramDef.name}' must be a valid object`,
                category: "parameter",
                field,
                stepId,
                actionId,
                suggestion: "Enter a valid JSON object",
              });
            }
            break;

          default:
            // Unknown parameter type
            issues.push({
              severity: "warning",
              message: `Unknown parameter type '${String(paramDef.type)}' for '${paramDef.name}'`,
              category: "parameter",
              field,
              stepId,
              actionId,
              suggestion: "Check action definition for correct parameter types",
            });
        }
      });

      // Check for unexpected parameters
      Object.keys(action.parameters).forEach((paramId) => {
        const isDefinedParam = definition.parameters.some(
          (def) => def.id === paramId,
        );
        if (!isDefinedParam) {
          issues.push({
            severity: "warning",
            message: `Unexpected parameter '${paramId}' - not defined in action schema`,
            category: "parameter",
            field: `parameters.${paramId}`,
            stepId,
            actionId,
            suggestion:
              "Remove this parameter or check if action definition is outdated",
          });
        }
      });
    });
  });

  return issues;
}

/* -------------------------------------------------------------------------- */
/* Semantic Validation                                                        */
/* -------------------------------------------------------------------------- */

export function validateSemantic(
  steps: ExperimentStep[],
  _context: ValidationContext,
): ValidationIssue[] {
  const issues: ValidationIssue[] = [];

  // Check for duplicate step IDs
  const stepIds = new Set<string>();
  const duplicateStepIds = new Set<string>();

  steps.forEach((step) => {
    if (stepIds.has(step.id)) {
      duplicateStepIds.add(step.id);
    }
    stepIds.add(step.id);
  });

  duplicateStepIds.forEach((stepId) => {
    issues.push({
      severity: "error",
      message: `Duplicate step ID: ${stepId}`,
      category: "semantic",
      stepId,
      suggestion: "Step IDs must be unique throughout the experiment",
    });
  });

  // Check for duplicate action IDs globally
  const actionIds = new Set<string>();
  const duplicateActionIds = new Set<string>();

  steps.forEach((step) => {
    step.actions.forEach((action) => {
      if (actionIds.has(action.id)) {
        duplicateActionIds.add(action.id);
      }
      actionIds.add(action.id);
    });
  });

  duplicateActionIds.forEach((actionId) => {
    const containingSteps = steps.filter((s) =>
      s.actions.some((a) => a.id === actionId),
    );

    containingSteps.forEach((step) => {
      issues.push({
        severity: "error",
        message: `Duplicate action ID: ${actionId}`,
        category: "semantic",
        stepId: step.id,
        actionId,
        suggestion: "Action IDs must be unique throughout the experiment",
      });
    });
  });

  // Check for empty steps
  steps.forEach((step) => {
    if (step.actions.length === 0) {
      issues.push({
        severity: "warning",
        message: "Step has no actions",
        category: "semantic",
        stepId: step.id,
        suggestion: "Add actions to this step or remove it",
      });
    }
  });

  // Documentation suggestions
  steps.forEach((step) => {
    // Missing step descriptions
    if (!step.description?.trim()) {
      issues.push({
        severity: "info",
        message: "Consider adding a description to document step purpose",
        category: "semantic",
        field: "description",
        stepId: step.id,
        suggestion:
          "Descriptions improve experiment documentation and reproducibility",
      });
    }

    // Actions without meaningful names
    step.actions.forEach((action) => {
      if (
        action.name === action.type ||
        action.name.toLowerCase().includes("untitled")
      ) {
        issues.push({
          severity: "info",
          message: "Consider providing a more descriptive action name",
          category: "semantic",
          field: "name",
          stepId: step.id,
          actionId: action.id,
          suggestion:
            "Descriptive names help with experiment understanding and debugging",
        });
      }
    });
  });

  // Workflow logic suggestions
  steps.forEach((step, index) => {
    // First step should typically use trial_start trigger
    if (index === 0 && step.trigger.type !== "trial_start") {
      issues.push({
        severity: "info",
        message: "First step typically uses trial_start trigger",
        category: "semantic",
        field: "trigger.type",
        stepId: step.id,
        suggestion: "Consider using trial_start trigger for the initial step",
      });
    }

    // Timer triggers without reasonable durations
    if (step.trigger.type === "timer") {
      const duration = step.trigger.conditions?.duration;
      if (typeof duration === "number") {
        if (duration < 100) {
          issues.push({
            severity: "warning",
            message: "Very short timer duration may cause timing issues",
            category: "semantic",
            field: "trigger.conditions.duration",
            stepId: step.id,
            suggestion: "Consider using at least 100ms for reliable timing",
          });
        }
        if (duration > 300000) {
          // 5 minutes
          issues.push({
            severity: "info",
            message: "Long timer duration - ensure this is intentional",
            category: "semantic",
            field: "trigger.conditions.duration",
            stepId: step.id,
            suggestion:
              "Verify the timer duration is correct for your use case",
          });
        }
      }
    }
  });

  return issues;
}

/* -------------------------------------------------------------------------- */
/* Cross-Step Execution Validation                                           */
/* -------------------------------------------------------------------------- */

export function validateExecution(
  steps: ExperimentStep[],
  _context: ValidationContext,
): ValidationIssue[] {
  const issues: ValidationIssue[] = [];

  // Note: Trigger validation removed - convertDatabaseToSteps() automatically assigns
  // correct triggers (trial_start for first step, previous_step for others) based on orderIndex.
  // Manual trigger configuration is intentional for advanced workflows.

  // Check for missing robot dependencies
  const robotActions = steps.flatMap((step) =>
    step.actions.filter(
      (action) =>
        action.execution?.transport === "ros2" ||
        action.execution?.transport === "rest",
    ),
  );

  if (robotActions.length > 0) {
    // This would need robot registry integration in full implementation
    issues.push({
      severity: "info",
      message:
        "Experiment contains robot actions - ensure robot connections are configured",
      category: "execution",
      suggestion:
        "Verify robot plugins are installed and robots are accessible",
    });
  }

  return issues;
}

/* -------------------------------------------------------------------------- */
/* Main Validation Function                                                   */
/* -------------------------------------------------------------------------- */

export function validateExperimentDesign(
  steps: ExperimentStep[],
  context: ValidationContext,
): ValidationResult {
  const issues: ValidationIssue[] = [];

  // Run all validation rule sets
  issues.push(...validateStructural(steps, context));
  issues.push(...validateParameters(steps, context));
  issues.push(...validateSemantic(steps, context));
  issues.push(...validateExecution(steps, context));

  // Count issues by severity
  const errorCount = issues.filter((i) => i.severity === "error").length;
  const warningCount = issues.filter((i) => i.severity === "warning").length;
  const infoCount = issues.filter((i) => i.severity === "info").length;

  // Experiment is valid if no errors (warnings and info are allowed)
  const valid = errorCount === 0;

  return {
    valid,
    issues,
    errorCount,
    warningCount,
    infoCount,
  };
}

/* -------------------------------------------------------------------------- */
/* Issue Grouping Utilities                                                   */
/* -------------------------------------------------------------------------- */

export function groupIssuesByEntity(
  issues: ValidationIssue[],
): Record<string, ValidationIssue[]> {
  const grouped: Record<string, ValidationIssue[]> = {};

  issues.forEach((issue) => {
    const entityId = issue.actionId ?? issue.stepId ?? "experiment";
    grouped[entityId] ??= [];
    grouped[entityId].push(issue);
  });

  return grouped;
}

export function getIssuesByStep(
  issues: ValidationIssue[],
  stepId: string,
): ValidationIssue[] {
  return issues.filter((issue) => issue.stepId === stepId);
}

export function getIssuesByAction(
  issues: ValidationIssue[],
  actionId: string,
): ValidationIssue[] {
  return issues.filter((issue) => issue.actionId === actionId);
}

/* -------------------------------------------------------------------------- */
/* Exports                                                                    */
/* -------------------------------------------------------------------------- */

export const Validators = {
  validateStructural,
  validateParameters,
  validateSemantic,
  validateExecution,
  validateExperimentDesign,
  groupIssuesByEntity,
  getIssuesByStep,
  getIssuesByAction,
};

export default Validators;
