import { Component, Input, ElementRef , AfterViewInit, ViewChild, HostListener   } from '@angular/core';
import { PatternResult, MatchedResult, RegexReplacement } from '../matched-result.model';
import * as ace from 'ace-builds';
const Range = ace.require('ace/range').Range;
import { BsModalService, BsModalRef } from 'ngx-bootstrap/modal';  // Import BsModalRef
import { ToastrService } from 'ngx-toastr';
import { RegexPatternService } from '../services/regexpattern.service';
import { SharedService } from '../shared.service';
import { MatchService } from '../match.service'
import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component';

import { XmlvalidationService } from '../services/xmlvalidation.service';

import * as vkbeautify from 'vkbeautify';
//import { DOMParser } from 'xmldom';
import * as xpath from 'xpath';
import { XMLValidator } from 'fast-xml-parser';

@Component({
  selector: 'app-preview-dialog',
  templateUrl: './preview-dialog.component.html',
  styleUrls: ['./preview-dialog.component.css']
})
export class PreviewDialogComponent implements AfterViewInit {
  @Input() previewResults: MatchedResult;
  @ViewChild('originalEditor',  { static: true }) originalEditorRef!: ElementRef;
  @ViewChild('previewEditor', { static: true  }) previewEditorRef!: ElementRef;

  private originalEditor:  any;
  private previewEditor: any; 

  xmlOriginalContent = '';
  xmlPreviewContent = '';
  linearizedXml: string = '';
  
  dataType: string = 'xml'; // Default to xml
  formattedOutput: string = '';
  errorDetails: string = ''; 
  result: any = null;
  
  private markers: number[] = [];
  isLoading: boolean = false; 
  loadingIndex: number | null = null;
  
  matchedResults: MatchedResult[] = []; // Store matched results
  regexReplacement: RegexReplacement;
  token = '';
  projectDefinitionId = '';
  filename = '';

  //evaluationResult: string[] = [];
  
  contextMenuVisible = false;
  contextMenuX = 0;
  contextMenuY = 0;
  showModal = false; // Flag to control modal visibility
  validationResult = '';
  filePath = ''; // Holds the file path for XSD or DTD
  validationType = ''; // 'XSD' or 'DTD'
  serverPath: string;

  xpathResults: { type: string; name: string; value: string }[] = [];
  
  constructor(public modalRef: BsModalRef, public secondModalRef: BsModalRef, private modalService: BsModalService, private matchService: MatchService, private regexPatternService: RegexPatternService, private sharedService: SharedService, private toastr: ToastrService, private xmlValidationService: XmlvalidationService) { }
  

  ngOnInit(): void {  
    this.matchService.matches$.subscribe(matches => {   
      this.matchedResults = matches;       
    });

    this.matchService.regexReplacement$.subscribe(regexReplacement => {   
      this.regexReplacement = regexReplacement;       
    });

    this.sharedService.token$.subscribe(token => {   
      this.token = token;       
    });

    this.sharedService.serverPath$.subscribe((path) => {
      this.serverPath = path;
    });  

    this.sharedService.projectdefinitionId$.subscribe((pdid) => {
      this.projectDefinitionId = pdid;
    });     

    this.sharedService.filename$.subscribe((filename) => {
      this.filename = filename;
    });  
  }  

  ngAfterViewInit(): void { 
    this.xmlOriginalContent = this.previewResults.content;
    this.xmlPreviewContent = this.previewResults.previewContent;
  
    this.initializeEditor(); 
  }  


  isContentEmpty(): boolean {
    return this.xmlPreviewContent ? this.xmlPreviewContent.trim() === '' : true;
  }

  

  initializeEditor(){
    // Initialize editors only after the view is initialized
    this.originalEditor = ace.edit(this.originalEditorRef.nativeElement);
    this.originalEditor.setTheme('ace/theme/monokai'); // Set theme
    this.originalEditor.session.setMode('ace/mode/xml');
    this.originalEditor.setValue(this.xmlOriginalContent, -1);   

    this.originalEditor.setOptions({
      showLineNumbers: true, // Disable line numbers
      showGutter: true,      // Disable the gutter
      highlightActiveLine: true, // Disable active line highlighting
      showPrintMargin:true
    });

   this.highlightMatch(this.previewResults.patternResults);
   
    this.previewEditor = ace.edit(this.previewEditorRef.nativeElement);
    this.previewEditor.setTheme('ace/theme/monokai'); // Set theme
    this.previewEditor.session.setMode('ace/mode/xml');
    this.previewEditor.setValue(this.xmlPreviewContent, -1);  

    this.previewEditor.setOptions({
      showLineNumbers: true, // Disable line numbers
      showGutter: false,      // Disable the gutter
      highlightActiveLine: false, // Disable active line highlighting
      showPrintMargin:false
    });
     
    // Call the functions to highlight matches and modified content
       
     this.highlightModifiedContent();  
    
  }
  
  
  
