/**
 * HTML Validation Utility
 * Uses HTMLHint for comprehensive HTML validation
 */

import { HTMLHint } from 'htmlhint';
import { validateLiquidHTML } from './liquidTemplateSupport';

// Default message formatter for when intl is not available
const defaultMessageFormatter = (messageKey, values = {}) => {
  // Fallback messages for when intl is not available
  const fallbackMessages = {
    'validator.validationFailed': `Validation failed: ${values.error || 'Unknown error'}`,
    'validator.liquidValidationFailed': 'Liquid validation failed',
    'validator.unsafeProtocolDetected': `Potentially unsafe protocol detected: ${values.protocol || 'unknown'}`,
    'validator.scriptTagsDetected': 'Script tags detected - may be filtered in production',
    'validator.outlookIncompatible': `Element may not be supported in Outlook: ${values.element || 'unknown'}`,
    'validator.emailCssUnsupported': 'CSS feature may not be supported in email clients',
    'validator.mobileIncompatible': `Element may not be supported on mobile: ${values.element || 'unknown'}`,
    'validator.largeImageDetected': 'Large image dimensions detected - consider mobile optimization',
    'validator.unclosedCssRule': 'Unclosed CSS rule detected',
    'validator.emptyCssRule': 'Empty CSS rule detected',
    'validator.cssValidationFailed': `CSS validation failed: ${values.error || 'Unknown error'}`,
  };

  return fallbackMessages[messageKey] || messageKey;
};

// Custom HTMLHint rules configuration
const HTML_RULES = {
  'tagname-lowercase': true,
  'attr-lowercase': true,
  'attr-value-double-quotes': true,
  'attr-value-not-empty': false,
  'attr-no-duplication': true,
  'doctype-first': false, // Allow HTML fragments
  'tag-pair': true,
  'tag-self-close': false,
  'spec-char-escape': false, // Allow > in HTML content - it's valid
  'id-unique': true,
  'src-not-empty': true,
  'title-require': false, // Allow fragments without title
  'alt-require': true,
  'doctype-html5': false, // Allow HTML fragments
  'id-class-value': 'dash',
  'style-disabled': false, // Allow inline styles
  'inline-style-disabled': false, // Allow inline styles
  'inline-script-disabled': false, // Allow inline scripts
  'space-tab-mixed-disabled': 'space',
  'id-class-ad-disabled': false,
  'href-abs-or-rel': false,
  'attr-unsafe-chars': true,
};

