import { Component, ElementRef, EventEmitter, Inject, Input, NgZone, Output, PLATFORM_ID, Renderer2, ViewChild } from '@angular/core';
import { ControlValueAccessor, Validator, AbstractControl, ValidationErrors } from '@angular/forms';
import { EditorComponent } from '@progress/kendo-angular-editor';

import { MsgBoxService } from '../../../services/msg-box.service';
import { DropDownOption } from '../../../entity/dropDownOption';
import { MD2Icon } from '../massive-darkness2.model';
import { first } from 'rxjs/operators';
import { MD2StateService } from '../../../services/MD2/md2-state.service';
import { MD2IconPickerDlgComponent } from './md2-icon-picker-dlg.component';
import { NbDialogService } from '@nebular/theme';

@Component({
  selector: 'md2-html-editor',
  templateUrl: './md2-html-editor.component.html',
  styleUrl: './md2-html-editor.component.scss'
})
export class MD2HtmlEditorComponent implements ControlValueAccessor {
  @ViewChild(EditorComponent) editor: EditorComponent;

  value: string = '';
  disabled: boolean = false;

    messageTemplateSchema = this.createCustomSchema();
  customCssClass = '.MD2Icon{font-family: "Massive Darkness 2", sans-serif !important; font-size: 40px; margin-left:5px}  body{font-size: 30px;  }';

  // ControlValueAccessor interface
  private onChangeFn = (value: string) => { };
  private onTouchedFn = () => { };

  constructor(
    private msgBoxService: MsgBoxService,
    private md2StateService: MD2StateService,
    private dialogService: NbDialogService,
    elementRef: ElementRef, ngZone: NgZone, @Inject(PLATFORM_ID) platformId: Object) {

  }

  // ControlValueAccessor implementation
  writeValue(value: string): void {
    this.value = value || '';
  }

  registerOnChange(fn: (value: string) => void): void {
    this.onChangeFn = fn;
  }

  registerOnTouched(fn: () => void): void {
    this.onTouchedFn = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  onChange(value: string): void {
    this.value = value;
    this.onChangeFn(value);
  }

  onTouched(): void {
    this.onTouchedFn();
  }

  showInsertMD2Icon() {
    this.dialogService.open(MD2IconPickerDlgComponent, {
      closeOnBackdropClick: true,
      closeOnEsc: true
    }).onClose.pipe(first()).subscribe((html: string) => {
      if (html && this.editor) {
        // Insert the HTML content at the current cursor position
        // Use ProseMirror's dispatch method to insert content at cursor
        const view = this.editor.view;
        if (view && view.state) {
          try {
            // Parse the HTML content
            const dom = document.createElement('div');
            dom.innerHTML = html;

            // Use ProseMirror's DOMParser to parse HTML
            // Access it from the view's state
            const pmState = view.state;

            // Use the editor's exec method or manual dispatch
            if ((this.editor as any).exec) {
              // Try using exec with 'insertHTML' command if available
              try {
                (this.editor as any).exec('insertHTML', html);
              } catch (e) {
                throw new Error('insertHTML not supported');
              }
            } else {
              throw new Error('exec method not available');
            }
          } catch (e) {
            console.error('Error inserting HTML:', e);
            // Fallback: append to the end
            const currentValue = this.editor.value || '';
            const newValue = currentValue + ' ' + html;
            this.value = newValue;
            this.onChange(newValue);
          }
        } else {
          // Fallback: append to the end
          const currentValue = this.editor.value || '';
          const newValue = currentValue + ' ' + html;
          this.value = newValue;
          this.onChange(newValue);
        }
      }
    });
  }
  
    // Text manipulation methods for Kendo Editor
    /**
     * Parses HTML string to ProseMirror nodes
     * @param htmlContent - HTML string to parse
     * @returns ProseMirror Fragment
     */
    private parseHtmlContent(htmlContent: string): any {
        if (!this.editor || !this.editor.view) return null;

        const view = this.editor.view;
        const element = document.createElement('div');
        element.innerHTML = htmlContent;

        // Use ProseMirror's DOMParser to parse HTML into nodes
        const parser = ProseMirrorDOMParser.fromSchema(view.state.schema);
        const slice = parser.parseSlice(element);
        return slice.content;
    }

    /**
     * Inserts HTML content at the beginning of the editor
     * @param content - HTML string to insert
     * @param asHtml - If true, parses as HTML; if false, inserts as plain text
     */
    public insertAtBeginning(content: string, asHtml: boolean = false): void {
        if (!this.editor || !this.editor.view) return;

        const view = this.editor.view;
        const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content);

        if (contentNode) {
            const transaction = view.state.tr.insert(0, contentNode);
            view.dispatch(transaction);
            this.onValueChanged(this.editor.value);
        }
    }

