/**
 * Copyright Aquera Inc 2023
 *
 * This source code is licensed under the BSD-3-Clause license found in the
 * LICENSE file in the root directory of this source tree.
 */

import { html, TemplateResult } from 'lit';
import { customElement, query, state, property } from 'lit/decorators.js';
import { styles } from './nile-content-editor.css';
import { CSSResultGroup } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import NileElement from '../internal/nile-element';
import { watch } from '../internal/watch';
import { KeyCode } from '../internal/enum';
import { live } from 'lit/directives/live.js';

/**
 * Nile icon component.
 *
 * @tag nile-attribute-expression
 *
 */
@customElement('nile-content-editor')
export class NileContentEditor extends NileElement {
  /**
 * @summary Allows you to handle both input and dropdown selection

 * @dependency nile-option
 
 * @event nile-change - Emitted when the control's value changes.  
 */

  @property() value: string = '';

  @property() options: Array<any>;

  @property() filteredOptions: Array<any>;

  @property() showLabel: boolean = true;

  @property() labelText = '';

  @property() type = 'text|text-area';

  @property() required: boolean = true;

  @query('.content-editable-input') contentEditor: HTMLInputElement;
  @query('.nile-options-container') autoOptions: HTMLInputElement;

  @state() openDropdown = false;

  tagIdentifier = '$';

  filteredValue = '';

  @property({ attribute: 'help-text' }) helpText = '';

  @property({ attribute: 'readonly' }) readonly = false;

  @property({ attribute: 'error-message' }) errorMessage = '';

  @property({ attribute: 'error' }) error = false;
  @property({ attribute: 'noborder' }) noborder = false;
  @property({ type: Boolean }) updateValue: any = false;

  @state() initialValue = '';

  static styles: CSSResultGroup = styles;

  connectedCallback() {
    this.handleOutsideClick = this.handleOutsideClick.bind(this);
    this.handleClipboardEvent = this.handleClipboardEvent.bind(this);
    super.connectedCallback();
    this.emit('nile-init');
    this.addOpenListeners();
    this.setInitialValues();
  }

  addOpenListeners() {
    window.addEventListener('click', this.handleOutsideClick);
    this.addEventListener('cut', this.handleClipboardEvent);
    this.addEventListener('copy', this.handleClipboardEvent);
    this.addEventListener('paste', this.handleClipboardEvent);
  }

  removeOpenListeners() {
    this.removeEventListener('click', this.handleOutsideClick);
    this.removeEventListener('cut', this.handleClipboardEvent);
    this.removeEventListener('copy', this.handleClipboardEvent);
    this.removeEventListener('paste', this.handleClipboardEvent);
  }

  handleOutsideClick(event: any) {
    if (event && event.target && !this.contains(event.target)) {
      this.toggleDropdown(false);
    }
  }

  async handleClipboardEvent(event: Event) {
    const selectedText = window?.getSelection()?.toString();
    switch (event.type) {
      case 'cut':
      case 'copy':
        if (!!selectedText) await navigator.clipboard.writeText(selectedText);
        document.execCommand(event.type);
        break;
      case 'paste':
        event.preventDefault();
        let pastedText = '';
        pastedText = await navigator.clipboard.readText();
        pastedText = pastedText
          .replace(/<(|\/)(html|body|meta|span)[^>]*?>/gi, '')
          .trim();
        document.execCommand('insertText', false, pastedText);
        break;
      default:
        break;
    }
    setTimeout(() => {
      this.emitInputChange();
    });
  }

  toggleDropdown(value: boolean) {
    this.openDropdown = value;
  }

  setInitialValues() {
    this.updateComplete.then(res => {
      if (res)
        this.contentEditor.innerHTML = this.generateHTMLTagsWithValues(
          this.value
        );
    });
  }

  @watch(['type', 'value'], { waitUntilFirstUpdate: true })
  handleTypeChange() {
    if (this.updateValue) {
      this.contentEditor.innerHTML = this.generateHTMLTagsWithValues(
        this.value
      );
    }
  }

  disconnectedCallback(): void {
    super.disconnectedCallback();
    this.removeOpenListeners();
    this.emit('nile-destroy');
  }

  private handlekeyDown(event: any) {
    if (event.code === KeyCode.ENTER && this.type === 'text') {
      event.preventDefault();
      return;
    }
    if (
      [KeyCode.CUT, KeyCode.COPY, KeyCode.PASTE].includes(event.code) &&
      (event.ctrlKey || event.metaKey)
    ) {
      return;
    }

    if (event.code === KeyCode.ESCAPE) {
      event.preventDefault();
      this.toggleDropdown(false);
      return;
    }
    setTimeout(() => {
      const value = this.contentEditor.innerText;
      this.emitInputChange();
      if (value.includes(this.tagIdentifier)) {
        this.insertNodes(this.contentEditor, this.contentEditor.childNodes);
        this.filterOptions();
        this.toggleDropdown(true);
      } else {
        this.toggleDropdown(false);
      }
    }, 10);
  }

  filterOptions() {
    this.filteredOptions = this.options;
    this.filteredValue = this.filteredValue.trim();
    if (!!this.options.length && !!this.filteredValue) {
      this.filteredOptions = this.options.filter(item =>
        item.includes(this.filteredValue)
      );
    } else {
      this.filteredOptions = this.options;
    }
  }

  generateValuesFromHTMLTags(string: any): string {
    let errorTag =
      /<span class="chips chip-error" contenteditable="false">(.*?)<\/span>/g;
    this.error = !!string.match(errorTag);
    var pattern =
      /<span class="chips(?: chip-error)?" contenteditable="false">(.*?)<\/span>/g;
    let result = string.replace(pattern, (match: any, option: any) => {
      return '{{' + option + '}}';
    });
    return result;
  }

