import { useCallback, useMemo } from 'react';
import { RowData } from './validationTypes';
import type { Field, Fields } from '../../../types';
import { ErrorSources, ErrorType, ValidationError } from '../../../types';
import { useUniqueValidation } from './useUniqueValidation';
import { isEmpty } from './validationTypes';

export const useRowOperations = <T extends string>(
  data: RowData<T>[],
  fields: Fields<T>,
  setData: React.Dispatch<React.SetStateAction<RowData<T>[]>>,
  setValidationErrors: React.Dispatch<React.SetStateAction<Map<number, Record<string, ValidationError[]>>>>,
  validateFieldFromHook: (value: any, field: Field<T>) => ValidationError[]
) => {
  // Uniqueness validation utilities
  const { validateUniqueField } = useUniqueValidation<T>(fields);

  // Determine which field keys are considered uniqueness-constrained
  const uniquenessFieldKeys = useMemo(() => {
    const keys = new Set<string>([
      'item_number',
      'upc',
      'barcode',
      'supplier_no',
      'notions_no',
      'name'
    ]);
    fields.forEach((f) => {
      if (f.validations?.some((v) => v.rule === 'unique')) {
        keys.add(String(f.key));
      }
    });
    return keys;
  }, [fields]);

  // Merge per-field uniqueness errors into the validation error map
  const mergeUniqueErrorsForFields = useCallback(
    (
      baseErrors: Map<number, Record<string, ValidationError[]>>,
      dataForCalc: RowData<T>[],
      fieldKeysToCheck: string[]
    ) => {
      if (!fieldKeysToCheck.length) return baseErrors;

      const newErrors = new Map(baseErrors);

      // For each field, compute duplicates and merge
      fieldKeysToCheck.forEach((fieldKey) => {
        if (!uniquenessFieldKeys.has(fieldKey)) return;

        // Compute unique errors for this single field
        const uniqueMap = validateUniqueField(dataForCalc, fieldKey);

        // Rows that currently have uniqueness errors for this field
        const rowsWithUniqueErrors = new Set<number>();
        uniqueMap.forEach((_, rowIdx) => rowsWithUniqueErrors.add(rowIdx));

        // First, apply/overwrite unique errors for rows that have duplicates
        uniqueMap.forEach((errorsForRow, rowIdx) => {
          const existing = { ...(newErrors.get(rowIdx) || {}) };

          // Convert InfoWithSource to ValidationError[] for this field
          const info = errorsForRow[fieldKey];
          // Only apply uniqueness error when the value is non-empty
          const currentValue = (dataForCalc[rowIdx] as any)?.[fieldKey];
          if (info && !isEmpty(currentValue)) {
            existing[fieldKey] = [
              {
                message: info.message,
                level: info.level,
                source: info.source ?? ErrorSources.Table,
                type: info.type ?? ErrorType.Unique
              }
            ];
          }

          if (Object.keys(existing).length > 0) newErrors.set(rowIdx, existing);
          else newErrors.delete(rowIdx);
        });

        // Then, remove any stale unique errors for this field where duplicates are resolved
        newErrors.forEach((rowErrs, rowIdx) => {
          // Skip rows that still have unique errors for this field
          if (rowsWithUniqueErrors.has(rowIdx)) return;

          if ((rowErrs as any)[fieldKey]) {
            const filtered = (rowErrs as any)[fieldKey].filter((e: ValidationError) => e.type !== ErrorType.Unique);
            if (filtered.length > 0) (rowErrs as any)[fieldKey] = filtered;
            else delete (rowErrs as any)[fieldKey];

            if (Object.keys(rowErrs).length > 0) newErrors.set(rowIdx, rowErrs);
            else newErrors.delete(rowIdx);
          }
        });
      });

      return newErrors;
    },
    [uniquenessFieldKeys, validateUniqueField]
  );

  // Helper function to validate a field value
  const fieldValidationHelper = useCallback(
    (rowIndex: number, specificField?: string) => {
      // Skip validation if row doesn't exist
      if (rowIndex < 0 || rowIndex >= data.length) return;

      // Get the row data
      const row = data[rowIndex];

      // If validating a specific field, only check that field
      if (specificField) {
        const field = fields.find((f) => String(f.key) === specificField);
        if (field) {
          const value = row[specificField as keyof typeof row];

          // Use state setter instead of direct mutation
          setValidationErrors((prev) => {
            let newErrors = new Map(prev);
            const existingErrors = { ...(newErrors.get(rowIndex) || {}) };

            // Quick check for required fields - this prevents flashing errors
            const isRequired = field.validations?.some(
              (v) => v.rule === "required"
            );
            const isEmpty =
              value === undefined ||
              value === null ||
              value === "" ||
              (Array.isArray(value) && value.length === 0) ||
              (typeof value === "object" &&
                value !== null &&
                Object.keys(value).length === 0);

            // For non-empty values, remove required errors immediately
            if (isRequired && !isEmpty && existingErrors[specificField]) {
              const nonRequiredErrors = existingErrors[specificField].filter(
                (e) => e.type !== ErrorType.Required
              );
              if (nonRequiredErrors.length === 0) {
                // If no other errors, remove the field entirely from errors
                delete existingErrors[specificField];
              } else {
                existingErrors[specificField] = nonRequiredErrors;
              }
            }

            // Run full validation for the field
            const errors = validateFieldFromHook(value, field as unknown as Field<T>);

            // Update validation errors for this field
            if (errors.length > 0) {
              existingErrors[specificField] = errors;
            } else {
              delete existingErrors[specificField];
            }

            // Update validation errors map
            if (Object.keys(existingErrors).length > 0) {
              newErrors.set(rowIndex, existingErrors);
            } else {
              newErrors.delete(rowIndex);
            }

            // If field is uniqueness-constrained, also re-validate uniqueness for the column
            if (uniquenessFieldKeys.has(specificField)) {
              const dataForCalc = data; // latest data
              newErrors = mergeUniqueErrorsForFields(newErrors, dataForCalc, [specificField]);
            }

            return newErrors;
          });
        }
      } else {
        // Validate all fields in the row
        setValidationErrors((prev) => {
          const newErrors = new Map(prev);
          const rowErrors: Record<string, ValidationError[]> = {};

          fields.forEach((field) => {
            const fieldKey = String(field.key);
            const value = row[fieldKey as keyof typeof row];
            const errors = validateFieldFromHook(value, field as unknown as Field<T>);

            if (errors.length > 0) {
              rowErrors[fieldKey] = errors;
            }
          });

          // Update validation errors map
          if (Object.keys(rowErrors).length > 0) {
            newErrors.set(rowIndex, rowErrors);
          } else {
            newErrors.delete(rowIndex);
          }

          return newErrors;
        });
      }
    },
    [data, fields, validateFieldFromHook, setValidationErrors, mergeUniqueErrorsForFields, uniquenessFieldKeys]
  );

  // Use validateRow as an alias for fieldValidationHelper for compatibility
  const validateRow = fieldValidationHelper;

  // Modified updateRow function that properly handles field-specific validation
  const updateRow = useCallback(
    (rowIndex: number, key: T, value: any) => {
      // Process value before updating data
      let processedValue = value;

      // Strip dollar signs from price fields
      if (
        (key === "msrp" || key === "cost_each") &&
        typeof value === "string"
      ) {
        processedValue = value.replace(/[$,]/g, "");

        // Also ensure it's a valid number
        const numValue = parseFloat(processedValue);
        if (!isNaN(numValue)) {
          processedValue = numValue.toFixed(2);
        }
      }

      // Find the row data first
      const rowData = data[rowIndex];
      if (!rowData) {
        console.error(`No row data found for index ${rowIndex}`);
        return;
      }

      // Create a copy of the row to avoid mutation
      const updatedRow = { ...rowData, [key]: processedValue };

      // Update the data immediately - this sets the value
      setData((prevData) => {
        const newData = [...prevData];
        if (rowIndex >= 0 && rowIndex < newData.length) {
          newData[rowIndex] = updatedRow;
        }
        return newData;
      });

      // Find the field definition
      const field = fields.find((f) => String(f.key) === key);
      if (!field) return;

      // CRITICAL FIX: Combine both validation operations into a single state update
      // to prevent intermediate rendering that causes error icon flashing
      setValidationErrors((prev) => {
        // Start with previous errors
        let newMap = new Map(prev);
        const existingErrors = newMap.get(rowIndex) || {};
        const newRowErrors = { ...existingErrors };

        // Check for required field first
        const isRequired = field.validations?.some(
          (v) => v.rule === "required"
        );
        const isEmpty =
          processedValue === undefined ||
          processedValue === null ||
          processedValue === "" ||
          (Array.isArray(processedValue) && processedValue.length === 0) ||
          (typeof processedValue === "object" &&
            processedValue !== null &&
            Object.keys(processedValue).length === 0);

        // For required fields with values, remove required errors
        if (isRequired && !isEmpty && newRowErrors[key as string]) {
          const hasRequiredError = newRowErrors[key as string].some(
            (e) => e.type === ErrorType.Required
          );

          if (hasRequiredError) {
            // Remove required errors but keep other types of errors
            const nonRequiredErrors = newRowErrors[key as string].filter(
              (e) => e.type !== ErrorType.Required
            );

            if (nonRequiredErrors.length === 0) {
              // If no other errors, delete the field's errors entirely
              delete newRowErrors[key as string];
            } else {
              // Otherwise keep non-required errors
              newRowErrors[key as string] = nonRequiredErrors;
            }
          }
        }

        // Now run full validation for the field (except for required which we already handled)
        const errors = validateFieldFromHook(
          processedValue,
          field as unknown as Field<T>
        ).filter((e) => e.type !== ErrorType.Required || isEmpty);

        // Update with new validation results
        if (errors.length > 0) {
          newRowErrors[key as string] = errors;
        } else {
          // Clear any existing errors for this field
          delete newRowErrors[key as string];
        }

        // Update the map
        if (Object.keys(newRowErrors).length > 0) {
          newMap.set(rowIndex, newRowErrors);
        } else {
          newMap.delete(rowIndex);
        }

        // If uniqueness applies, validate affected columns
        const fieldsToCheck: string[] = [];
        if (uniquenessFieldKeys.has(String(key))) fieldsToCheck.push(String(key));
        if (key === ('upc' as T) || key === ('barcode' as T) || key === ('supplier' as T)) {
          if (uniquenessFieldKeys.has('item_number')) fieldsToCheck.push('item_number');
        }

        if (fieldsToCheck.length > 0) {
          const dataForCalc = (() => {
            const copy = [...data];
            if (rowIndex >= 0 && rowIndex < copy.length) {
              copy[rowIndex] = { ...(copy[rowIndex] || {}), [key]: processedValue } as RowData<T>;
            }
            return copy;
          })();
          newMap = mergeUniqueErrorsForFields(newMap, dataForCalc, fieldsToCheck);
        }

        return newMap;
      });

      // Handle simple secondary effects here
      setTimeout(() => {
        // Use __index to find the actual row in the full data array
        const rowId = rowData.__index;

        // Handle company change - clear line/subline
        if (key === "company" && processedValue) {
          // Clear any existing line/subline values
          setData((prevData) => {
            const newData = [...prevData];
            const idx = newData.findIndex((item) => item.__index === rowId);
            if (idx >= 0) {
              newData[idx] = {
                ...newData[idx],
                line: undefined,
                subline: undefined,
              };
            }
            return newData;
          });
        }

        // Handle line change - clear subline
        if (key === "line" && processedValue) {
          // Clear any existing subline value
          setData((prevData) => {
            const newData = [...prevData];
            const idx = newData.findIndex((item) => item.__index === rowId);
            if (idx >= 0) {
              newData[idx] = {
                ...newData[idx],
                subline: undefined,
              };
            }
            return newData;
          });
        }
      }, 5); // Reduced delay for faster secondary effects
    },
    [data, fields, validateFieldFromHook, setData, setValidationErrors, mergeUniqueErrorsForFields, uniquenessFieldKeys]
  );

  // Improved revalidateRows function
  const revalidateRows = useCallback(
    async (
      rowIndexes: number[],
      updatedFields?: { [rowIndex: number]: string[] },
      dataOverride?: RowData<T>[]
    ) => {
      // Process all specified rows using a single state update to avoid race conditions
      setValidationErrors((prev) => {
        let newErrors = new Map(prev);
        const workingData = dataOverride ?? data;

        // Track which uniqueness fields need to be revalidated across the dataset
        const uniqueFieldsToCheck = new Set<string>();

        // Process each row
        for (const rowIndex of rowIndexes) {
          if (rowIndex < 0 || rowIndex >= workingData.length) continue;

          const row = workingData[rowIndex];
          if (!row) continue;

          // If we have specific fields to update for this row
          const fieldsToValidate = updatedFields?.[rowIndex] || [];

          if (fieldsToValidate.length > 0) {
            // Get existing errors for this row
            const existingRowErrors = { ...(newErrors.get(rowIndex) || {}) };

            // Validate each specified field
            for (const fieldKey of fieldsToValidate) {
              const field = fields.find((f) => String(f.key) === fieldKey);
              if (!field) continue;

              const value = row[fieldKey as keyof typeof row];

              // Run validation for this field
              const errors = validateFieldFromHook(value, field as unknown as Field<T>);

              // Update errors for this field
              if (errors.length > 0) {
                existingRowErrors[fieldKey] = errors;
              } else {
                delete existingRowErrors[fieldKey];
              }

              // If field is uniqueness-constrained, mark for uniqueness pass
              if (uniquenessFieldKeys.has(fieldKey)) {
                uniqueFieldsToCheck.add(fieldKey);
              }
            }

            // Update the row's errors
            if (Object.keys(existingRowErrors).length > 0) {
              newErrors.set(rowIndex, existingRowErrors);
            } else {
              newErrors.delete(rowIndex);
            }
          } else {
            // No specific fields provided - validate the entire row
            const rowErrors: Record<string, ValidationError[]> = {};

            // Validate all fields in the row
            for (const field of fields) {
              const fieldKey = String(field.key);
              const value = row[fieldKey as keyof typeof row];

              // Run validation for this field
              const errors = validateFieldFromHook(value, field as unknown as Field<T>);

              // Update errors for this field
              if (errors.length > 0) {
                rowErrors[fieldKey] = errors;
              }

              // If field is uniqueness-constrained and we validated it, include for uniqueness pass
              if (uniquenessFieldKeys.has(fieldKey)) {
                uniqueFieldsToCheck.add(fieldKey);
              }
            }

            // Update the row's errors
            if (Object.keys(rowErrors).length > 0) {
              newErrors.set(rowIndex, rowErrors);
            } else {
              newErrors.delete(rowIndex);
            }
          }
        }

        // Run per-field uniqueness checks and merge results
        if (uniqueFieldsToCheck.size > 0) {
          newErrors = mergeUniqueErrorsForFields(newErrors, workingData, Array.from(uniqueFieldsToCheck));
        }

        return newErrors;
      });
    },
    [data, fields, validateFieldFromHook, mergeUniqueErrorsForFields, uniquenessFieldKeys]
  );

  // Copy a cell value to all cells below it in the same column
  const copyDown = useCallback(
    (rowIndex: number, key: T) => {
      // Get the source value to copy
      const sourceValue = data[rowIndex][key];

      // Update all rows below with the same value using the existing updateRow function
      // This ensures all validation logic runs consistently
      for (let i = rowIndex + 1; i < data.length; i++) {
        // Just use updateRow which will handle validation with proper timing
        updateRow(i, key, sourceValue);
      }
    },
    [data, updateRow]
  );

  return {
    validateRow,
    updateRow,
    revalidateRows,
    copyDown
  };
}; 
