import {
  Component,
  OnInit,
  OnDestroy,
  inject,
  TemplateRef,
  ViewChild,
  ViewContainerRef,
  Renderer2,
  ElementRef,
} from '@angular/core';
import { Editor } from 'ngx-editor';
import { DateAdapter } from '@angular/material/core';
import { JournalEntry } from 'src/interfaces/interfaces';
import localeDe from '@angular/common/locales/de';
import { registerLocaleData } from '@angular/common';
import { DataService } from 'src/services/data/data.service';
import { TooltipService } from 'src/services/tooltip/tooltip.service';
import { HighlightComponent } from 'src/app/shared-components/highlight/highlight.component';

@Component({
  selector: 'app-journal-notes',
  templateUrl: './journal-notes.component.html',
  styleUrl: './journal-notes.component.scss',
})
export class JournalNotesComponent implements OnInit, OnDestroy {
  /** Reference to the tooltip */
  @ViewChild('tooltip', { read: TemplateRef }) tooltip!: TemplateRef<any>;

  /** Reference to the creation anchor */
  @ViewChild('creationAnchor', { read: ViewContainerRef })
  creationAnchor!: ViewContainerRef;

  editor: Editor = new Editor();
  toolbar: any = [
    // default value
    ['bold', 'italic'],
    ['bullet_list'],
    [{ heading: ['h3', 'h4', 'h5', 'h6'] }],
  ];

  public entries: JournalEntry[] = [];
  public currentEntry: JournalEntry = {
    title: '',
    content: '',
    created: new Date(),
  };

  public isInEditMode = false;
  public currentEntryIndex: number = 0;
  private backupIndex: number = -1;
  public isNewEntry = false;

  public tooltipText: string = '';

  public npcDescriptions: any = {};

  tooltipifiedEntry: JournalEntry = {
    title: 'Title',
    content: 'Content',
    created: new Date(),
  };

  private _adapter: DateAdapter<any> = inject(DateAdapter);
  private dataService: DataService = inject(DataService);
  private tooltipService: TooltipService = inject(TooltipService);
  private renderer: Renderer2 = inject(Renderer2);
  private el: ElementRef = inject(ElementRef);

  ngOnInit(): void {
    registerLocaleData(localeDe);
    this._adapter.setLocale('de');
    this.entries = this.dataService.notesData;

    // if the list is empty, set the currentEntryIndex to -1 to hide the entry-container
    if (this.entries.length === 0) {
      this.currentEntryIndex = -1;
    } else {
      this.currentEntry = this.entries[0];
      this.tooltipifiedEntry = JSON.parse(JSON.stringify(this.currentEntry));
    }
    this.tooltipify();
  }

  // ACTIONS FROM THE TEMPLATE

  /**
   * Sets the currentEntry variable when being clicked on in the entries list on the left.
   * It also modifies the content of the entry by highlighting names and adding tooltips.
   * Is only called when the entry is not in edit mode or a different entry is selected.
   * @param index The index of the selected entry.
   */
  public selectEntry(index: number): void {
    if (this.isInEditMode || index !== this.currentEntryIndex) {
      this.currentEntryIndex = index;
      this.currentEntry = this.getEntryAt(index);
      this.isNewEntry = false;
      this.isInEditMode = false;
      this.tooltipify();
    }
  }

  /**
   * Adds an new empty entry, switches to edit mode and removes the highlighting of the selected entry.
   */
  public addEntry(): void {
    this.currentEntry = {
      title: '',
      content: '',
      created: new Date(),
    };
    this.isNewEntry = true;
    this.isInEditMode = true;
    this.backupIndex = this.currentEntryIndex;
    // Hightlight no entry because the placeholder is shown as active
    this.currentEntryIndex = -1;
  }

  /**
   * Switches to edit mode.
   */
  public editEntry(): void {
    this.isInEditMode = true;
  }

  /**
   * If the current entry is a new entry, it will be prepended to the entries list.
   * Else it is saved at the current index.
   * The content is the tooltipified and saved in the database.
   */
  public saveEntry(): void {
    if (this.isNewEntry) {
      // Prepend the new JournalEntry
      this.entries.unshift(this.currentEntry);
      this.isNewEntry = false;
      this.currentEntryIndex = 0;
    }
    this.isInEditMode = false;
    this.entries[this.currentEntryIndex] = this.currentEntry;
    this.tooltipify();
    this.uploadNotes();
  }

  /**
   * Discards the current entry and resets the currentEntry to the last saved version.
   * If the entry was a new entry, the currentEntryIndex is reset to the last selected entry.
   * The content is also tooltipified again.
   */
  public discardEntry(): void {
    if (this.isNewEntry) {
      this.currentEntryIndex = this.backupIndex;
      this.isNewEntry = false;
    }
    if (this.entries.length > 0) {
      // Reset the currentEntry to the last saved version
      this.currentEntry = this.getEntryAt(this.currentEntryIndex);
    }
    this.isInEditMode = false;
    this.tooltipify();
  }

  /**
   * Deletes the current entry and removes it from the entries list.
   * If the last entry was deleted, the currentEntryIndex is reset to -1.
   * The content is saved in the database.
   */
  public deleteEntry(): void {
    this.entries.splice(this.currentEntryIndex, 1);
    if (this.entries.length === 0) {
      this.currentEntryIndex = -1;
    } else {
      this.currentEntryIndex = Math.max(this.currentEntryIndex - 1, 0);
      this.currentEntry = this.getEntryAt(this.currentEntryIndex);
      // Update the tooltipified entry
      this.tooltipify();
    }
    this.uploadNotes();
  }

  /**
   * Returns a deep copy of the entry at the given index.
   * @param index Defines the index of the entry that should be returned.
   * @returns A deep copy of the entry at the given index.
   */
  private getEntryAt(index: number): JournalEntry {
    return JSON.parse(JSON.stringify(this.entries[index]));
  }

  /**
   * Saves the current entries in the data service.
   */
  private uploadNotes(): void {
    this.dataService.notesData = this.entries;
  }

  /**
   * Converts the content of the current entry to a tooltipified version.
   * It then adds the highlights to the names and sets the tooltip text.
   * Is called on every refresh of an entry (selecting, saving, discarding, deleting, adding, etc.)
   */
  public tooltipify(): void {
    let result: any = this.tooltipService.tooltipifyEntry(
      this.getEntryAt(this.currentEntryIndex).content,
    );

    this.tooltipifiedEntry.content = result.content;
    this.npcDescriptions = result.npcDescriptions;
    setTimeout(() => {
      result.npcs.forEach((name: string) => {
        this.addHighlightsToText(name);
      });
    });
  }

  /**
   * Adds a highlight component to the names that are mentioned in the current entry.
   * Highlights and adds tooltips to the names that are mentioned in the current entry.
   * @param name The name of the person which is currently highlighted.
   */
  private addHighlightsToText(name: string) {
    // get all elements where a highlight component should be added to
    const parent = this.el.nativeElement.querySelectorAll('.' + name);
    parent.forEach((element: any) => {
      const componentRef =
        this.creationAnchor.createComponent(HighlightComponent);
      componentRef.instance.text = name;
      componentRef.instance.tooltip = this.tooltip;
      this.renderer.appendChild(element, componentRef.location.nativeElement);
      // add a mouseover event listener to the highlight component, when it is hovered over, the tooltip-text is set
      this.renderer.listen(
        componentRef.location.nativeElement,
        'mouseover',
        () => {
          this.tooltipText = this.npcDescriptions[name];
        },
      );
    });
  }

  ngOnDestroy(): void {
    this.editor.destroy();
  }
}
