/**
 * Validation Hook for HTML Editor
 * Manages real-time validation and error display.
 * UI gating: only Rule Group #1 (Input & Sanitization) blocks Save/Update/Preview/Test.
 * All other rules are warnings for backward compatibility with CKEditor legacy templates.
 */

import {
  useState, useEffect, useCallback, useRef,
} from 'react';
import { validateHTML, extractAndValidateCSS } from '../utils/htmlValidator';
import { sanitizeHTML, isContentSafe, findUnsafeContent } from '../utils/contentSanitizer';
import { BLOCKING_ERROR_RULE_IDS, VALIDATION_SEVERITY } from '../constants';
import { ISSUE_SOURCES } from '../utils/validationConstants';

/**
 * Custom hook for managing HTML/CSS validation
 * @param {string} content - Current content to validate
 * @param {string} variant - Editor variant ('email' or 'inapp')
 * @param {Object} options - Validation options
 * @param {Function} formatSanitizerMessage - Message formatter function for sanitizer internationalization
 * @param {Function} formatValidatorMessage - Message formatter function for validator internationalization
 * @returns {Object} Validation state and methods
 */
/**
 * Get line number for a character position in text
 */
const getLineNumberFromPosition = (text, position) => {
  if (position === undefined || position < 0) return 1;
  return text.substring(0, position).split('\n').length;
};

/**
 * Get 1-based line and column from a character position in text
 */
const getLineAndColumnFromPosition = (text, position) => {
  if (!text || position === undefined || position < 0) {
    return { line: 1, column: 1 };
  }
  const before = text.substring(0, position);
  const lines = before.split('\n');
  const line = lines.length;
  const lastLine = lines[lines.length - 1] || '';
  const column = lastLine.length + 1;
  return { line, column };
};

/**
 * Find line number for a tag or pattern in content
 * Used for API errors to locate where the error occurs
 */
const findLineNumberForTag = (content, tagName) => {
  if (!content || !tagName) return null;

  // Try to find the tag in the content
  // Look for patterns like {{ tagName }}, {{tagName}}, etc.
  const escapedTagName = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const patterns = [
    new RegExp(`\\{\\{\\s*${escapedTagName}\\s*\\}\\}`, 'g'),
    new RegExp(`\\{%[^%]*${escapedTagName}[^%]*%\\}`, 'g'),
  ];

  for (const pattern of patterns) {
    const match = pattern.exec(content);
    if (match) {
      return getLineNumberFromPosition(content, match.index);
    }
  }

  return null;
};