    /**
     * Inserts HTML content at the end of the editor
     * @param content - HTML string to insert
     * @param asHtml - If true, parses as HTML; if false, inserts as plain text
     */
    public insertAtEnd(content: string, asHtml: boolean = false): void {
        if (!this.editor || !this.editor.view) return;

        const view = this.editor.view;
        const endPos = view.state.doc.content.size;
        const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content);

        if (contentNode) {
            const transaction = view.state.tr.insert(endPos, contentNode);
            view.dispatch(transaction);
            this.onValueChanged(this.editor.value);
        }
    }

    /**
     * Inserts HTML content before the current selection
     * @param content - HTML string to insert
     * @param asHtml - If true, parses as HTML; if false, inserts as plain text
     */
    public insertBeforeSelection(content: string, asHtml: boolean = false): void {
        if (!this.editor || !this.editor.view) return;

        const view = this.editor.view;
        const { from } = view.state.selection;
        const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content);

        if (contentNode) {
            const transaction = view.state.tr.insert(from, contentNode);
            view.dispatch(transaction);
            this.onValueChanged(this.editor.value);
        }
    }

    /**
     * Inserts HTML content after the current selection
     * @param content - HTML string to insert
     * @param asHtml - If true, parses as HTML; if false, inserts as plain text
     */
    public insertAfterSelection(content: string, asHtml: boolean = false): void {
        if (!this.editor || !this.editor.view) return;

        const view = this.editor.view;
        const { to } = view.state.selection;
        const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content);

        if (contentNode) {
            const transaction = view.state.tr.insert(to, contentNode);
            view.dispatch(transaction);
            this.onValueChanged(this.editor.value);
        }
    }

    /**
     * Replaces the currently selected text with new content
     * @param content - HTML string to replace selection with
     * @param asHtml - If true, parses as HTML; if false, inserts as plain text
     */
    public replaceSelectedText(content: string, asHtml: boolean = false): void {
        if (!this.editor || !this.editor.view) return;

        const view = this.editor.view;
        const { from, to } = view.state.selection;
        const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content);

        if (!contentNode) return;

        // If there's no selection, just insert at cursor position
        if (from === to) {
            const transaction = view.state.tr.insert(from, contentNode);
            view.dispatch(transaction);
        } else {
            // Replace the selected content
            const transaction = view.state.tr.replaceWith(from, to, contentNode);
            view.dispatch(transaction);
        }
        this.onValueChanged(this.editor.value);
    }

    /**
     * Gets the currently selected text in the editor
     * @returns The selected text as a string
     */
    public getSelectedText(): string {
        if (!this.editor) return '';
        return this.editor.selectionText || '';
    }
    public getSelectionTextOrWholeText(): string {
        if (!this.editor) return '';

        // If there's selected text, return it
        if (this.editor.selectionText && this.editor.selectionText.trim().length > 0) {
            return this.editor.selectionText;
        }

        // Otherwise, get the whole text content from the editor's document
        if (this.editor.view && this.editor.view.state) {
            return this.editor.view.state.doc.textContent;
        }

        // Fallback: strip HTML tags and return plain text
        const div = document.createElement('div');
        div.innerHTML = this.editor.value;
        return div.textContent || div.innerText || '';
    }

    /**
     * Gets the current cursor position or selection range
     * @returns Object with 'from' and 'to' positions
     */
    public getSelectionRange(): { from: number; to: number } | null {
        if (!this.editor || !this.editor.view) return null;

        const { from, to } = this.editor.view.state.selection;
        return { from, to };
    }

    /**
     * Inserts HTML content at a specific position
     * @param content - HTML string to insert
     * @param position - Position to insert at (0 = beginning)
     * @param asHtml - If true, parses as HTML; if false, inserts as plain text
     */
    public insertAtPosition(content: string, position: number, asHtml: boolean = false): void {
        if (!this.editor || !this.editor.view) return;

        const view = this.editor.view;
        const maxPos = view.state.doc.content.size;
        const safePos = Math.min(Math.max(0, position), maxPos);
        const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content);

        if (contentNode) {
            const transaction = view.state.tr.insert(safePos, contentNode);
            view.dispatch(transaction);
            this.onValueChanged(this.editor.value);
        }
    }

    /**
     * Replaces text in a specific range
     * @param content - HTML string to insert
     * @param from - Start position
     * @param to - End position
     * @param asHtml - If true, parses as HTML; if false, inserts as plain text
     */
    public replaceRange(content: string, from: number, to: number, asHtml: boolean = false): void {
        if (!this.editor || !this.editor.view) return;

        const view = this.editor.view;
        const maxPos = view.state.doc.content.size;
        const safeFrom = Math.min(Math.max(0, from), maxPos);
        const safeTo = Math.min(Math.max(safeFrom, to), maxPos);
        const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content);

        if (contentNode) {
            const transaction = view.state.tr.replaceWith(safeFrom, safeTo, contentNode);
            view.dispatch(transaction);
            this.onValueChanged(this.editor.value);
        }
    }



    showVariablePicker() {
        this.easyEditorService.openTableMultiPicker(this.variables, this.variableTableSettings, "Please select a variable").pipe(first()).subscribe(result => {
            if (result) {
                result.forEach(c => {
                    this.insertAfterSelection(`<${RbjTagNode} class="rbj-tag" ${RbjTagIdAttribute}="${c.name}" ${RbjTagValueAttribute}="${c.name}">${c.name}</${RbjTagNode}>`, true);
                });
            }
        });
    }


    // Private methods
    private createCustomSchema(): Schema {
        let nodes = schema.spec.nodes.addBefore("div", "rbjTag", rbjTagNodeSpec);
        let marks = schema.spec.marks;
        //marks = marks.addToStart("rbjSpanTag", rbjTagMarkSpec);
        return new Schema({
            nodes: nodes,
            marks: marks
        });
    }
}

