import { LitElement } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import type { MentionItem } from './nile-rte-mentions-item';

type MentionsConfig = Record<string, MentionItem[]>;

@customElement('nile-rte-mentions')
export class NileRteMentions extends LitElement {
  protected createRenderRoot() {
    return this;
  }

  @property({ type: Boolean, attribute: 'displayformat' , reflect: true})
  displayFormat = false;

  @property({
    attribute: 'mentions',
    converter: {
      fromAttribute: (value: string): MentionsConfig => {
        try {
          const parsed = JSON.parse(value);
          const out: MentionsConfig = {};
          for (const trig of Object.keys(parsed)) {
            const arr = parsed[trig];
            if (Array.isArray(arr)) {
              out[trig] = arr
                .filter(
                  i =>
                    i &&
                    typeof i.key === 'string' &&
                    typeof i.label === 'string'
                )
                .map(i => ({ key: i.key, label: i.label }));
            }
          }
          return out;
        } catch {
          return {};
        }
      },
      toAttribute: (v: MentionsConfig) => JSON.stringify(v),
    },
  })
  mentions: MentionsConfig = {};

  // nile-rte-mentions.ts (inside class)

  @property({
    attribute: 'mentionformat',
    converter: {
      fromAttribute: (
        v: string
      ): Record<string, { prefix?: string; suffix?: string }> => {
        try {
          const o = JSON.parse(v);
          return o && typeof o === 'object' ? o : {};
        } catch {
          return {};
        }
      },
      toAttribute: (v: Record<string, { prefix?: string; suffix?: string }>) =>
        JSON.stringify(v ?? {}),
    },
  })
  mentionFormat: Record<string, { prefix?: string; suffix?: string }> = {};

  @state() private itemsFormats: Record<
    string,
    { prefix?: string; suffix?: string }
  > = {};

  private getTriggerFormat(trigger: string): {
    prefix: string;
    suffix: string;
  } {
    const fromItems = this.itemsFormats[trigger] || {};
    const fromAttr = this.mentionFormat[trigger] || {};
    return {
      prefix: fromItems.prefix ?? fromAttr.prefix ?? '',
      suffix: fromItems.suffix ?? fromAttr.suffix ?? '',
    };
  }

  private formatLabel(trigger: string, rawLabel: string): string {
    const { prefix, suffix } = this.getTriggerFormat(trigger);
    return `${prefix ?? ''}${rawLabel}${suffix ?? ''}`;
  }

  @property({
    attribute: 'mentioncolors',
    converter: {
      fromAttribute: (value: string): Record<string, string> => {
        try {
          const o = JSON.parse(value);
          return o && typeof o === 'object' ? o : {};
        } catch {
          return {};
        }
      },
      toAttribute: (v: Record<string, string>) => JSON.stringify(v ?? {}),
    },
  })
  mentionColors: Record<string, string> = {};

  @state() private externalConfig: MentionsConfig = {};

  @state() private itemsConfig: MentionsConfig = {};

  @state() private mentionActiveIndex: number = -1;

  @state() private itemsColors: Record<string, string> = {};

  private get config(): MentionsConfig {
    return { ...this.externalConfig, ...this.mentions, ...this.itemsConfig };
  }

  // wired from editor
  private editorEl: HTMLElement | null = null;
  private hostEl: HTMLElement | null = null;

  private dropdownEl: HTMLElement | null = null;
  private menuEl: HTMLElement | null = null;

  private lastRange: Range | null = null;

  private mentionOpen = false;
  private mentionTrigger: string | null = null;
  private mentionQuery = '';
  private mentionSuggestions: MentionItem[] = [];
  private mentionFiltered: MentionItem[] = [];
  private mentionX = 0;
  private mentionY = 0;
  private triggerBtn: HTMLElement | null = null;

  private mo: MutationObserver | null = null;

