/**
 * Notation Preserver for Mathematical Exercises
 *
 * Validates and corrects mathematical notation to ensure consistency
 * with PDF standards. Handles LaTeX formatting and topic-specific rules.
 */

import { TopicType } from '@prisma/client';
import { logger } from '../../../shared/utils/logger';

/**
 * Validation Result Interface
 */
export interface ValidationResult {
  isValid: boolean;
  issues: string[];
  warnings: string[];
  corrected?: any;
}

/**
 * Notation Rule Interface
 */
interface NotationRule {
  pattern: RegExp;
  replacement: string;
  description: string;
  category: 'CRITICAL' | 'IMPORTANT' | 'STYLE';
}

/**
 * Topic-specific Notation Rules
 */
const NOTATION_RULES_BY_TOPIC: Record<TopicType, NotationRule[]> = {
  VECTORES: [
    {
      pattern: /\\vec\{([vuw])\}/g,
      replacement: '\\vec{$1}',
      description: 'Vector notation must use \\vec{} with arrow',
      category: 'CRITICAL',
    },
    {
      pattern: /\((\d+),\s*(\d+)\)/g,
      replacement: '($1; $2)',
      description: 'Vector components must use semicolon separator (x; y), not comma',
      category: 'CRITICAL',
    },
    {
      pattern: /\(([^)]+),([^)]+)\)/g,
      replacement: '($1;$2)',
      description: 'Vector components must use semicolon separator',
      category: 'CRITICAL',
    },
    {
      pattern: /\\cdot\s*\\vec/g,
      replacement: '\\lambda \\vec',
      description: 'Scalar multiplication should use lambda notation',
      category: 'IMPORTANT',
    },
    {
      pattern: /\|([vuw])\|/g,
      replacement: '|\\vec{$1}|',
      description: 'Vector magnitude should use vector notation',
      category: 'IMPORTANT',
    },
    {
      pattern: /\\mathbf\{[i-w]\}/g,
      replacement: '\\hat{$1}',
      description: 'Unit vectors should use hat notation',
      category: 'STYLE',
    },
  ],

  MATRICES: [
    {
      pattern: /\\begin\{bmatrix\}/g,
      replacement: '\\begin{pmatrix}',
      description: 'Use pmatrix environment for matrices',
      category: 'CRITICAL',
    },
    {
      pattern: /\\end\{bmatrix\}/g,
      replacement: '\\end{pmatrix}',
      description: 'Use pmatrix environment for matrices',
      category: 'CRITICAL',
    },
    {
      pattern: /\bdet\b/g,
      replacement: '\\det',
      description: 'Determinant function should use \\det',
      category: 'IMPORTANT',
    },
    {
      pattern: /A\^2/g,
      replacement: 'A^{-1}',
      description: 'Matrix inverse should use A^{-1}',
      category: 'IMPORTANT',
    },
    {
      pattern: /I_\d/g,
      replacement: 'I_n',
      description: 'Identity matrix should use subscript',
      category: 'STYLE',
    },
  ],

  SISTEMAS: [
    {
      pattern: /x(\d+)(?!\_)/g,
      replacement: 'x_$1',
      description: 'Variables must use subscript notation x_1, x_2',
      category: 'CRITICAL',
    },
    {
      pattern: /\\leq/g,
      replacement: '\\le',
      description: 'Use \\le for consistency',
      category: 'STYLE',
    },
    {
      pattern: /\\geq/g,
      replacement: '\\ge',
      description: 'Use \\ge for consistency',
      category: 'STYLE',
    },
    {
      pattern: /\\begin\{cases\}([^\\]+)\\end\{cases\}/g,
      replacement: '\\begin{cases}$1\\end{cases}',
      description: 'System notation should use cases environment',
      category: 'CRITICAL',
    },
  ],

  ESPACIOS_VECTORIALES: [
    {
      pattern: /\(([VW]);/g,
      replacement: '($1; +; \\mathbb{R}; \\cdot)',
      description: 'Space notation must follow (V; +; R; .) format',
      category: 'CRITICAL',
    },
    {
      pattern: /\\R/g,
      replacement: '\\mathbb{R}',
      description: 'Real numbers should use \\mathbb{R}',
      category: 'CRITICAL',
    },
    {
      pattern: /\\C/g,
      replacement: '\\mathbb{C}',
      description: 'Complex numbers should use \\mathbb{C}',
      category: 'CRITICAL',
    },
    {
      pattern: /\\subseteq/g,
      replacement: '\\subseteq',
      description: 'Subspace should use \\subseteq',
      category: 'IMPORTANT',
    },
    {
      pattern: /\\text\{span\}/g,
      replacement: '\\operatorname{span}',
      description: 'Span should use operatorname',
      category: 'STYLE',
    },
    {
      pattern: /\\text\{dim\}/g,
      replacement: '\\dim',
      description: 'Dimension should use \\dim',
      category: 'STYLE',
    },
  ],

  PROGRAMACION_LINEAL: [
    {
      pattern: /\\text\{Minimizar\}/g,
      replacement: 'Minimizar',
      description: 'Use plain text for Minimizar/Maximizar',
      category: 'STYLE',
    },
    {
      pattern: /\\text\{Maximizar\}/g,
      replacement: 'Maximizar',
      description: 'Use plain text for Minimizar/Maximizar',
      category: 'STYLE',
    },
    {
      pattern: /\\leq/g,
      replacement: '\\le',
      description: 'Use \\le for constraints',
      category: 'STYLE',
    },
    {
      pattern: /\\geq/g,
      replacement: '\\ge',
      description: 'Use \\ge for constraints',
      category: 'STYLE',
    },
    {
      pattern: /x\*,y\*/g,
      replacement: '(x^*, y^*)',
      description: 'Optimal solution should use (x^*, y^*)',
      category: 'IMPORTANT',
    },
  ],
};