  generateHTMLTagsWithValues(string: any): string {
    var pattern = /{{(.*?)}}/g;
    var result = string.replace(pattern, (match: any, option: any) => {
      return this.options.includes(option)
        ? '<span class="chips" contenteditable="false">' + option + '</span>'
        : this.setError(option);
    });
    return result;
  }

  setError(option: any) {
    this.error = true;
    return (
      '<span class="chips chip-error" contenteditable="false">' +
      option +
      '</span>'
    );
  }

  emitInputChange(): void {
    if (this.contentEditor) {
      let fieldValue = this.contentEditor.innerHTML;
      fieldValue = fieldValue.replace(/&nbsp;/g, ' ');
      fieldValue = fieldValue.replace('<br>', '');
      this.emit('nile-change', {
        value: this.generateValuesFromHTMLTags(fieldValue),
      });
    }
  }

  replaceText = '';

  insertNodes(
    parentNode: Node,
    childNodes: NodeList,
    autoOptionsTag?: HTMLElement
  ) {
    [...childNodes].forEach((node: Node, index: number) => {
      if (node.hasChildNodes()) {
        if (autoOptionsTag) {
          this.insertNodes(node, node.childNodes, autoOptionsTag);
        } else {
          this.insertNodes(node, node.childNodes);
        }
      } else {
        if (node.nodeValue?.includes(this.tagIdentifier)) {
          if (autoOptionsTag) {
            this.insertAutoOptionsTag(node, autoOptionsTag);
          }

          this.setFilterValue(node.nodeValue);

          return;
        }
      }
    });
  }

  insertAutoOptionsTag(node: any, autoOptionsTag: any) {
    const selection = window.getSelection();
    const range = document.createRange();
    const curssorNodeindex = node.nodeValue.indexOf(this.tagIdentifier);
    range.setStart(node, curssorNodeindex);
    range.insertNode(autoOptionsTag);
    range.setStartAfter(autoOptionsTag);
    range.collapse(true);
    selection?.removeAllRanges();
    selection?.addRange(range);

    if (autoOptionsTag.nextSibling?.nodeValue) {
      autoOptionsTag.nextSibling.nodeValue =
        autoOptionsTag.nextSibling?.nodeValue?.replace(
          this.tagIdentifier + this.filteredValue,
          ''
        );
    }
    this.contentEditor.focus();
  }

  setFilterValue(value: any) {
    //replace Text - check if text exists after tagidentifier , if exists take account of that too
    if (!this.openDropdown) {
      this.replaceText = value.split(this.tagIdentifier).slice(1).join();
    }
    if (!!value && this.openDropdown) {
      this.filteredValue = value
        ?.split(this.tagIdentifier)
        .slice(1)
        .join()
        .replace(this.replaceText, '');
    } else {
      this.filteredValue = '';
    }
  }

  handleOptions(option: any): void {
    this.toggleDropdown(false);
    let autoOptionsTag = document.createElement('span');
    autoOptionsTag.setAttribute('class', 'chips');
    autoOptionsTag.setAttribute('contentEditable', 'false');
    autoOptionsTag.innerText = option;
    this.insertNodes(
      this.contentEditor,
      this.contentEditor.childNodes,
      autoOptionsTag
    );
    this.resetOptions();
    this.emitInputChange();
  }

  resetOptions() {
    this.filteredOptions = this.options;
    this.filteredValue = '';
  }

  public renderAutoOptions(): TemplateResult {
    return html`<div
      class="${classMap({
        'nile-options-container': true,
        'dropdown-position': this.type === 'text-area',
      })}"
    >
      ${this.filteredOptions &&
      this.filteredOptions.map((option: any) => {
        return html` <nile-option
          .value="${option}"
          @click="${(event: Event) => {
            this.handleOptions(option);
          }}"
          >${option}
        </nile-option>`;
      })}
    </div>`;
  }

  public render(): TemplateResult {
    const hasHelpText = this.helpText ? true : false;
    const hasError = !!this.error;
    const hasErrorMessage = !!this.errorMessage;
    const readonly = !!this.readonly;
    const noborder = !!this.noborder;
    const type = this.type;
    return html`
      <nile-popup
        .active="${live(this.openDropdown)}"
        sync="width"
        strategy="fixed"
        placement="bottom"
        class=${classMap({
          dropdown: true,
          'dropdown--open': true,
          popup: true,
        })}
      >
        <div class="content-editable-wrapper" slot="anchor">
          ${this.showLabel && this.labelText
            ? html`<label class="ods-label">${this.labelText} </label> ${this
                  .required
                  ? html`<span class="asterik">*</span>`
                  : ''}`
            : ''}
          <div
            contenteditable="${!readonly}"
            spellcheck="false"
            class=${classMap({
              'content-editable-input': true,
              error: hasError || hasErrorMessage,
              'read-only': readonly,
              'no-border': noborder,
              'text-area': type === 'text-area' ? true : false,
            })}
            @keydown=${this.handlekeyDown}
          ></div>
          ${hasHelpText
            ? html`
                <nile-form-help-text>${this.helpText}</nile-form-help-text>
              `
            : ``}
          ${hasErrorMessage
            ? html`
                <nile-form-error-message
                  >${this.errorMessage}</nile-form-error-message
                >
              `
            : ``}
        </div>
        ${this.filteredOptions && !!this.filteredOptions.length
          ? this.renderAutoOptions()
          : null}
      </nile-popup>
    `;
  }
}

export default NileContentEditor;

declare global {
  interface HTMLElementTagNameMap {
    'nile-content-editor': NileContentEditor;
  }
}
