import { Component, OnInit } from '@angular/core';
import * as ace from 'ace-builds';
import 'ace-builds/src-noconflict/mode-xml'; // Adjust mode as per your file type
import 'ace-builds/src-noconflict/theme-monokai'; // Theme for better visual distinction
import { Diff } from 'diff'; // Correctly import the diff function

@Component({
  selector: 'app-xml-validator',
  templateUrl: './xml-validator.component.html',
  styleUrls: ['./xml-validator.component.css']
})
export class XmlValidatorComponent implements OnInit {
  editor1: ace.Ace.Editor;
  editor2: ace.Ace.Editor;
  content1: string = '';
  content2: string = '';

  constructor() { }

  ngOnInit(): void {
  //  this.initializeEditors();

     // Populate editors with sample content
    // this.editor1.setValue(`Hello World`);
     //this.editor2.setValue(`Hello Universe`);

     this.editor1 = ace.edit('editor1');
    this.editor1.getSession().setMode('ace/mode/xml'); // Adjust mode as per your file type
    this.editor1.setTheme('ace/theme/monokai');
   // this.editor1.setReadOnly(true); // Read-only for initial content
    this.editor1.setValue("Hello world",0)

    this.editor2 = ace.edit('editor2');
    this.editor2.getSession().setMode('ace/mode/xml'); // Adjust mode as per your file type
    this.editor2.setTheme('ace/theme/monokai');
    //this.editor2.setReadOnly(true); // Read-only for initial content
    this.editor2.setValue("Hello universe",0)
  }

   initializeEditors(): void {
    this.editor1 = ace.edit('editor1', { mode: 'ace/mode/text', theme: 'ace/theme/monokai' });
    this.editor2 = ace.edit('editor2', { mode: 'ace/mode/text', theme: 'ace/theme/monokai' });
  }

  compareFiless() {
    debugger;
    if (!Diff) { // Ensure diff is properly imported
      console.error('Diff function is not available');
      return;
    }

    const changes = Diff(this.editor1.getValue(), this.editor2.getValue());

    this.editor1.getSession().clearAnnotations();
    this.editor2.getSession().clearAnnotations();

    changes.forEach((change) => {
      if (change.removed) {
        debugger;
        const range = new ace.Range(change.startLine, change.startColumn, change.endLine, change.endColumn);
        this.editor1.getSession().addMarker(range, 'diff-removed', 'text');
      }
      if (change.added) {
        debugger;
        const range = new ace.Range(change.value.startLine, change.value.startColumn, change.value.endLine, change.value.endColumn);
        this.editor2.getSession().addMarker(range, 'diff-added', 'text');
      }
    });
  }

  compareFiles() {
    const content1 = this.editor1.getValue();
    const content2 = this.editor2.getValue();

    const diffs = Diff.diffWords(content1, content2);

    this.highlightDifferences(this.editor1, content1, diffs, 'file1');
    this.highlightDifferences(this.editor2, content2, diffs, 'file2');
  }

  compareSelections(): void {
    debugger;
    const selection1 = this.editor1.getSelectedText();
    const selection2 = this.editor2.getSelectedText();
    //this.highlightDifferences(selection1, selection2);
  }

  private highlightDifferences(editor: ace.Ace.Editor, content: string, diffs: Diff.Change[], file: 'file1' | 'file2') {
    const annotations = [];
    let offset = 0;

    diffs.forEach(diff => {
      if (diff.added && file === 'file2' || diff.removed && file === 'file1') {
        const startIdx = content.indexOf(diff.value, offset);
        if (startIdx !== -1) {
          annotations.push({
            row: content.substr(0, startIdx).split('\n').length - 1,
            column: startIdx,
            text: 'Difference',
            type: 'error'
          });
          offset = startIdx + diff.value.length;
        }
      } else if (!diff.added && !diff.removed) {
        offset += diff.value.length;
      }
    });

    editor.session.setAnnotations(annotations);
  }