  attach(editorEl: HTMLElement, hostEl: HTMLElement) {
    this.editorEl = editorEl;
    this.hostEl = hostEl;

    this.injectStyles();
    this.ensureMentionDropdown();
    this.rebuildConfigFromChildren();

    this.mo = new MutationObserver(() => this.rebuildConfigFromChildren());
    this.mo.observe(this, { childList: true, subtree: true, attributes: true });

    this.editorEl.addEventListener('input', this.onEditorInput);
    this.editorEl.addEventListener('mouseup', this.saveSelection);
    this.editorEl.addEventListener('keyup', this.onEditorKeyUp);
    this.editorEl.addEventListener('keydown', this.onEditorKeyDown);
    this.editorEl.addEventListener('scroll', this.repositionMention);

    document.addEventListener('selectionchange', this.onSelectionChange, true);
  }

  detach() {
    if (this.mo) {
      this.mo.disconnect();
      this.mo = null;
    }
    if (this.editorEl) {
      this.editorEl.removeEventListener('input', this.onEditorInput);
      this.editorEl.removeEventListener('mouseup', this.saveSelection);
      this.editorEl.removeEventListener('keyup', this.onEditorKeyUp);
      this.editorEl.removeEventListener('keydown', this.onEditorKeyDown);
      this.editorEl.removeEventListener('scroll', this.repositionMention);
    }
    document.removeEventListener(
      'selectionchange',
      this.onSelectionChange,
      true
    );
  }

  setExternalConfig(cfg: MentionsConfig) {
    this.externalConfig = cfg || {};
  }

  disconnectedCallback(): void {
    this.detach();
    super.disconnectedCallback();
  }

  private rebuildConfigFromChildren() {
    const cfg: MentionsConfig = {};
    const colors: Record<string, string> = {};
    const formats: Record<string, { prefix?: string; suffix?: string }> = {};

    const items = Array.from(
      this.querySelectorAll('nile-rte-mentions-item')
    ) as Array<
      HTMLElement & {
        mentionsCharacter?: string;
        mentionsData?: MentionItem[];
        mentionsColor?: string;
      }
    >;

    for (const item of items) {
      const trig =
        (item as any).mentionsCharacter ??
        item.getAttribute('mentionscharacter') ??
        '';

      if (!trig || typeof trig !== 'string') continue;

      let data: MentionItem[] | null =
        (item as any).mentionsData ??
        (() => {
          const raw = item.getAttribute('mentionsdata');
          if (!raw) return null;
          try {
            const parsed = JSON.parse(raw);
            return Array.isArray(parsed)
              ? parsed.filter(
                  i =>
                    i &&
                    typeof i.key === 'string' &&
                    typeof i.label === 'string'
                )
              : [];
          } catch {
            return [];
          }
        })();

      const color =
        (item as any).mentionsColor ?? item.getAttribute('mentionscolor') ?? '';

      const prefix =
        (item as any).mentionsPrefix ??
        item.getAttribute('mentionsprefix') ??
        '';
      const suffix =
        (item as any).mentionsSuffix ??
        item.getAttribute('mentionssuffix') ??
        '';

      if (!Array.isArray(data)) data = [];
      cfg[trig] = data;
      if (color) colors[trig] = String(color);
      if (prefix || suffix) formats[trig] = { prefix, suffix };
    }

    this.itemsConfig = cfg;
    this.itemsColors = colors;
    this.itemsFormats = formats;
  }

  private injectStyles() {
    if (this.querySelector('style[data-mentions-style]')) return;
    const style = document.createElement('style');
    style.setAttribute('data-mentions-style', 'true');
    style.textContent = `
      .mention-dropdown {
        position: absolute; z-index: 1000; list-style: none; margin: 0; padding: 4px;
        border: 1px solid #e5e7eb; border-radius: 6px; background: #fff;
        box-shadow: 0 4px 10px rgba(0,0,0,.08); max-height: 180px; overflow: auto; font-size: 14px;
      }
      .mention-dropdown li { padding: 6px 8px; cursor: pointer; border-radius: 4px; }
      .mention-dropdown li:hover { background: #f1f5f9; }
      .mention { background: #eef2ff; padding: 0 3px; border-radius: 3px; }
   nile-menu.mentions-menu::part(menu__items-wrapper){
   max-height: 260px;
   overflow-y: auto;
   }
   .mentions-menu nile-menu-item.active {
  background: #dbeafe;   /* highlight */
  outline: none;
}


    `;
    this.insertBefore(style, this.firstChild);
  }