/**
 * Common LaTeX Validation Rules
 */
const COMMON_LATEX_RULES: NotationRule[] = [
  {
    pattern: /(?<!\\)\$([^$]+)(?<!\\)\$/g,
    replacement: '$$$1$$',
    description: 'Use $$ for display math',
    category: 'IMPORTANT',
  },
  {
    pattern: /\\sin/g,
    replacement: '\\sin',
    description: 'Trigonometric functions should use backslash',
    category: 'STYLE',
  },
  {
    pattern: /\\cos/g,
    replacement: '\\cos',
    description: 'Trigonometric functions should use backslash',
    category: 'STYLE',
  },
  {
    pattern: /\\tan/g,
    replacement: '\\tan',
    description: 'Trigonometric functions should use backslash',
    category: 'STYLE',
  },
  {
    pattern: /\\log/g,
    replacement: '\\log',
    description: 'Logarithm should use backslash',
    category: 'STYLE',
  },
  {
    pattern: /\\ln/g,
    replacement: '\\ln',
    description: 'Natural log should use backslash',
    category: 'STYLE',
  },
];

/**
 * Invalid LaTeX Patterns
 */
const INVALID_LATEX_PATTERNS = [
  { pattern: /\\[a-zA-Z]+\s*\{[^}]*$/g, message: 'Unclosed LaTeX command' },
  { pattern: /\{[^}]*$/g, message: 'Unclosed brace' },
  { pattern: /\$\$[^$]*$/g, message: 'Unclosed display math' },
  { pattern: /\$[^$]*$/g, message: 'Unclosed inline math' },
  { pattern: /\\begin\{[^}]+\}[^\\]*\\end\{[^}]+\}/g, message: 'Unmatched environment', negative: true },
];

/**
 * Notation Preserver Class
 */
export class NotationPreserver {
  private rulesCache: Map<TopicType, NotationRule[]> = new Map();

  constructor() {
    this.initializeRulesCache();
  }

  /**
   * Initialize rules cache for performance
   */
  private initializeRulesCache(): void {
    Object.entries(NOTATION_RULES_BY_TOPIC).forEach(([topic, rules]) => {
      this.rulesCache.set(topic as TopicType, rules);
    });
  }

  /**
   * Validate and fix notations in an exercise
   */
  validateAndFixNotations(
    topic: TopicType,
    exercise: any
  ): { correctedExercise: any; validation: ValidationResult } {
    const issues: string[] = [];
    const warnings: string[] = [];
    let correctedExercise = JSON.parse(JSON.stringify(exercise));

    // Get topic-specific rules
    const topicRules = this.rulesCache.get(topic) || [];

    // Validate each field containing LaTeX
    const fieldsToCheck = [
      'statement',
      'correctAnswer',
      'solutionSteps',
      'formulas',
      'hints',
      'multipleChoiceOptions',
    ];

    for (const field of fieldsToCheck) {
      if (!correctedExercise[field]) continue;

      if (Array.isArray(correctedExercise[field])) {
        correctedExercise[field] = correctedExercise[field].map((item: any, index: number) => {
          return this.validateItem(item, topicRules, issues, warnings, `${field}[${index}]`);
        });
      } else {
        correctedExercise[field] = this.validateItem(
          correctedExercise[field],
          topicRules,
          issues,
          warnings,
          field
        );
      }
    }

    // Validate LaTeX syntax
    const latexValidation = this.validateLatexSyntax(correctedExercise);
    issues.push(...latexValidation.issues);
    warnings.push(...latexValidation.warnings);

    const validation: ValidationResult = {
      isValid: issues.filter(i => i.includes('CRITICAL')).length === 0,
      issues,
      warnings,
    };

    logger.debug({
      topic,
      isValid: validation.isValid,
      issuesCount: issues.length,
      warningsCount: warnings.length,
    }, 'Notation validation completed');

    return { correctedExercise, validation };
  }