  highlightDifferencess(text1: string, text2: string): void {
    debugger;
    const diff = Diff.diffWords(text1, text2);
    this.applyDiffToEditor(this.editor1, text1, diff);
    this.applyDiffToEditor(this.editor2, text2, diff);
  }

  applyDiffToEditor(editor: ace.Ace.Editor, originalText: string, diff: Diff.Change[]): void {
    const session = editor.getSession();
  
    // Clear existing markers
    const markers = session.getMarkers(false);
    Object.keys(markers).forEach(markerId => session.removeMarker(parseInt(markerId, 10)));
  
    // Apply new markers for diffs
    diff.forEach(part => {
      const { added, removed, value } = part;
      if (added || removed) {
        const range = this.getRangeForDiff(originalText, value);
        if (range) {
          const markerClass = added ? 'ace_added' : 'ace_removed';
          session.addMarker(range, markerClass, 'text');
        }
      }
    });
  }
  
  

  getRangeForDiff(text: string, diffText: string): any {
    const Range = ace.require('ace/range').Range; // Access Range from ace
    const index = text.indexOf(diffText);
  
    if (index === -1) {
      return null; // Diff text not found
    }
  
    const linesBefore = text.substring(0, index).split('\n');
    const startRow = linesBefore.length - 1;
    const startCol = linesBefore.length > 0 && linesBefore[startRow]
      ? linesBefore[startRow].length
      : 0;
  
    const linesDiff = diffText.split('\n');
    const endRow = startRow + linesDiff.length - 1;
    const endCol = linesDiff.length > 0 && linesDiff[linesDiff.length - 1]
      ? linesDiff[linesDiff.length - 1].length
      : 0;
  
    console.log(`Range: Start [${startRow}, ${startCol}] End [${endRow}, ${endCol}]`);
  
    return new Range(startRow, startCol, endRow, endCol);
  }
  
  
  
  

  checkUnclosedTags() {
    debugger;
   // this.unclosedTags = this.findUnclosedTags(this.xmlInput);
  }

  findUnclosedTags(xmlContent: string): string[] {
    const tagStack: string[] = [];
    const errors: string[] = [];
    const tagRegex = /<\/?([a-zA-Z0-9]+)(\s[^>]*)?>/g;
    let match: RegExpExecArray | null;
  
    while ((match = tagRegex.exec(xmlContent)) !== null) {
      const tagName = match[1];
  
      if (!match[0].startsWith('</')) {
        // Opening tag: push to stack
        tagStack.push(tagName);
      } else {
        // Closing tag: check stack
        const lastTag = tagStack.pop();
        if (lastTag !== tagName) {
          errors.push(`Tag mismatch: expected </${lastTag}> but found </${tagName}>`);
        }
      }
    }
  
    // Remaining unclosed tags in the stack
    while (tagStack.length > 0) {
      const unclosedTag = tagStack.pop();
      errors.push(`Unclosed tag: <${unclosedTag}>`);
    }
  
    console.error('XML Parsing Error:', errors);

    return errors;
  }

  trackUnclosedTags(xml: string): string[] {
    const parser = new DOMParser();
    const xmlDoc = parser.parseFromString(xml, 'application/xml');
    const openTags: string[] = [];
    const unclosedTags: string[] = [];
  
    // Helper function to traverse and find open/close tags
    function traverseNode(node: Node) {
    //  debugger;
      if (node.nodeType === Node.ELEMENT_NODE) {
        openTags.push(node.nodeName); // Add the opening tag
        // Traverse child nodes recursively
        node.childNodes.forEach(traverseNode);
        
        // Check if the tag has a corresponding closing tag (self-closing tags are ignored)
        if (!node.hasChildNodes() && !node.nodeName.includes('/')) {
          unclosedTags.push(node.nodeName);
        }
        
        // Remove the tag from the openTags stack after processing its children
        openTags.pop();
      }
    }
  
    // Start traversing from the root element
    traverseNode(xmlDoc.documentElement);
  
    return unclosedTags;
  }
  
}