  private getTriggerColor(trigger: string): string | undefined {
    return this.itemsColors[trigger] ?? this.mentionColors[trigger];
  }

  private ensureMentionDropdown() {
    if (this.dropdownEl || !this.hostEl) return;

    const dd = document.createElement('nile-dropdown');
    dd.style.zIndex = '1000';

    const btn = document.createElement('nile-button');
    btn.setAttribute('slot', 'trigger');
    btn.style.position = 'absolute';
    btn.style.width = '1px';
    btn.style.height = '1px';
    btn.style.border = '0';
    btn.style.padding = '0';
    btn.style.opacity = '0';
    btn.style.pointerEvents = 'none';
    dd.appendChild(btn);
    this.triggerBtn = btn;
    const menu = document.createElement('nile-menu');
    menu.classList.add('mentions-menu');
    dd.appendChild(menu);

    this.hostEl.appendChild(dd);
    this.dropdownEl = dd;
    this.menuEl = menu;
  }

  private onSelectionChange = () => {
    if (!this.editorEl) return;
    const sel = document.getSelection();
    if (!sel || sel.rangeCount === 0) return;
    const range = sel.getRangeAt(0);
    if (this.editorEl.contains(range.commonAncestorContainer)) {
      this.lastRange = range.cloneRange();
    }
  };
  private saveSelection = () => {
    const sel = window.getSelection();
    if (sel && sel.rangeCount) this.lastRange = sel.getRangeAt(0).cloneRange();
  };
  private restoreSelection() {
    if (!this.lastRange) return;
    const sel = document.getSelection();
    if (!sel) return;
    sel.removeAllRanges();
    sel.addRange(this.lastRange);
  }
 

  private getCaretClientRect(): DOMRect | null {
    const sel = document.getSelection();
    if (!sel || sel.rangeCount === 0) return null;

    const range = sel.getRangeAt(0).cloneRange();
    range.collapse(false);

    const rects = range.getClientRects();
    if (rects && rects.length > 0) return rects[0];

    const marker = document.createElement('span');
    marker.setAttribute('data-caret-marker', '1');
    marker.textContent = '\u200B';
    range.insertNode(marker);

    const rect = marker.getBoundingClientRect();

    const after = document.createRange();
    after.setStartAfter(marker);
    after.collapse(true);
    sel.removeAllRanges();
    sel.addRange(after);
    marker.remove();

    return rect || null;
  }

  private onEditorInput = () => {
    
    if (this.mentionOpen) {
      this.handleMention();
      return;
    }
    const sel = window.getSelection();
    if (!sel || !sel.anchorNode) return;
  
    let node: Node | null = sel.anchorNode;
    if (node.nodeType !== Node.TEXT_NODE && node.firstChild?.nodeType === Node.TEXT_NODE) {
      node = node.firstChild;
    }
  
    if (!node || node.nodeType !== Node.TEXT_NODE) return;
  
    const text = node.textContent || '';
    const cfg = this.config;
  
    
    for (const trigger of Object.keys(cfg)) {
      const triggerIndex = text.lastIndexOf(trigger);
      if (triggerIndex !== -1) {
        const caretPos = sel.focusOffset;
        if (caretPos >= triggerIndex + trigger.length && caretPos <= triggerIndex + trigger.length + 1) {
          this.openMention(trigger);
          return;
        }
      }
    }
  };
  
  

