/**
 * Liquid Template Language Support for CodeMirror 6
 * Provides syntax highlighting and validation for Liquid templates in HTML
 */

import { html } from '@codemirror/lang-html';
import { syntaxHighlighting, HighlightStyle } from '@codemirror/language';
import { tags } from '@lezer/highlight';
import { EditorView } from '@codemirror/view';
import { VALIDATION_SEVERITY } from '../constants';
import { ISSUE_SOURCES } from './validationConstants';

/**
 * Liquid Template Syntax Patterns
 */
export const LIQUID_PATTERNS = {
  // Liquid output tags: {{ variable }}
  OUTPUT_TAG: /\{\{\s*([^}]+)\s*\}\}/g,

  // Liquid logic tags: {% if condition %}, {% for item in items %}, etc.
  LOGIC_TAG: /\{%\s*([^%]+)\s*%\}/g,

  // Liquid comments: {% comment %} ... {% endcomment %}
  COMMENT_TAG: /\{%\s*comment\s*%\}[\s\S]*?\{%\s*endcomment\s*%\}/g,

  // Liquid filters: {{ variable | filter }}
  FILTER: /\|\s*([a-zA-Z_][a-zA-Z0-9_]*)/g,

  // Liquid variables and properties
  VARIABLE: /([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)/g,

  // Liquid keywords
  KEYWORDS: /\b(if|unless|elsif|else|endif|for|endfor|case|when|endcase|assign|capture|endcapture|include|render|layout|break|continue|cycle|tablerow|endtablerow|raw|endraw|liquid)\b/g,

  // Liquid operators
  OPERATORS: /\b(and|or|not|contains|in|==|!=|<|>|<=|>=)\b/g,

  // String literals in Liquid
  STRING_LITERALS: /(["'])((?:\\.|(?!\1)[^\\])*?)\1/g,
};

/**
 * Enhanced VS Code Theme with Liquid Template Support
 */
export const liquidVSCodeTheme = HighlightStyle.define([
  // Base content
  { tag: tags.content, color: '#d4d4d4' },
  { tag: tags.name, color: '#d4d4d4' },

  // HTML elements
  { tag: tags.tagName, color: '#569cd6', fontWeight: 'bold' },
  { tag: tags.attributeName, color: '#92c5f8' },
  { tag: tags.attributeValue, color: '#ce9178' },
  { tag: tags.angleBracket, color: '#808080' },
  { tag: tags.quote, color: '#ce9178' },

  // Standard syntax
  { tag: tags.comment, color: '#6a9955', fontStyle: 'italic' },
  { tag: tags.string, color: '#ce9178' },
  { tag: tags.number, color: '#b5cea8' },
  { tag: tags.keyword, color: '#569cd6', fontWeight: 'bold' },
  { tag: tags.operator, color: '#d4d4d4' },
  { tag: tags.variableName, color: '#9cdcfe' },
  { tag: tags.function, color: '#dcdcaa' },

  // Liquid-specific styling (using meta and special tags)
  { tag: tags.meta, color: '#c586c0', fontWeight: 'bold' }, // Liquid tags {% %}
  { tag: tags.special, color: '#4fc1ff' }, // Liquid output {{ }}
  { tag: tags.processingInstruction, color: '#569cd6' }, // Liquid keywords
  { tag: tags.atom, color: '#4ec9b0' }, // Liquid filters
  { tag: tags.punctuation, color: '#d4d4d4' },
  { tag: tags.bracket, color: '#ffd700' },
  { tag: tags.brace, color: '#ffd700' },
]);

/**
 * Liquid Template Validator
 */
export class LiquidValidator {
  constructor() {
    this.errors = [];
    this.warnings = [];
    this.info = [];
  }

  /**
   * Validates Liquid template syntax in HTML content
   * @param {string} html - HTML content with Liquid templates
   * @returns {Object} Validation results
   */
  validate(html) {
    this.errors = [];
    this.warnings = [];
    this.info = [];

    if (!html || typeof html !== 'string') {
      return this.getResults();
    }

    // Validate Liquid syntax
    this.validateLiquidTags(html);
    this.validateLiquidLogic(html);
    this.validateLiquidFilters(html);
    this.validateLiquidVariables(html);

    return this.getResults();
  }

  /**
   * Validates Liquid tag syntax
   */
  validateLiquidTags(html) {
    // Only validate actual Liquid tags, not HTML content
    // Check for unclosed output tags: {{ without matching }}
    this.validateUnclosedOutputTags(html);

    // Check for unclosed logic tags: {% without matching %}
    this.validateUnclosedLogicTags(html);

    // Check for nested braces within Liquid tags
    this.validateNestedBraces(html);
  }

  /**
   * Validates unclosed output tags using stack-based matching
   */
  validateUnclosedOutputTags(html) {
    const stack = []; // Stack to track opening {{ positions

    // Create a combined pattern to find all {{ and }} tokens in order
    const combinedPattern = /(\{\{|\}\})/g;
    let match;

    // Perform single left-to-right scan
    while ((match = combinedPattern.exec(html)) !== null) {
      const token = match[1];
      const position = match.index;

      if (token === '{{') {
        // Push opening brace position onto stack
        stack.push(position);
      } else if (token === '}}') {
        // Found closing brace
        if (stack.length > 0) {
          // Pop matching opening brace from stack
          stack.pop();
        } else {
          // Stray closing brace - no matching opening brace
          this.errors.push({
            type: VALIDATION_SEVERITY.ERROR,
            message: 'Stray closing }} without matching opening {{',
            line: this.getLineNumber(html, position),
            column: 1,
            rule: 'liquid-stray-closing-output',
            severity: VALIDATION_SEVERITY.ERROR,
            source: ISSUE_SOURCES.LIQUID,
          });
        }
      }
    }

    // After scan, any remaining entries on stack are unclosed opening braces
    if (stack.length > 0) {
      // Report each unclosed opening brace
      stack.forEach((position) => {
        this.errors.push({
          type: VALIDATION_SEVERITY.ERROR,
          message: 'unclosed Liquid output tag - missing }}',
          line: this.getLineNumber(html, position),
          column: 1,
          rule: 'liquid-unclosed-output',
          severity: VALIDATION_SEVERITY.ERROR,
          source: ISSUE_SOURCES.LIQUID,
        });
      });
    }
  }

  /**
   * Validates unclosed logic tags
   */
  validateUnclosedLogicTags(html) {
    // Find all {% positions
    const openTags = [];
    let match;
    const openPattern = /\{%/g;
    while ((match = openPattern.exec(html)) !== null) {
      openTags.push(match.index);
    }

    // Find all %} positions
    const closeTags = [];
    const closePattern = /%\}/g;
    while ((match = closePattern.exec(html)) !== null) {
      closeTags.push(match.index);
    }

    // Check if we have unmatched opening tags
    if (openTags.length > closeTags.length) {
      const unmatchedCount = openTags.length - closeTags.length;
      this.errors.push({
        type: VALIDATION_SEVERITY.ERROR,
        message: `${unmatchedCount} unclosed Liquid logic tag(s) - missing %}`,
        line: this.getLineNumber(html, openTags[openTags.length - 1]),
        column: 1,
        rule: 'liquid-unclosed-logic',
        severity: VALIDATION_SEVERITY.ERROR,
        source: ISSUE_SOURCES.LIQUID,
      });
    }
  }

  /**
   * Validates nested braces within Liquid tags
   */
  validateNestedBraces(html) {
    // Check for {{ inside {{ }} tags
    const nestedOutputPattern = /\{\{[^}]*\{\{[^}]*\}\}/g;
    const nestedOutput = html.match(nestedOutputPattern);
    if (nestedOutput) {
      nestedOutput.forEach((match) => {
        this.errors.push({
          type: VALIDATION_SEVERITY.ERROR,
          message: `Nested braces in Liquid output tag: ${match}`,
          line: this.getLineNumber(html, html.indexOf(match)),
          column: 1,
          rule: 'liquid-nested-braces',
          severity: VALIDATION_SEVERITY.ERROR,
          source: ISSUE_SOURCES.LIQUID,
        });
      });
    }

    // Check for {% inside {% %} tags
    const nestedLogicPattern = /\{%[^%]*\{%[^%]*%\}/g;
    const nestedLogic = html.match(nestedLogicPattern);
    if (nestedLogic) {
      nestedLogic.forEach((match) => {
        this.errors.push({
          type: VALIDATION_SEVERITY.ERROR,
          message: `Nested braces in Liquid logic tag: ${match}`,
          line: this.getLineNumber(html, html.indexOf(match)),
          column: 1,
          rule: 'liquid-nested-braces',
          severity: VALIDATION_SEVERITY.ERROR,
          source: ISSUE_SOURCES.LIQUID,
        });
      });
    }
  }

  /**
   * Validates Liquid logic blocks
   */
  validateLiquidLogic(html) {
    const logicTags = [];
    let match;

    // Extract all logic tags
    const logicPattern = /\{%\s*([^%]+)\s*%\}/g;
    while ((match = logicPattern.exec(html)) !== null) {
      const content = match[1].trim();
      const keyword = content.split(/\s+/)[0];

      logicTags.push({
        full: match[0],
        content,
        keyword,
        position: match.index,
        line: this.getLineNumber(html, match.index),
      });
    }

    // Check for balanced tags
    this.validateBalancedTags(logicTags);
  }

  /**
   * Validates balanced Liquid tags (if/endif, for/endfor, etc.)
   */
  validateBalancedTags(logicTags) {
    const stack = [];
    const pairs = {
      if: 'endif',
      unless: 'endunless',
      for: 'endfor',
      case: 'endcase',
      capture: 'endcapture',
      comment: 'endcomment',
      tablerow: 'endtablerow',
      raw: 'endraw',
    };

    logicTags.forEach((tag) => {
      const {keyword} = tag;

      if (pairs[keyword]) {
        // Opening tag
        stack.push({ keyword, tag });
      } else if (Object.values(pairs).includes(keyword)) {
        // Closing tag
        const expectedOpening = Object.keys(pairs).find((key) => pairs[key] === keyword);
        const lastOpening = stack.pop();

        if (!lastOpening) {
          this.errors.push({
            type: VALIDATION_SEVERITY.ERROR,
            message: `Unexpected closing tag: {% ${keyword} %}`,
            line: tag.line,
            column: 1,
            rule: 'liquid-unexpected-closing',
            severity: VALIDATION_SEVERITY.ERROR,
            source: ISSUE_SOURCES.LIQUID,
          });
        } else if (lastOpening.keyword !== expectedOpening) {
          this.errors.push({
            type: VALIDATION_SEVERITY.ERROR,
            message: `Mismatched Liquid tags: {% ${lastOpening.keyword} %} ... {% ${keyword} %}`,
            line: tag.line,
            column: 1,
            rule: 'liquid-mismatched-tags',
            severity: VALIDATION_SEVERITY.ERROR,
            source: ISSUE_SOURCES.LIQUID,
          });
        }
      }
    });

    // Check for unclosed opening tags
    if (stack.length > 0) {
      stack.forEach(({ keyword, tag }) => {
        const expectedClosing = pairs[keyword];
        const error = {
          type: VALIDATION_SEVERITY.ERROR,
          message: `Unclosed Liquid tag: {% ${keyword} %} - missing {% ${expectedClosing} %}`,
          line: tag.line,
          column: 1,
          rule: 'liquid-unclosed-tag',
          severity: VALIDATION_SEVERITY.ERROR,
          source: ISSUE_SOURCES.LIQUID,
        };
        this.errors.push(error);
      });
    }

    // Check for unclosed opening tags
    stack.forEach((unclosed) => {
      this.errors.push({
        type: VALIDATION_SEVERITY.ERROR,
        message: `Unclosed Liquid tag: {% ${unclosed.keyword} %}`,
        line: unclosed.tag.line,
        column: 1,
        rule: 'liquid-unclosed-tag',
        severity: VALIDATION_SEVERITY.ERROR,
        source: ISSUE_SOURCES.LIQUID,
      });
    });
  }

  /**
   * Validates Liquid filters
   */
  validateLiquidFilters(html) {
    // Check for malformed filters
    const malformedFilters = html.match(/\|\s*\||\|\s*$/gm);
    if (malformedFilters) {
      malformedFilters.forEach((match) => {
        this.warnings.push({
          type: VALIDATION_SEVERITY.WARNING,
          message: `Malformed Liquid filter: ${match.trim()}`,
          line: this.getLineNumber(html, html.indexOf(match)),
          column: 1,
          rule: 'liquid-malformed-filter',
          severity: VALIDATION_SEVERITY.WARNING,
          source: ISSUE_SOURCES.LIQUID,
        });
      });
    }

    // Check for common filter usage
    // Standard Liquid filters that are commonly used and shouldn't trigger info messages
    // This list includes standard Liquid filters to avoid false positives
    const commonFilters = [
      // String filters
      'capitalize', 'upcase', 'downcase', 'strip', 'lstrip', 'rstrip', 'strip_html', 'strip_newlines',
      'truncate', 'truncatewords', 'prepend', 'append', 'remove', 'remove_first', 'replace', 'replace_first',
      'split', 'join', 'concat', 'escape', 'escape_once', 'newline_to_br',
      // Array filters
      'first', 'last', 'size', 'slice', 'sort', 'sort_natural', 'map', 'where', 'uniq', 'compact', 'reverse',
      // Number filters
      'abs', 'ceil', 'floor', 'round', 'times', 'divided_by', 'modulo', 'minus', 'plus', 'at_least', 'at_most',
      // Date filters
      'date',
      // Other common filters
      'default', 'url_encode', 'url_decode',
    ];
    const filterPattern = /\|\s*([a-zA-Z_][a-zA-Z0-9_]*)/g;
    let filterMatch;

    while ((filterMatch = filterPattern.exec(html)) !== null) {
      const filterName = filterMatch[1];
      if (!commonFilters.includes(filterName)) {
        // Only show info for truly custom/unknown filters
        // Standard Liquid filters are now included in commonFilters list
        this.info.push({
          type: VALIDATION_SEVERITY.INFO,
          message: `Using filter: ${filterName}`,
          line: this.getLineNumber(html, filterMatch.index),
          column: 1,
          rule: 'liquid-filter-usage',
          severity: VALIDATION_SEVERITY.INFO,
          source: ISSUE_SOURCES.LIQUID,
        });
      }
    }
  }

  /**
   * Validates Liquid variables
   */
  validateLiquidVariables(html) {
    // Check for undefined variable patterns (basic check)
    const suspiciousVariables = html.match(/\{\{\s*[^}]*undefined[^}]*\s*\}\}/g);
    if (suspiciousVariables) {
      suspiciousVariables.forEach((match) => {
        this.warnings.push({
          type: VALIDATION_SEVERITY.WARNING,
          message: `Potentially undefined variable: ${match}`,
          line: this.getLineNumber(html, html.indexOf(match)),
          column: 1,
          rule: 'liquid-undefined-variable',
          severity: VALIDATION_SEVERITY.WARNING,
          source: ISSUE_SOURCES.LIQUID,
        });
      });
    }
  }


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

  /**
   * Returns validation results
   */
  getResults() {
    return {
      isValid: this.errors.length === 0,
      errors: this.errors,
      warnings: this.warnings,
      info: this.info,
    };
  }
}

/**
 * Enhanced CodeMirror extensions with Liquid support
 */
export const createLiquidExtensions = () => [
  // HTML language support (base)
  html(),

  // Enhanced syntax highlighting with Liquid support
  syntaxHighlighting(liquidVSCodeTheme),

  // Editor theme
  EditorView.theme({
    "&": {
      height: "500px",
      backgroundColor: "#1e1e1e",
      fontSize: "14px",
      fontFamily: "'DM Mono', 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace",
    },
    ".cm-content": {
      padding: "12px",
      backgroundColor: "#1e1e1e",
      fontSize: "14px",
      lineHeight: "20px",
      fontFamily: "'DM Mono', 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace",
      color: "#d4d4d4",
    },
    ".cm-focused": {
      outline: "none",
    },
    ".cm-lineNumbers": {
      fontSize: "14px",
      lineHeight: "20px",
      color: "#858585",
      backgroundColor: "#1e1e1e",
      paddingRight: "8px",
      paddingLeft: "8px",
      borderRight: "1px solid #3e3e3e",
      minWidth: "45px",
      textAlign: "right",
    },
    ".cm-gutters": {
      backgroundColor: "#1e1e1e",
      borderRight: "1px solid #3e3e3e",
    },
    ".cm-activeLine": {
      backgroundColor: "#2a2d2e",
    },
    ".cm-selection": {
      backgroundColor: "#264f78",
    },
    ".cm-cursor": {
      borderLeft: "2px solid #ffffff",
    },
  }),
];

/**
 * Validates HTML content with Liquid template support
 * @param {string} html - HTML content with Liquid templates
 * @param {string} variant - Editor variant ('email' or 'inapp')
 * @returns {Object} Validation results
 */
export const validateLiquidHTML = (html, variant = 'email') => {
  const liquidValidator = new LiquidValidator();
  return liquidValidator.validate(html);
};

/**
 * Common Liquid template examples for autocomplete/snippets
 */
export const LIQUID_SNIPPETS = {
  // Output tags
  customer_name: '{{ customer.first_name }} {{ customer.last_name }}',
  customer_email: '{{ customer.email }}',
  current_date: '{{ "now" | date: "%B %d, %Y" }}',
  organization_name: '{{ organization.name }}',

  // Logic tags
  if_statement: '{% if condition %}\n  <!-- content -->\n{% endif %}',
  for_loop: '{% for item in collection %}\n  {{ item.name }}\n{% endfor %}',
  unless_statement: '{% unless condition %}\n  <!-- content -->\n{% endunless %}',
  case_statement: '{% case variable %}\n  {% when "value1" %}\n    <!-- content -->\n  {% when "value2" %}\n    <!-- content -->\n  {% else %}\n    <!-- default content -->\n{% endcase %}',

  // Common filters
  date_filter: '{{ date_variable | date: "%B %d, %Y" }}',
  capitalize_filter: '{{ text | capitalize }}',
  truncate_filter: '{{ text | truncate: 50 }}',
  default_filter: '{{ variable | default: "Default Value" }}',
};

export default {
  LIQUID_PATTERNS,
  liquidVSCodeTheme,
  LiquidValidator,
  createLiquidExtensions,
  validateLiquidHTML,
  LIQUID_SNIPPETS,
};