// Define the custom node specification for the rbj-tag element.
export const rbjTagNodeSpec: NodeSpec = {
    // Define the node attributes for the tag
    attrs: {
        "rbj-tag-id": { default: "" },
        "tag-value": { default: "" },
        "tag-preview": { default: "" }
    },
    // Specify that this node should be treated as an inline element
    inline: true,
    // Allow the node to be part of inline content
    group: "inline",
    // Make it atomic (non-editable, treated as a single unit)
    atom: true,

    // Define how the node should be rendered in the DOM
    toDOM: (node) => {
        let tagPreview = node.attrs["tag-preview"];
        let displayValue = tagPreview == 'true' ? node.attrs["tag-value"] : node.attrs["rbj-tag-id"];

        return [
            "span",
            {
                class: "rbj-tag",
                "rbj-tag-id": node.attrs["rbj-tag-id"],
                "tag-marker": node.attrs["tag-marker"],
                "tag-value": node.attrs["tag-value"],
                "tag-preview": node.attrs["tag-preview"],
                //contenteditable: "false",
                //spellcheck: "false",
                style: "display: inline;"
            },
            //node.attrs["tag-marker"] + node.attrs["tag-value"]  // Display the tag content directly
            displayValue
        ];
    },

    // Define how to parse the node from existing DOM elements
    parseDOM: [
        {
            // Look for span elements with class rbj-tag (higher priority)
            tag: "span[rbj-tag-id]",
            priority: 51,  // Higher priority to catch before other parsers
            // Extract attributes from the DOM element
            getAttrs: (dom) => {
                const element = dom as HTMLElement;
                // Must have rbj-tag-id attribute to be valid
                if (!element.hasAttribute("rbj-tag-id")) {
                    return false;
                }
                return {
                    "rbj-tag-id": element.getAttribute("rbj-tag-id") || "",
                    "tag-preview": element.getAttribute("tag-preview") || "false",
                    "tag-value": element.getAttribute("tag-value") || element.getAttribute("rbj-tag-id")
                };
            }
        },
        {
            // Look for div elements with rbj-tag-id attribute (for backward compatibility)
            tag: "div[rbj-tag-id]",
            // Extract attributes from the DOM element
            getAttrs: (dom) => {
                const element = dom as HTMLElement;
                return {
                    "rbj-tag-id": element.getAttribute("rbj-tag-id") || "",
                    "tag-preview": element.getAttribute("tag-preview") || "false",
                    "tag-value": element.getAttribute("tag-value") || element.getAttribute("rbj-tag-id")
                };
            }
        }
    ]
};