// Additional custom validation rules
const CUSTOM_VALIDATIONS = {
  // Check for potentially problematic patterns
  UNSAFE_PROTOCOLS: /javascript:|data:|vbscript:/gi,
  UNCLOSED_TAGS: /<(?!\/|!)(\w+)(?:[^>]*[^\/])?>(?!.*<\/\1>)/gi,
  MALFORMED_ATTRIBUTES: /\s(\w+)(?!=)/g,
  SCRIPT_TAGS: /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,

  // Email-specific validations
  OUTLOOK_INCOMPATIBLE: /<(canvas|video|audio|svg)\b/gi,
  UNSUPPORTED_CSS: /@(media|keyframes|supports)\s*\{/gi,

  // InApp-specific validations
  MOBILE_INCOMPATIBLE: /<(object|embed|applet)\b/gi,
  LARGE_IMAGES: /width\s*:\s*[5-9]\d{2,}px|height\s*:\s*[5-9]\d{2,}px/gi,
};

/**
 * Validates HTML content and returns detailed error information
 * @param {string} html - HTML content to validate
 * @param {string} variant - Editor variant ('email' or 'inapp')
 * @param {Function} formatMessage - Message formatter function for internationalization
 * @returns {Object} Validation result with errors and warnings
 */
export const validateHTML = (html, variant = 'email', formatMessage = defaultMessageFormatter, options = {}) => {
  if (!html || typeof html !== 'string') {
    return {
      isValid: true,
      errors: [],
      warnings: [],
      info: [],
    };
  }

  const results = {
    isValid: true,
    errors: [],
    warnings: [],
    info: [],
  };

  try {
    // Run HTMLHint validation
    const htmlHintResults = HTMLHint.verify(html, HTML_RULES);

    // Process HTMLHint results
    htmlHintResults.forEach((issue) => {
      const error = {
        type: issue.type,
        message: issue.message,
        line: issue.line,
        column: issue.col,
        rule: issue.rule.id,
        severity: getSeverityLevel(issue.type, issue.rule.id),
        source: 'htmlhint',
      };

      if (error.severity === 'warning') {
        results.warnings.push(error);
      } else if (error.severity === 'info') {
        results.info.push(error);
      } else {
        results.warnings.push(error);
      }
    });
  } catch (error) {
    // HTMLHint failed, but we still want to run custom validations and Liquid validation
    results.warnings.push({
      type: 'warning',
      message: formatMessage('validator.validationFailed', { error: error.message }),
      line: 1,
      column: 1,
      rule: 'validation-error',
      severity: 'warning',
      source: 'validator',
    });
  }

  // Always run custom validations and Liquid validation, even if HTMLHint failed
  // This ensures unsafe protocol detection and other critical validations still run
  runCustomValidations(html, variant, results, formatMessage);
  if (!options.skipLiquidValidation) {
    runLiquidValidation(html, variant, results, formatMessage);
  }

  return results;
};

/**
 * Determines severity level based on error type and rule.
 * ONLY Rule Group #1 (Input & Sanitization) is blocking; that is handled in
 * contentSanitizer/useValidation. All HTML/CSS/Liquid/security rules here are
 * WARNING only for backward compatibility with CKEditor legacy templates.
 */
const getSeverityLevel = (type, ruleId) => {
  const warningRules = [
    'tag-pair',
    'attr-no-duplication',
    'id-unique',
    'spec-char-escape',
    'tagname-lowercase',
    'attr-lowercase',
    'attr-value-double-quotes',
    'alt-require',
  ];

  if (warningRules.includes(ruleId)) {
    return 'warning';
  }
  // Downgrade HTMLHint "error" type to warning (Rule Group #1 is sanitizer-only)
  if (type === 'error') {
    return 'warning';
  }
  return 'info';
};

/**
 * Runs custom validation rules
 * @param {string} html - HTML content to validate
 * @param {string} variant - Editor variant
 * @param {Object} results - Results object to add validation issues to
 * @param {Function} formatMessage - Message formatter function
 */
const runCustomValidations = (html, variant, results, formatMessage = defaultMessageFormatter) => {
  // Check for unsafe protocols using RegExp.exec loop
  // These are BLOCKING ERRORS (Rule Group #1: sanitizer.dangerousProtocolDetected)
  const unsafeProtocolsRegex = new RegExp(CUSTOM_VALIDATIONS.UNSAFE_PROTOCOLS.source, CUSTOM_VALIDATIONS.UNSAFE_PROTOCOLS.flags);
  unsafeProtocolsRegex.lastIndex = 0; // Reset lastIndex before running
  let match;
  while ((match = unsafeProtocolsRegex.exec(html)) !== null) {
    results.errors.push({
      type: 'error',
      message: formatMessage('validator.unsafeProtocolDetected', { protocol: match[0] }),
      line: getLineNumber(html, match.index),
      column: 1,
      rule: 'sanitizer.dangerousProtocolDetected',
      severity: 'error',
      source: 'custom',
    });
    results.isValid = false;

    // Guard against zero-length matches to avoid infinite loops
    if (match[0].length === 0) {
      unsafeProtocolsRegex.lastIndex++;
    }
  }

  // Check for script tags (security concern) using RegExp.exec loop
  const scriptTagsRegex = new RegExp(CUSTOM_VALIDATIONS.SCRIPT_TAGS.source, CUSTOM_VALIDATIONS.SCRIPT_TAGS.flags);
  scriptTagsRegex.lastIndex = 0; // Reset lastIndex before running
  while ((match = scriptTagsRegex.exec(html)) !== null) {
    results.warnings.push({
      type: 'warning',
      message: formatMessage('validator.scriptTagsDetected'),
      line: getLineNumber(html, match.index),
      column: 1,
      rule: 'script-tag-warning',
      severity: 'warning',
      source: 'custom',
    });

    // Guard against zero-length matches to avoid infinite loops
    if (match[0].length === 0) {
      scriptTagsRegex.lastIndex++;
    }
  }

  // Variant-specific validations
  if (variant === 'email') {
    validateEmailSpecific(html, results, formatMessage);
  } else if (variant === 'inapp') {
    validateInAppSpecific(html, results, formatMessage);
  }
};

/**
 * Email-specific validations
 * @param {string} html - HTML content to validate
 * @param {Object} results - Results object to add validation issues to
 * @param {Function} formatMessage - Message formatter function
 */
const validateEmailSpecific = (html, results, formatMessage = defaultMessageFormatter) => {
  // Check for Outlook incompatible elements using RegExp.exec loop
  const outlookIncompatibleRegex = new RegExp(CUSTOM_VALIDATIONS.OUTLOOK_INCOMPATIBLE.source, CUSTOM_VALIDATIONS.OUTLOOK_INCOMPATIBLE.flags);
  outlookIncompatibleRegex.lastIndex = 0; // Reset lastIndex before running
  let match;
  while ((match = outlookIncompatibleRegex.exec(html)) !== null) {
    results.warnings.push({
      type: 'warning',
      message: formatMessage('validator.outlookIncompatible', { element: match[0] }),
      line: getLineNumber(html, match.index),
      column: 1,
      rule: 'outlook-compatibility',
      severity: 'warning',
      source: 'email-specific',
    });

    // Guard against zero-length matches to avoid infinite loops
    if (match[0].length === 0) {
      outlookIncompatibleRegex.lastIndex++;
    }
  }

  // Check for unsupported CSS using RegExp.exec loop
  const unsupportedCSSRegex = new RegExp(CUSTOM_VALIDATIONS.UNSUPPORTED_CSS.source, CUSTOM_VALIDATIONS.UNSUPPORTED_CSS.flags);
  unsupportedCSSRegex.lastIndex = 0; // Reset lastIndex before running
  while ((match = unsupportedCSSRegex.exec(html)) !== null) {
    results.warnings.push({
      type: 'warning',
      message: formatMessage('validator.emailCssUnsupported'),
      line: getLineNumber(html, match.index),
      column: 1,
      rule: 'email-css-compatibility',
      severity: 'warning',
      source: 'email-specific',
    });

    // Guard against zero-length matches to avoid infinite loops
    if (match[0].length === 0) {
      unsupportedCSSRegex.lastIndex++;
    }
  }
};

/**
 * InApp-specific validations
 * @param {string} html - HTML content to validate
 * @param {Object} results - Results object to add validation issues to
 * @param {Function} formatMessage - Message formatter function
 */
const validateInAppSpecific = (html, results, formatMessage = defaultMessageFormatter) => {
  // Check for mobile incompatible elements using RegExp.exec loop
  const mobileIncompatibleRegex = new RegExp(CUSTOM_VALIDATIONS.MOBILE_INCOMPATIBLE.source, CUSTOM_VALIDATIONS.MOBILE_INCOMPATIBLE.flags);
  mobileIncompatibleRegex.lastIndex = 0; // Reset lastIndex before running
  let match;
  while ((match = mobileIncompatibleRegex.exec(html)) !== null) {
    results.warnings.push({
      type: 'warning',
      message: formatMessage('validator.mobileIncompatible', { element: match[0] }),
      line: getLineNumber(html, match.index),
      column: 1,
      rule: 'mobile-compatibility',
      severity: 'warning',
      source: 'inapp-specific',
    });

    // Guard against zero-length matches to avoid infinite loops
    if (match[0].length === 0) {
      mobileIncompatibleRegex.lastIndex++;
    }
  }

  // Check for large images that might not fit mobile screens using RegExp.exec loop
  const largeImagesRegex = new RegExp(CUSTOM_VALIDATIONS.LARGE_IMAGES.source, CUSTOM_VALIDATIONS.LARGE_IMAGES.flags);
  largeImagesRegex.lastIndex = 0; // Reset lastIndex before running
  while ((match = largeImagesRegex.exec(html)) !== null) {
    results.info.push({
      type: 'info',
      message: formatMessage('validator.largeImageDetected'),
      line: getLineNumber(html, match.index),
      column: 1,
      rule: 'mobile-image-size',
      severity: 'info',
      source: 'inapp-specific',
    });

    // Guard against zero-length matches to avoid infinite loops
    if (match[0].length === 0) {
      largeImagesRegex.lastIndex++;
    }
  }
};

/**
 * Runs Liquid template validation
 * @param {string} html - HTML content to validate
 * @param {string} variant - Editor variant
 * @param {Object} results - Results object to add validation issues to
 * @param {Function} formatMessage - Message formatter function
 */
const runLiquidValidation = (html, variant, results, formatMessage = defaultMessageFormatter) => {
  try {
    const liquidResults = validateLiquidHTML(html, variant);

    // Merge Liquid validation results
    // Client-side Liquid validation errors are blocking (genuine syntax errors)
    if (liquidResults.errors) {
      results.errors.push(...liquidResults.errors.map((e) => ({ ...e, severity: 'error' })));
      if (liquidResults.errors.length > 0) {
        results.isValid = false;
      }
    }

    if (liquidResults.warnings) {
      results.warnings.push(...liquidResults.warnings);
    }

    if (liquidResults.info) {
      results.info.push(...liquidResults.info);
    }
  } catch (error) {
    console.warn(formatMessage('validator.liquidValidationFailed'), error);
    // Don't fail the entire validation if Liquid validation has issues
  }
};

/**
 * Gets line number for a character position in text
 */
const getLineNumber = (text, position) => {
  if (position === undefined || position < 0) return 1;
  return text.substring(0, position).split('\n').length;
};

/**
 * Validates CSS content separately
 * @param {string} css - CSS content to validate
 * @param {Function} formatMessage - Message formatter function for internationalization
 * @returns {Object} Validation result with errors and warnings
 */
export const validateCSS = (css, formatMessage = defaultMessageFormatter) => {
  const results = {
    isValid: true,
    errors: [],
    warnings: [],
    info: [],
  };

  if (!css || typeof css !== 'string') {
    return results;
  }

  try {
    // Basic CSS validation patterns
    const validationPatterns = {
      unclosedBraces: /\{[^{}]*$/gm,
      invalidProperty: /[^;{}]+:\s*[^;{}]*[^;}]/g,
      missingColon: /[^;{}]+\s+[^;{}:]+;/g,
      emptyRule: /[^{}]+\{\s*\}/g,
    };

    // Check for unclosed braces using RegExp.exec loop
    const unclosedBracesRegex = new RegExp(validationPatterns.unclosedBraces.source, validationPatterns.unclosedBraces.flags);
    unclosedBracesRegex.lastIndex = 0; // Reset lastIndex before running
    let match;
    while ((match = unclosedBracesRegex.exec(css)) !== null) {
      results.warnings.push({
        type: 'warning',
        message: formatMessage('validator.unclosedCssRule'),
        line: getLineNumber(css, match.index),
        column: 1,
        rule: 'unclosed-brace',
        severity: 'warning',
        source: 'css-validator',
      });

      // Guard against zero-length matches to avoid infinite loops
      if (match[0].length === 0) {
        unclosedBracesRegex.lastIndex++;
      }
    }

    // Check for empty rules using RegExp.exec loop
    const emptyRulesRegex = new RegExp(validationPatterns.emptyRule.source, validationPatterns.emptyRule.flags);
    emptyRulesRegex.lastIndex = 0; // Reset lastIndex before running
    while ((match = emptyRulesRegex.exec(css)) !== null) {
      results.warnings.push({
        type: 'warning',
        message: formatMessage('validator.emptyCssRule'),
        line: getLineNumber(css, match.index),
        column: 1,
        rule: 'empty-rule',
        severity: 'warning',
        source: 'css-validator',
      });

      // Guard against zero-length matches to avoid infinite loops
      if (match[0].length === 0) {
        emptyRulesRegex.lastIndex++;
      }
    }
  } catch (error) {
    results.warnings.push({
      type: 'warning',
      message: formatMessage('validator.cssValidationFailed', { error: error.message }),
      line: 1,
      column: 1,
      rule: 'css-validation-error',
      severity: 'warning',
      source: 'css-validator',
    });
  }

  return results;
};

/**
 * Extracts and validates CSS from HTML content
 * @param {string} html - HTML content containing CSS
 * @param {Function} formatMessage - Message formatter function for internationalization
 * @returns {Object} Combined validation results from all CSS blocks
 */
export const extractAndValidateCSS = (html, formatMessage = defaultMessageFormatter) => {
  if (!html) {
    return {
      isValid: true, errors: [], warnings: [], info: [],
    };
  }

  // Extract CSS from style tags
  const styleTagPattern = /<style[^>]*>([\s\S]*?)<\/style>/gi;
  let match;
  const allResults = {
    isValid: true,
    errors: [],
    warnings: [],
    info: [],
  };

  while ((match = styleTagPattern.exec(html)) !== null) {
    const cssContent = match[1];
    const cssResults = validateCSS(cssContent, formatMessage);

    // Merge results
    allResults.errors.push(...cssResults.errors);
    allResults.warnings.push(...cssResults.warnings);
    allResults.info.push(...cssResults.info);

    if (!cssResults.isValid) {
      allResults.isValid = false;
    }
  }

  // Check for unclosed style tags (warning only for CKEditor legacy compatibility)
  const unclosedStylePattern = /<style[^>]*>(?![\s\S]*?<\/style>)/gi;
  if (unclosedStylePattern.test(html)) {
    allResults.warnings.push({
      type: 'warning',
      message: formatMessage('validator.unclosedCssRule'),
      line: 1,
      column: 1,
      rule: 'unclosed-style-tag',
      severity: 'warning',
      source: 'css-validator',
    });
  }

  return allResults;
};

export default {
  validateHTML,
  validateCSS,
  extractAndValidateCSS,
};