  /**
   * Validate a single item (string or object with text fields)
   */
  private validateItem(
    item: any,
    rules: NotationRule[],
    issues: string[],
    warnings: string[],
    path: string
  ): any {
    if (typeof item === 'string') {
      return this.applyRules(item, rules, issues, warnings, path);
    }

    if (typeof item === 'object' && item !== null) {
      const result: any = {};
      for (const [key, value] of Object.entries(item)) {
        if (typeof value === 'string') {
          result[key] = this.applyRules(value, rules, issues, warnings, `${path}.${key}`);
        } else {
          result[key] = value;
        }
      }
      return result;
    }

    return item;
  }

  /**
   * Apply notation rules to a string
   */
  private applyRules(
    text: string,
    topicRules: NotationRule[],
    issues: string[],
    warnings: string[],
    path: string
  ): string {
    let result = text;
    const allRules = [...COMMON_LATEX_RULES, ...topicRules];

    for (const rule of allRules) {
      const matches = result.match(rule.pattern);
      if (matches) {
        result = result.replace(rule.pattern, rule.replacement);

        const message = `[${rule.category}] ${path}: ${rule.description}`;
        if (rule.category === 'CRITICAL') {
          issues.push(message);
        } else if (rule.category === 'IMPORTANT') {
          warnings.push(message);
        } else {
          // Style issues - optional reporting
        }
      }
    }

    return result;
  }

  /**
   * Validate LaTeX syntax in exercise
   */
  validateLatexSyntax(exercise: any): ValidationResult {
    const issues: string[] = [];
    const warnings: string[] = [];

    const extractLatex = (obj: any, latexStrings: string[] = []): string[] => {
      if (typeof obj === 'string') {
        // Extract LaTeX content from strings
        const matches = obj.match(/\\\$[\s\S]*?\$\\\$|\\\$[^$]+\\\$|\$[^$]+\$|\\[a-zA-Z]+\{[^}]*\}|\\[a-zA-Z]+/g);
        if (matches) {
          latexStrings.push(...matches);
        }
        return latexStrings;
      }

      if (Array.isArray(obj)) {
        obj.forEach(item => extractLatex(item, latexStrings));
        return latexStrings;
      }

      if (typeof obj === 'object' && obj !== null) {
        Object.values(obj).forEach(value => extractLatex(value, latexStrings));
      }

      return latexStrings;
    };

    const latexStrings = extractLatex(exercise);

    for (const latex of latexStrings) {
      for (const { pattern, message, negative } of INVALID_LATEX_PATTERNS) {
        const hasIssue = negative
          ? !pattern.test(latex) && latex.includes('\\begin')
          : pattern.test(latex);

        if (hasIssue) {
          issues.push(`LaTeX syntax error: ${message} in "${latex.substring(0, 50)}..."`);
        }
      }

      // Check for common LaTeX errors
      if (latex.includes('\\begin{') && !latex.includes('\\end{')) {
        issues.push(`Unclosed environment in: ${latex.substring(0, 50)}...`);
      }

      if ((latex.match(/\{/g) || []).length !== (latex.match(/\}/g) || []).length) {
        warnings.push('Unmatched braces in LaTeX expression');
      }
    }