  highlightModifiedContent(): void {
    const originalLines = this.xmlOriginalContent.split('\n');
    const previewLines = this.xmlPreviewContent.split('\n');
  
    for (let i = 0; i < previewLines.length; i++) {
      if (originalLines[i] !== previewLines[i]) {
        const originalLine = originalLines[i] || ''; // Handle undefined for originalLines
        const previewLine = previewLines[i];
  
        // Find the start of the difference
        let startCol = 0;
        while (
          startCol < originalLine.length &&
          startCol < previewLine.length &&
          originalLine[startCol] === previewLine[startCol]
        ) {
          startCol++;
        }
  
        // Find the end of the difference
        let endColOriginal = originalLine.length - 1;
        let endColPreview = previewLine.length - 1;
        while (
          endColOriginal >= startCol &&
          endColPreview >= startCol &&
          originalLine[endColOriginal] === previewLine[endColPreview]
        ) {
          endColOriginal--;
          endColPreview--;
        }
  
        // Add a marker for the exact modified content
        const range = new Range(i, startCol, i, endColPreview + 1); // Use preview's end
        this.previewEditor.session.addMarker(
          range,
          'highlight',
          'text'
        );
      }
    }
  }
  

  highlightPattern(patternResults: PatternResult[]): void {
    if (!patternResults) return;
    this.highlightMatch(patternResults);
  }

  highlightMatch(patternResults: PatternResult[]): void {
    this.clearMarkers();

    patternResults.forEach((patternResult) => {
      patternResult.matches.forEach((match) => {
        const startIndex = this.originalEditor.getSession().doc.indexToPosition(match.index);
        const endIndex = this.originalEditor.getSession().doc.indexToPosition(match.index + match.content.length);
        const range = new Range(startIndex.row, startIndex.column, endIndex.row, endIndex.column);
        const markerId = this.originalEditor.getSession().addMarker(range, 'highlight', 'text');
        this.markers.push(markerId);
      });
    });
  }  

  clearMarkers(): void {
    this.markers.forEach(markerId => this.originalEditor.getSession().removeMarker(markerId));
    this.markers = [];
  }

  confirmChanges(): void { 
    this.applyChanges();
  }

  closeDialog(): void {
   this.modalRef.hide();
  }

  applyChanges() {     
    this.isLoading = true; 
    setTimeout(() => {      
      this.regexPatternService.applyChanges(this.previewResults, this.token).subscribe(
        (results) => {     
          this.isLoading = false; 
        
          const updatedData = {
            content: results.content,
            outputFilePath : results.outputFilePath,
            previewContent: results.previewContent,
            finalContent: results.finalContent,
            status:  results.status
          };

          const updatedMatchedResults = this.updateMatchedResult(this.matchedResults, results.fileName, updatedData);

          this.matchService.setMatches(updatedMatchedResults);  
          this.toastr.success('Changes applied successfully.');
          console.log('Changes applied successfully.');
        },
        (error) => {       
          //console.error('Error fetching data:', error);
          this.toastr.error('Error occurred while applying changes in file.');
          this.isLoading = false; // Stop loader on error
        }
      );     
    }, 1000);   
  }

  replaceAll() {
    this.isLoading = true; 
    setTimeout(() => {      
      this.regexPatternService.replaceAll(this.regexReplacement.directoryPath,this.regexReplacement.searchTerm,
        this.regexReplacement.replacementText,
        this.regexReplacement.fileType,
        this.regexReplacement.caseInsensitive,
        this.regexReplacement.singleLine,
        this.regexReplacement.multiLine,
        this.regexReplacement.ignoreWhitespace,
        this.regexReplacement.explicitCapture,
        this.token).subscribe(
        (results) => {
          this.matchService.setMatches(results);  
          this.isLoading = false; 
          this.toastr.success('Changes applied successfully to all files.');
          console.log('Changes applied successfully to all files.');
        },
        (error) => {       
          //console.error('Error fetching data:', error);
          this.toastr.error('Error occurred while applying changes in file.');
          this.isLoading = false; // Stop loader on error
        }
      );     
   }, 1000);   
  }