  private onEditorKeyUp = (e: KeyboardEvent) => {
    this.saveSelection();
    const key = e.key;
    const cfg = this.config;
    if (key && cfg[key]) {
      if (this.mentionOpen && this.mentionTrigger !== key) {
        this.closeMention();
      }
      this.openMention(key);
      return;
    }
    if (this.mentionOpen) {
      this.handleMention();
    }
  };
  

  private onEditorKeyDown = (e: KeyboardEvent) => {
    if (!this.mentionOpen) return;
    const key = e.key;

    if (key === 'ArrowDown' || key === 'ArrowUp') {
      e.preventDefault();
      if (!this.mentionFiltered.length) return;

      if (key === 'ArrowDown') {
        this.mentionActiveIndex =
          (this.mentionActiveIndex + 1) % this.mentionFiltered.length;
      } else {
        this.mentionActiveIndex =
          (this.mentionActiveIndex - 1 + this.mentionFiltered.length) %
          this.mentionFiltered.length;
      }

      this.renderMentionList();
    } else if (key === 'Enter') {
      if (
        this.mentionActiveIndex >= 0 &&
        this.mentionFiltered[this.mentionActiveIndex]
      ) {
        e.preventDefault();
        this.selectMention(this.mentionFiltered[this.mentionActiveIndex]);
      }
    } else if (key === 'Escape') {
      e.preventDefault();
      this.closeMention();
    }
  };

  private openMention(trigger: string) {
    this.saveSelection();
    if (!this.editorEl || !this.dropdownEl || !this.hostEl) return;

    const caretRect = this.getCaretClientRect();
    if (!caretRect) return;

    const hostRect = this.hostEl.getBoundingClientRect();

    this.mentionX = caretRect.left - hostRect.left;
    this.mentionY = caretRect.bottom - hostRect.top;

    this.mentionTrigger = trigger;
    this.mentionSuggestions = this.config[trigger] || [];
    this.mentionQuery = '';
    this.updateMentionFiltered();

    this.mentionActiveIndex = this.mentionFiltered.length ? 0 : -1;
    this.mentionOpen = true;
    this.renderMentionList();

    this.dispatchEvent(
      new CustomEvent('nile-mention-opened', {
        detail: { trigger, suggestions: this.mentionSuggestions },
        bubbles: true,
        composed: true,
      })
    );
  }

  private closeMention() {
    this.mentionOpen = false;
    this.mentionTrigger = null;
    this.mentionQuery = '';
    this.mentionFiltered = [];
    // hide dropdown
    this.dropdownEl?.removeAttribute('open');
  }

  private handleMention() {
    if (!this.mentionOpen || !this.mentionTrigger) return;
    const sel = window.getSelection();
    if (!sel || !sel.anchorNode) return;

    let node: Node | null = sel.anchorNode;
    if (node.nodeType !== Node.TEXT_NODE) {
      const c =
        (node as HTMLElement).childNodes?.[sel.anchorOffset] || node.firstChild;
      if (c?.nodeType === Node.TEXT_NODE) node = c;
    }
    const text =
      node && node.nodeType === Node.TEXT_NODE ? node.textContent || '' : '';
    const idx = text.lastIndexOf(this.mentionTrigger);
    if (idx >= 0) {
      this.mentionQuery = text.substring(idx + 1);
      this.updateMentionFiltered();
      this.renderMentionList();
    } else {
      this.closeMention();
    }
  }

  private updateMentionFiltered() {
    const q = this.mentionQuery.toLowerCase();
    this.mentionFiltered = (this.mentionSuggestions || []).filter(item =>
      item.label.toLowerCase().startsWith(q)
    );
  }