export const useValidation = (content, variant = 'email', options = {}, formatSanitizerMessage = null, formatValidatorMessage = null) => {
  const {
    enableRealTime = true,
    debounceMs = 500,
    enableSanitization = true,
    securityLevel = 'standard',
    apiValidationErrors = null, // API validation errors from validateLiquidTemplateContent
    isFullMode = false, // When true, skip liquid validation (standalone/full mode)
  } = options;

  // Validation state
  const [validationState, setValidationState] = useState({
    isValidating: false,
    lastValidated: null,
    htmlErrors: [],
    htmlWarnings: [],
    htmlInfo: [],
    cssErrors: [],
    cssWarnings: [],
    cssInfo: [],
    securityIssues: [],
    sanitizationWarnings: [],
    isValid: true,
    isSecure: true,
    summary: {
      totalErrors: 0,
      totalWarnings: 0,
      totalInfo: 0,
      hasSecurityIssues: false,
    },
  });

  // Refs for debouncing
  const debounceRef = useRef(null);
  const lastContentRef = useRef(null); // Initialize as null to allow first validation

  /**
   * Performs comprehensive validation
   */
  const performValidation = useCallback(async (htmlContent) => {
    if (!htmlContent) {
      setValidationState((prev) => ({
        ...prev,
        isValidating: false,
        htmlErrors: [],
        htmlWarnings: [],
        htmlInfo: [],
        cssErrors: [],
        cssWarnings: [],
        cssInfo: [],
        securityIssues: [],
        sanitizationWarnings: [],
        isValid: true,
        isSecure: true,
        summary: {
          totalErrors: 0,
          totalWarnings: 0,
          totalInfo: 0,
          hasSecurityIssues: false,
        },
      }));
      return;
    }

    setValidationState((prev) => ({ ...prev, isValidating: true }));

    try {
      // 1. HTML Validation
      const htmlValidation = validateHTML(htmlContent, variant, formatValidatorMessage, { skipLiquidValidation: isFullMode });

      // 2. CSS Validation (extract from HTML)
      const cssValidation = extractAndValidateCSS(htmlContent, formatValidatorMessage);

      // 3. Security Validation
      const isSecure = isContentSafe(htmlContent);
      const securityIssues = isSecure ? [] : findUnsafeContent(htmlContent);

      // 4. Sanitization (if enabled) – Rule Group #1 issues come from here
      let sanitizationResult = null;
      if (enableSanitization) {
        sanitizationResult = sanitizeHTML(htmlContent, variant, securityLevel, formatSanitizerMessage);
      }

      const sanitizationWarnings = sanitizationResult?.warnings || [];
      const blockingSanitizerCount = sanitizationWarnings.filter((w) => BLOCKING_ERROR_RULE_IDS.includes(w.rule)).length;
      const protocolSecurityCount = (securityIssues || []).filter((s) => ['JavaScript Protocol', 'Data URL', 'VBScript Protocol'].includes(s?.type)).length;

      // Summary: totalErrors/totalWarnings are for display; blocking count is for gating
      const totalErrors = htmlValidation.errors.length + cssValidation.errors.length + blockingSanitizerCount + protocolSecurityCount;
      const totalWarnings = htmlValidation.warnings.length + cssValidation.warnings.length
        + (sanitizationWarnings.length - blockingSanitizerCount)
        + (securityIssues.length - protocolSecurityCount);
      const totalInfo = htmlValidation.info.length + cssValidation.info.length;
      const hasSecurityIssues = securityIssues.length > 0;

      // Update state
      setValidationState({
        isValidating: false,
        lastValidated: new Date(),
        htmlErrors: htmlValidation.errors,
        htmlWarnings: htmlValidation.warnings,
        htmlInfo: htmlValidation.info,
        cssErrors: cssValidation.errors,
        cssWarnings: cssValidation.warnings,
        cssInfo: cssValidation.info,
        securityIssues,
        sanitizationWarnings,
        isValid: htmlValidation.isValid && cssValidation.isValid,
        isSecure,
        summary: {
          totalErrors,
          totalWarnings,
          totalInfo,
          hasSecurityIssues,
        },
      });
    } catch (error) {
      console.error('Validation error:', error);
      setValidationState((prev) => ({
        ...prev,
        isValidating: false,
        htmlErrors: [{
          type: 'error',
          message: `Validation failed: ${error.message}`,
          line: 1,
          column: 1,
          rule: 'validation-error',
          severity: 'error',
          source: 'validator',
        }],
        isValid: false,
        summary: {
          ...prev.summary,
          totalErrors: 1,
        },
      }));
    }
  }, [variant, enableSanitization, securityLevel, formatSanitizerMessage, formatValidatorMessage, isFullMode]);

  /**
   * Validates content with debouncing
   */
  const validateContent = useCallback((htmlContent) => {
    // Clear existing debounce
    if (debounceRef.current) {
      clearTimeout(debounceRef.current);
    }

    // Skip if content hasn't changed
    if (htmlContent === lastContentRef.current) {
      return;
    }

    lastContentRef.current = htmlContent;

    if (enableRealTime) {
      // Debounce validation
      debounceRef.current = setTimeout(() => {
        performValidation(htmlContent);
      }, debounceMs);
    }
  }, [performValidation, enableRealTime, debounceMs]);

  /**
   * Force immediate validation
   */
  const forceValidation = useCallback(() => {
    if (debounceRef.current) {
      clearTimeout(debounceRef.current);
    }
    performValidation(content);
  }, [performValidation, content]);

  /**
   * Clear all validation results
   */
  const clearValidation = useCallback(() => {
    if (debounceRef.current) {
      clearTimeout(debounceRef.current);
    }
    // Keep lastContentRef to prevent unnecessary revalidation of the same content
    // lastContentRef.current remains unchanged
    setValidationState({
      isValidating: false,
      lastValidated: null,
      htmlErrors: [],
      htmlWarnings: [],
      htmlInfo: [],
      cssErrors: [],
      cssWarnings: [],
      cssInfo: [],
      securityIssues: [],
      sanitizationWarnings: [],
      isValid: true,
      isSecure: true,
      summary: {
        totalErrors: 0,
        totalWarnings: 0,
        totalInfo: 0,
        hasSecurityIssues: false,
      },
    });
  }, []);

  /**
   * Get errors by severity level
   */
  const getErrorsBySeverity = useCallback((severity) => {
    const allErrors = [
      ...validationState.htmlErrors,
      ...validationState.htmlWarnings,
      ...validationState.htmlInfo,
      ...validationState.cssErrors,
      ...validationState.cssWarnings,
      ...validationState.cssInfo,
    ];

    return allErrors.filter((error) => error.severity === severity);
  }, [validationState]);

  /**
   * Get errors by source
   */
  const getErrorsBySource = useCallback((source) => {
    const allErrors = [
      ...validationState.htmlErrors,
      ...validationState.htmlWarnings,
      ...validationState.htmlInfo,
      ...validationState.cssErrors,
      ...validationState.cssWarnings,
      ...validationState.cssInfo,
    ];

    return allErrors.filter((error) => error.source === source);
  }, [validationState]);

  /**
   * Extract line number from error message if present
   * API errors might contain line numbers in messages like "Error at line 5" or "Line 10: error"
   * Also tries to find line number by searching for the problematic tag in content
   */
  const extractLineNumberFromMessage = useCallback((message) => {
    if (!message || typeof message !== 'string') {
      return null;
    }
    // Try to match patterns like "line 5", "Line 10", "at line 15", "line: 20", etc.
    const lineMatch = message.match(/\b(?:line|Line|LINE)\s*:?\s*(\d+)\b/i);
    if (lineMatch && lineMatch[1]) {
      return parseInt(lineMatch[1], 10);
    }

    // Try to extract tag name from error message (e.g., "Unsupported tags: test" -> "test")
    const tagMatch = message.match(/(?:tag|tags|Tag|Tags)[\s:]+([a-zA-Z_][a-zA-Z0-9_.]*)/i);
    if (tagMatch && tagMatch[1] && content) {
      const tagName = tagMatch[1];
      const lineNumber = findLineNumberForTag(content, tagName);
      if (lineNumber) {
        return lineNumber;
      }
    }

    return null;
  }, [content]);

  /**
   * Get all errors and warnings combined
   * Includes both client-side validation errors and API validation errors
   */
  const getAllIssues = useCallback(() => {
    // API errors (liquid + standard) are blocking – they block Save/Update/Preview/Test
    const apiLiquidErrors = (isFullMode ? [] : (apiValidationErrors?.liquidErrors || [])).map((errorMessage) => {
      const extractedLine = extractLineNumberFromMessage(errorMessage);
      return {
        type: VALIDATION_SEVERITY.ERROR,
        message: errorMessage,
        line: extractedLine,
        column: null,
        rule: 'liquid-api-validation',
        severity: VALIDATION_SEVERITY.ERROR,
        source: ISSUE_SOURCES.LIQUID,
      };
    });

    const apiStandardErrors = (apiValidationErrors?.standardErrors || []).map((errorMessage) => {
      const extractedLine = extractLineNumberFromMessage(errorMessage);
      return {
        type: VALIDATION_SEVERITY.ERROR,
        message: errorMessage,
        line: extractedLine,
        column: null,
        rule: 'standard-api-validation',
        severity: VALIDATION_SEVERITY.ERROR,
        source: 'api-validator',
      };
    });

    // Security: protocol types are Rule Group #1 (blocking); others are warnings
    // Use issue.position (from findUnsafeContent) to show real line/column when available
    const PROTOCOL_TYPES = ['JavaScript Protocol', 'Data URL', 'VBScript Protocol'];
    const contentStr = typeof content === 'string' ? content : '';
    const securityAsIssues = (validationState.securityIssues || []).map((issue) => {
      const isBlocking = PROTOCOL_TYPES.includes(issue?.type);
      const { line, column } = (issue?.position !== undefined && contentStr)
        ? getLineAndColumnFromPosition(contentStr, issue.position)
        : { line: 1, column: 1 };
      return {
        type: isBlocking ? VALIDATION_SEVERITY.ERROR : VALIDATION_SEVERITY.WARNING,
        message: `Security issue: ${issue.type}`,
        line,
        column,
        rule: isBlocking ? 'sanitizer.dangerousProtocolDetected' : 'security-violation',
        severity: isBlocking ? VALIDATION_SEVERITY.ERROR : VALIDATION_SEVERITY.WARNING,
        source: 'security',
      };
    });

    // Sanitization warnings (Rule Group #1 entries have rule set by contentSanitizer)
    const sanitizationAsIssues = (validationState.sanitizationWarnings || []).map((w) => {
      const sev = BLOCKING_ERROR_RULE_IDS.includes(w.rule) ? VALIDATION_SEVERITY.ERROR : VALIDATION_SEVERITY.WARNING;
      return {
        ...w,
        severity: sev,
        rule: w.rule || 'sanitizer.unknown',
        line: w.line ?? 1,
        column: w.column ?? 1,
        source: w.source || 'sanitizer',
      };
    });

    const allIssues = [
      ...(validationState.htmlErrors || []),
      ...(validationState.htmlWarnings || []),
      ...(validationState.htmlInfo || []),
      ...(validationState.cssErrors || []),
      ...(validationState.cssWarnings || []),
      ...(validationState.cssInfo || []),
      ...securityAsIssues,
      ...sanitizationAsIssues,
      ...apiLiquidErrors,
      ...apiStandardErrors,
    ].sort((a, b) => {
      // Sort by severity (error > warning > info) then by line number
      const severityOrder = { error: 0, warning: 1, info: 2 };
      if (severityOrder[a.severity] !== severityOrder[b.severity]) {
        return severityOrder[a.severity] - severityOrder[b.severity];
      }
      return (a.line || 0) - (b.line || 0);
    });

    // Ensure we always return an array
    return Array.isArray(allIssues) ? allIssues : [];
  }, [validationState, apiValidationErrors, extractLineNumberFromMessage, content, isFullMode]);

  /**
   * Check if validation is clean (no errors or warnings)
   * Includes API validation errors in the check
   */
  const isClean = useCallback(() => {
    const liquidErrorCount = isFullMode ? 0 : (apiValidationErrors?.liquidErrors?.length || 0);
    const hasApiErrors = liquidErrorCount + (apiValidationErrors?.standardErrors?.length || 0) > 0;
    return validationState.summary.totalErrors === 0
           && validationState.summary.totalWarnings === 0
           && !validationState.summary.hasSecurityIssues
           && !hasApiErrors;
  }, [validationState.summary, apiValidationErrors, isFullMode]);

  // Effect to validate content when it changes
  useEffect(() => {
    if (enableRealTime && content !== lastContentRef.current) {
      validateContent(content);
    }
  }, [content, validateContent, enableRealTime]);

  // Cleanup on unmount
  useEffect(() => () => {
    if (debounceRef.current) {
      clearTimeout(debounceRef.current);
    }
  }, []);

  const hasApiLiquidErrors = isFullMode ? false : (apiValidationErrors?.liquidErrors?.length || 0) > 0;
  const hasApiErrors = hasApiLiquidErrors || (apiValidationErrors?.standardErrors?.length || 0) > 0;

  const protocolTypes = ['JavaScript Protocol', 'Data URL', 'VBScript Protocol'];
  // Client-side Liquid validation errors are blocking (genuine syntax errors) - skip in full mode
  const hasClientSideLiquidErrors = isFullMode ? false : (validationState.htmlErrors || []).some((e) => e.source === ISSUE_SOURCES.LIQUID && e.severity === VALIDATION_SEVERITY.ERROR);
  const hasBlockingErrors = (validationState.sanitizationWarnings || []).some((w) => BLOCKING_ERROR_RULE_IDS.includes(w.rule)) || (validationState.securityIssues || []).some((s) => protocolTypes.includes(s?.type)) || hasApiErrors || hasClientSideLiquidErrors;

  return {
    // Validation state
    ...validationState,

    // Methods
    validateContent,
    forceValidation,
    clearValidation,

    // Helpers
    getErrorsBySeverity,
    getErrorsBySource,
    getAllIssues,
    isClean,

    // Computed properties
    hasErrors: validationState.summary.totalErrors > 0 || hasApiErrors,
    hasWarnings: validationState.summary.totalWarnings > 0,
    hasSecurityIssues: validationState.summary.hasSecurityIssues,
    /** True only when Rule Group #1 (Input & Sanitization) issues exist. Use for UI gating. */
    hasBlockingErrors,
  };
};

export default useValidation;