  previewChanges(index: number) {      
    this.originalEditor.setValue("", -1);   
    this.previewEditor.setValue("", -1);   

    setTimeout(() => {  
    this.previewResults = this.matchedResults[index];      
    this.xmlOriginalContent = this.previewResults.content;
    this.xmlPreviewContent = this.previewResults.previewContent;

    this.initializeEditor();   
    },1000);      
   
  }

  updateMatchedResult(
    matchedResults: MatchedResult[],
    fileName: string,
    updateData: Partial<MatchedResult>
  ): MatchedResult[] {
    return matchedResults.map(result =>
      result.fileName === fileName
        ? { ...result, ...updateData } // Merge old and new data for the matching fileName
        : result // Return unchanged if fileName does not match
    );
  }

  openModal() {
    const initialState = {
      message: 'Do you want to replace the file?'
    };

    this.secondModalRef = this.modalService.show(ConfirmDialogComponent, { initialState });

    this.secondModalRef.content.onAccept = () => {
      this.replaceFile();
      this.secondModalRef.hide();   
    };
    this.secondModalRef.content.onReject = () => {   
      this.deleteFile();
      this.secondModalRef.hide(); 
    };
  }

  replaceFile() {   
      this.regexPatternService.accept(this.matchedResults, this.token).subscribe(
        (results) => {         
          this.matchService.setMatches(results);  
          this.toastr.success('Files are replaced successfully.');
          console.log('Files are replaced successfully.');
        },
        (error) => {                 
          this.toastr.error('Error occurred while replacing file in the input file path.');         
        }
      );           
  }

  deleteFile() {   
      this.regexPatternService.reject(this.matchedResults, this.token).subscribe(
        (results) => {           
          this.matchService.setMatches(results);  
          this.toastr.success('Files are deleted successfully from OUT path.');
          console.log('Files are deleted successfully from OUT path.');
        },
        (error) => {                 
          this.toastr.error('Error occurred while deleting from OUT path.');          
        }
      );  
  }

 

  linearizeXml(): void {  
    if (!this.xmlOriginalContent) {
     // this.linearizedXml = 'Please provide XML input.';
      return;
    }

    try {
      // Remove line breaks, tabs, and unnecessary whitespace between tags
      this.linearizedXml = this.xmlOriginalContent
        .replace(/>\s+</g, '><') // Remove spaces between tags
        .replace(/\s+/g, ' ')   // Replace multiple spaces with a single space
        .trim();
      console.log('Linearized XML:', this.linearizedXml);
    //  this.prettyXmlOutput = this.linearizedXml;
      this.xmlOriginalContent = this.linearizedXml;
      this.originalEditor.setValue(this.xmlOriginalContent, -1);   
    } catch (error) {
      this.linearizedXml = 'Error: Invalid XML input.';
    }
  }

  prettyPrint(): void {   
    try {
      if (this.dataType === 'json') {
        // Format JSON
        const parsedJson = JSON.parse(this.xmlOriginalContent);
        this.formattedOutput = JSON.stringify(parsedJson, null, 2);
      } else if (this.dataType === 'xml') {
        // Format XML using vkbeautify
        this.formattedOutput = vkbeautify.xml(this.xmlOriginalContent);
      } else if (this.dataType === 'html') {
        // Format HTML using vkbeautify
        this.formattedOutput = vkbeautify.xml(this.xmlOriginalContent); // Same as XML formatting
      }
      this.xmlOriginalContent   =this.formattedOutput;
      this.originalEditor.setValue(this.xmlOriginalContent, -1);   
      console.log('Pritty Print:', this.formattedOutput);
    } catch (error) {
      this.formattedOutput = 'Error: Invalid input data.';
    }
  }
 
 
  validateXML() {
    const value = this.originalEditor.getValue();  
    // Check if XML is well-formed
    const isValid = XMLValidator.validate(value);
    if (isValid === true) {
      console.log('XML is well-formed');
    } else {
      this.errorDetails = isValid.err.msg;
      console.error('XML Parsing Error:', isValid.err);
    }
  } 
  
  