  private renderMentionList() {
    if (!this.dropdownEl || !this.menuEl) return;
    if (!this.triggerBtn) return;

    this.triggerBtn.style.left = `${this.mentionX}px`;
    this.triggerBtn.style.top = `${this.mentionY}px`;

    this.menuEl.innerHTML = '';

    if (!this.mentionOpen || !this.mentionFiltered.length) {
      this.dropdownEl.removeAttribute('open');
      return;
    }

    this.mentionFiltered.forEach((item, idx) => {
      const mi = document.createElement('nile-menu-item');
      mi.textContent = item.label;

      mi.setAttribute('role', 'option');
      if (idx === this.mentionActiveIndex) {
        mi.classList.add('active');
        mi.setAttribute('aria-selected', 'true');
      } else {
        mi.classList.remove('active');
        mi.removeAttribute('aria-selected');
      }

      mi.addEventListener('mouseenter', () => {
        this.mentionActiveIndex = idx;
        this.renderMentionList();
      });

      mi.addEventListener('pointerdown', e => {
        e.preventDefault();
        this.selectMention(item);
      });

      this.menuEl?.appendChild(mi);
    });

    this.dropdownEl.setAttribute('open', '');
  }

  private repositionMention = () => {
    if (!this.mentionOpen || !this.hostEl || !this.triggerBtn) return;

    const caretRect = this.getCaretClientRect();
    if (!caretRect) return;

    const hostRect = this.hostEl.getBoundingClientRect();

    this.mentionX = caretRect.left - hostRect.left;
    this.mentionY = caretRect.bottom - hostRect.top;

    this.triggerBtn.style.left = `${this.mentionX}px`;
    this.triggerBtn.style.top = `${this.mentionY}px`;
  };

  private selectMention(item: MentionItem) {
    this.restoreSelection();
    if (!this.lastRange || !this.mentionTrigger || !this.editorEl) return;

    const trigger = this.mentionTrigger;

    let node: Node = this.lastRange.startContainer;
    let offset = this.lastRange.startOffset;
    if (node.nodeType !== Node.TEXT_NODE) {
      const child = node.childNodes[offset];
      if (child?.nodeType === Node.TEXT_NODE) {
        node = child;
        offset = 0;
      } else return;
    }

    const textNode = node as Text;
    const text = textNode.textContent || '';
    const atIndex = text.lastIndexOf(this.mentionTrigger, offset);
    if (atIndex < 0) return;

    const endIndex = atIndex + 1 + this.mentionQuery.length;
    const mentionRange = document.createRange();
    mentionRange.setStart(textNode, atIndex);
    mentionRange.setEnd(textNode, endIndex);
    mentionRange.deleteContents();

    const span = document.createElement('span');
    span.classList.add('mention');

    const fmt = this.getTriggerFormat(this.mentionTrigger);

    if (this.displayFormat) {
      span.textContent = `${fmt.prefix ?? ''}${item.label}${fmt.suffix ?? ''}`;
    } else {
      span.textContent = `${this.mentionTrigger}${item.label}`;
      if (fmt.prefix) span.setAttribute('data-mention-prefix', fmt.prefix);
      if (fmt.suffix) span.setAttribute('data-mention-suffix', fmt.suffix);
    }

    span.setAttribute('data-mention-key', item.key);
    span.setAttribute('data-mention-label', item.label);
    span.setAttribute('data-mention-trigger', this.mentionTrigger!);

    const bg = this.getTriggerColor(this.mentionTrigger);
    if (bg) span.style.backgroundColor = bg;

    mentionRange.insertNode(span);

    const spaceNode = document.createTextNode('\u00A0');
    span.after(spaceNode);

    const sel = window.getSelection();
    const afterRange = document.createRange();
    afterRange.setStartAfter(spaceNode);
    afterRange.collapse(true);
    sel?.removeAllRanges();
    sel?.addRange(afterRange);
    this.lastRange = afterRange.cloneRange();

    this.closeMention();

    this.dispatchEvent(
      new CustomEvent('nile-mention-selected', {
        detail: {
          trigger,
          key: item.key,
          label: item.label,
          prefix: fmt.prefix ?? '',
          suffix: fmt.suffix ?? '',
        },
        bubbles: true,
        composed: true,
      })
    );

    this.editorEl.dispatchEvent(new InputEvent('input', { bubbles: true }));
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'nile-rte-mentions': NileRteMentions;
  }
}