    return {
      isValid: issues.length === 0,
      issues,
      warnings,
    };
  }

  /**
   * Check if notation matches PDF standards
   */
  checkPDFNotation(topic: TopicType, text: string): {
    isCompliant: boolean;
    differences: string[];
  } {
    const differences: string[] = [];
    const rules = this.rulesCache.get(topic) || [];

    for (const rule of rules.filter(r => r.category === 'CRITICAL')) {
      if (rule.pattern.test(text)) {
        differences.push(rule.description);
      }
    }

    return {
      isCompliant: differences.length === 0,
      differences,
    };
  }

  /**
   * Auto-fix common notation errors
   */
  autoFixNotation(topic: TopicType, text: string): string {
    let result = text;
    const rules = this.rulesCache.get(topic) || [];

    // Apply all rules sorted by category priority
    const priorityOrder = { CRITICAL: 0, IMPORTANT: 1, STYLE: 2 };
    const sortedRules = [...rules, ...COMMON_LATEX_RULES].sort(
      (a, b) => priorityOrder[a.category] - priorityOrder[b.category]
    );

    for (const rule of sortedRules) {
      result = result.replace(rule.pattern, rule.replacement);
    }

    return result;
  }

  /**
   * Extract and validate formulas from exercise
   */
  extractFormulas(exercise: any): {
    formulas: Array<{ latex: string; location: string; isValid: boolean }>;
    issues: string[];
  } {
    const formulas: Array<{ latex: string; location: string; isValid: boolean }> = [];
    const issues: string[] = [];

    const extractFromPath = (obj: any, path: string = 'root') => {
      if (typeof obj === 'string') {
        const formulaMatches = obj.match(/\$\$[\s\S]*?\$\$|\$[^$]+\$|\\[a-zA-Z]+\{[^}]*\}/g);
        if (formulaMatches) {
          formulaMatches.forEach(formula => {
            formulas.push({
              latex: formula,
              location: path,
              isValid: this.isLatexValid(formula),
            });
          });
        }
      } else if (Array.isArray(obj)) {
        obj.forEach((item, index) => extractFromPath(item, `${path}[${index}]`));
      } else if (typeof obj === 'object' && obj !== null) {
        Object.entries(obj).forEach(([key, value]) =>
          extractFromPath(value, `${path}.${key}`)
        );
      }
    };

    extractFromPath(exercise);

    formulas.forEach(f => {
      if (!f.isValid) {
        issues.push(`Invalid formula at ${f.location}: ${f.latex.substring(0, 50)}`);
      }
    });

    return { formulas, issues };
  }

  /**
   * Basic LaTeX validation
   */
  private isLatexValid(latex: string): boolean {
    // Check for balanced braces
    let braceCount = 0;
    for (const char of latex) {
      if (char === '{') braceCount++;
      if (char === '}') braceCount--;
      if (braceCount < 0) return false;
    }
    if (braceCount !== 0) return false;

    // Check for balanced math mode delimiters
    const dollarCount = (latex.match(/\$/g) || []).length;
    if (dollarCount % 2 !== 0) return false;

    return true;
  }

  /**
   * Compare notation between two exercises
   */
  compareNotation(exercise1: any, exercise2: any): {
    isConsistent: boolean;
    differences: string[];
  } {
    const differences: string[] = [];

    const extractNotation = (obj: any): Set<string> => {
      const notations = new Set<string>();
      const extract = (o: any) => {
        if (typeof o === 'string') {
          const patterns = o.match(/\\[a-zA-Z]+/g) || [];
          patterns.forEach(p => notations.add(p));
        } else if (Array.isArray(o)) {
          o.forEach(extract);
        } else if (typeof o === 'object' && o !== null) {
          Object.values(o).forEach(extract);
        }
      };
      extract(obj);
      return notations;
    };

    const notation1 = extractNotation(exercise1);
    const notation2 = extractNotation(exercise2);

    const symDiff = new Set([...notation1].filter(x => !notation2.has(x)));
    symDiff.forEach(n => differences.push(`Notation difference: ${n}`));

    return {
      isConsistent: differences.length === 0,
      differences,
    };
  }

  /**
   * Get notation rules for a topic
   */
  getTopicRules(topic: TopicType): NotationRule[] {
    return [...(this.rulesCache.get(topic) || []), ...COMMON_LATEX_RULES];
  }

  /**
   * Add custom notation rule
   */
  addCustomRule(
    topic: TopicType,
    rule: Omit<NotationRule, 'category'>
  ): void {
    const newRule: NotationRule = {
      ...rule,
      category: 'IMPORTANT',
    };

    const currentRules = this.rulesCache.get(topic) || [];
    this.rulesCache.set(topic, [...currentRules, newRule]);

    logger.info({ topic, rule: newRule }, 'Custom notation rule added');
  }
}

/**
 * Export singleton instance
 */
export const notationPreserver = new NotationPreserver();

/**
 * Export convenience functions
 */
export function validateAndFixNotations(
  topic: TopicType,
  exercise: any
): { correctedExercise: any; validation: ValidationResult } {
  return notationPreserver.validateAndFixNotations(topic, exercise);
}

export function autoFixNotation(topic: TopicType, text: string): string {
  return notationPreserver.autoFixNotation(topic, text);
}

export default NotationPreserver;