  evaluateXPath(): void {
    const xpathExpr = prompt('Enter XPath expression:');
    const content = this.originalEditor ? this.originalEditor.getValue() : '';
  
    try {
      const doc = new DOMParser().parseFromString(content, 'application/xml');
      
      // Use the xpath library to evaluate the expression
      const nodes = xpath.select(xpathExpr!, doc);
  
      // Ensure nodes is always treated as an array
      const nodeArray = Array.isArray(nodes) ? nodes : [nodes];
  
      // Prepare the output in the format you expect
      const output: { type: string; name: string; value: string }[] = [];
  
      nodeArray.forEach((node: any) => {
        let type = 'element';
        let name = node.nodeName;
        let value = node.textContent || '';
  
        if (node.nodeType === 2) { // ATTRIBUTE_NODE in xmldom
          type = 'attribute';
          value = node.value;
        }
  
        output.push({ type, name, value });
      });
  
      this.xpathResults = output; // Store results for UI display
    } catch (err) {
      console.error('Error evaluating XPath:', err);
      alert('Error evaluating XPath: ' + err);
    }
  }  

  convertToText() {
    const xmlContent = this.originalEditor.getValue();
    try {
     // Remove XML tags and extra spaces/newlines
    const textContent = xmlContent
    .replace(/<[^>]+>/g, '')  // Remove XML tags   
    .replace(/^\s+/gm, '')
    .trim();                  // Remove leading and trailing spaces
      this.originalEditor.setValue(textContent);
    } catch (e) {
      alert('Error converting XML to Text.');
    }
  }

  toggleComment() {
    const selectedText = this.originalEditor.getSelectedText();
    if (selectedText.startsWith("<!--") && selectedText.endsWith("-->")) {
      this.originalEditor.session.replace(this.originalEditor.getSelectionRange(), selectedText.replace(/<!--|-->/g, ''));
    } else {
      const commentText = `<!--${selectedText}-->`;
      this.originalEditor.session.replace(this.originalEditor.getSelectionRange(), commentText);
    }
  }

  onRightClick(event: MouseEvent): void {
    event.preventDefault(); // Prevent default right-click menu
    this.contextMenuVisible = true;
    this.contextMenuX = event.clientX;
    this.contextMenuY = event.clientY;
  }

  @HostListener('document:click', ['$event'])
  onDocumentClick(event: MouseEvent): void {
    this.contextMenuVisible = false; // Hide context menu on outside click
  }

  performAction(action: string): void {
    this.contextMenuVisible = false;

    switch (action) {
      case 'prettyPrint':
        this.prettyPrint();
        break;
      case 'linearizeXml':
        this.linearizeXml();
        break;
      case 'validateXml':
        this.validateXML();
        break;
      case 'evaluateXPath':
        this.evaluateXPath();
        break;   
      case 'convertToText':
        this.convertToText();
        break;  
      case 'toggleComment':
        this.toggleComment();
        break; 
      default:
        console.warn('Unknown action:', action);
    }
  }

  // Open modal for validation
  openValidationModal(type: 'xsd' | 'dtd' | 'xslt') {
    this.validationType = type;
    this.filePath = ''; // Reset file path
    this.validationResult = ''; // Reset validation result
    this.showModal = true; // Show modal
    this.hideContextMenu(); // Hide context menu

     this.xmlValidationService.DocumentPath(this.validationType, this.projectDefinitionId).subscribe({     
      next: (path) => (this.filePath = path),
      error: (err) => console.error('Error fetching file path:', err),
    });

  }

  // Validate XML against XSD or DTD
  validate() { 
    if (!this.filePath) {
      this.validationResult = 'File path is required.';
      return;
    }
    
    if (this.validationType === 'xslt') {
      const validateObservable =  this.xmlValidationService.Transform(this.originalEditor.getValue(), this.filePath, this.serverPath, this.filename)      

      validateObservable.subscribe({            
          next: (response) => {
            this.validationResult =  response.messages.join('\n');
            this.previewEditor = ace.edit(this.previewEditorRef.nativeElement);        
            this.previewEditor.setValue(response.fileContent, -1);
          },
          error: (err) => {
            console.error('Error:', err);
            this.validationResult  = err.error; // Display the error message from the server
          },
        });     
    }
    else
    {
      const validateObservable =
        this.validationType === 'xsd'
        ? this.xmlValidationService.ValidateXsd(this.originalEditor.getValue(), this.filePath)       
        : this.xmlValidationService.ValidateDtd(this.originalEditor.getValue(), this.filePath);

      validateObservable.subscribe(
        (results: string[]) => {  
          this.validationResult = results.join('\n'); // Combine array items into a single string with line breaks
        },
        (error) => {   
          this.validationResult = (error && error.error && error.error.error) || "An unexpected error occurred.";
          console.error(this.validationResult);
        }
      );
    }
      
  }

  // Close the modal
  closeModal() {
    this.showModal = false;
  }

  // Hide context menu
  hideContextMenu() {
    this.contextMenuVisible = false;
  }   

}
