import {
  Component,
  OnInit,
  OnDestroy,
  HostListener,
  inject,
  signal
} from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { ElectronBridgeService } from '../../../core/platform/electron/electron-bridge.service';
import { ViewportService } from '../../../core/platform/viewport.service';
import { ContextMenuComponent } from '../../../shared';
import type { ContextMenuParams } from '../../../core/platform/electron/electron-api.models';
import {
  CustomEmojiContextMenuTarget,
  CustomEmojiService,
  resolveCustomEmojiContextMenuTarget
} from '../../../domains/custom-emoji';
import { APP_TRANSLATE_IMPORTS } from '../../../core/i18n';

type ContextMenuCommand = 'cut' | 'copy' | 'paste' | 'selectAll';
type ContextMenuAction = ContextMenuCommand | 'copyLink' | 'copyImage';
type TextControlElement = HTMLInputElement | HTMLTextAreaElement;
type ContextMenuTarget = TextControlElement | HTMLElement;

interface ContextMenuSelectionSnapshot {
  range: Range | null;
  selectedText: string;
  selectionDirection: 'forward' | 'backward' | 'none' | null;
  selectionEnd: number | null;
  selectionStart: number | null;
  target: ContextMenuTarget | null;
}

const NON_TEXT_INPUT_TYPES = new Set([
  'button',
  'checkbox',
  'color',
  'date',
  'datetime-local',
  'file',
  'hidden',
  'image',
  'month',
  'number',
  'radio',
  'range',
  'reset',
  'submit',
  'time',
  'week'
]);

@Component({
  selector: 'app-native-context-menu',
  standalone: true,
  imports: [ContextMenuComponent, ...APP_TRANSLATE_IMPORTS],
  templateUrl: './native-context-menu.component.html'
})
export class NativeContextMenuComponent implements OnInit, OnDestroy {
  params = signal<ContextMenuParams | null>(null);
  customEmojiMenu = signal<(CustomEmojiContextMenuTarget & { posX: number; posY: number }) | null>(null);

  private readonly document = inject(DOCUMENT);
  private readonly customEmoji = inject(CustomEmojiService);
  private readonly electronBridge = inject(ElectronBridgeService);
  private readonly viewport = inject(ViewportService);
  private cleanup: (() => void) | null = null;
  private selectionSnapshot: ContextMenuSelectionSnapshot | null = null;

  @HostListener('document:contextmenu', ['$event'])
  onDocumentContextMenu(event: MouseEvent): void {
    const customEmojiTarget = resolveCustomEmojiContextMenuTarget(event.target);

    if (customEmojiTarget) {
      this.openCustomEmojiMenu(event, customEmojiTarget);
      return;
    }

    if (event.defaultPrevented) {
      return;
    }

    // On mobile (non-Electron), let the OS-native context menu handle text inputs,
    // selection, links, and images. Intercepting here suppresses the OS menu and
    // leaves the user without copy/paste/select-all affordances.
    if (this.viewport.isMobile() && !this.electronBridge.isAvailable) {
      return;
    }

    this.captureSelectionSnapshot(event);

    if (this.electronBridge.isAvailable) {
      return;
    }

    const params = this.buildBrowserContextMenuParams(event);

    if (!params) {
      this.close();
      return;
    }

    event.preventDefault();
    this.params.set(params);
  }

  ngOnInit(): void {
    this.document.addEventListener('contextmenu', this.onDocumentContextMenuCapture, true);

    const api = this.electronBridge.getApi();

    if (!api?.onContextMenu) {
      return;
    }

    this.cleanup = api.onContextMenu((incoming) => {
      const hasContent = incoming.isEditable
        || !!incoming.selectionText
        || !!incoming.linkURL
        || (incoming.mediaType === 'image' && !!incoming.srcURL);

      if (!hasContent) {
        this.close();
        return;
      }

      this.params.set(incoming);
    });
  }

  ngOnDestroy(): void {
    this.document.removeEventListener('contextmenu', this.onDocumentContextMenuCapture, true);
    this.cleanup?.();
    this.cleanup = null;
  }

  close(): void {
    this.params.set(null);
    this.customEmojiMenu.set(null);
    this.selectionSnapshot = null;
  }

  async addCustomEmojiToLibrary(): Promise<void> {
    const menu = this.customEmojiMenu();

    if (!menu) {
      return;
    }

    await this.customEmoji.saveEmojiToLibrary(menu.emojiId);
    this.close();
  }

  async removeCustomEmojiFromLibrary(): Promise<void> {
    const menu = this.customEmojiMenu();

    if (!menu) {
      return;
    }

    await this.customEmoji.removeEmojiFromLibrary(menu.emojiId);
    this.close();
  }

  onActionPointerDown(event: PointerEvent, action: ContextMenuAction): void {
    if (event.button !== 0) {
      return;
    }

    event.preventDefault();
    void this.runAction(action);
  }

  onActionClick(event: MouseEvent, action: ContextMenuAction): void {
    if (event.detail > 0) {
      return;
    }

    event.preventDefault();
    void this.runAction(action);
  }

  private readonly onDocumentContextMenuCapture = (event: MouseEvent): void => {
    if (resolveCustomEmojiContextMenuTarget(event.target)) {
      event.preventDefault();
    }
  };

  private openCustomEmojiMenu(event: MouseEvent, target: CustomEmojiContextMenuTarget): void {
    event.preventDefault();
    event.stopPropagation();
    this.params.set(null);
    this.selectionSnapshot = null;

    if (target.action === 'add') {
      if (!this.customEmoji.findEmoji(target.emojiId) || this.customEmoji.isEmojiInLibrary(target.emojiId)) {
        this.close();
        return;
      }
    }

    if (target.action === 'remove' && !this.customEmoji.isEmojiInLibrary(target.emojiId)) {
      this.close();
      return;
    }

    this.customEmojiMenu.set({
      ...target,
      posX: event.clientX,
      posY: event.clientY
    });
  }

  private async runAction(action: ContextMenuAction): Promise<void> {
    try {
      switch (action) {
        case 'copyLink':
          await this.copyLink();
          break;
        case 'copyImage':
          await this.copyImage();
          break;
        default:
          await this.execCommand(action);
          break;
      }
    } finally {
      this.close();
    }
  }

  private async execCommand(command: ContextMenuCommand): Promise<void> {
    let handled = false;

    switch (command) {
      case 'copy':
        handled = await this.copySelection();
        break;
      case 'cut':
        handled = await this.cutSelection();
        break;
      case 'paste':
        handled = await this.pasteSelection();
        break;
      case 'selectAll':
        handled = this.selectAllSelection();
        break;
    }

    if (handled) {
      return;
    }

    const api = this.electronBridge.getApi();

    if (api?.contextMenuCommand) {
      this.restoreSelectionSnapshot();
      await api.contextMenuCommand(command);
    }
  }

  private async copyLink(): Promise<void> {
    const url = this.params()?.linkURL;

    if (url) {
      await this.writeTextToClipboard(url);
    }
  }

  private async copyImage(): Promise<void> {
    const srcURL = this.params()?.srcURL;
    const api = this.electronBridge.getApi();

    if (!srcURL) {
      return;
    }

    if (api?.copyImageToClipboard) {
      const copied = await api.copyImageToClipboard(srcURL).catch(() => false);

      if (copied) {
        return;
      }
    }

    await this.copyImageToBrowserClipboard(srcURL);
  }

  private captureSelectionSnapshot(event: MouseEvent): void {
    const target = this.getTargetElement(event.target);
    const textControl = this.resolveTextControlTarget(target);

    if (textControl) {
      this.selectionSnapshot = this.createTextControlSnapshot(textControl);
      return;
    }

    const selection = this.document.getSelection();

    this.selectionSnapshot = {
      range: selection?.rangeCount ? selection.getRangeAt(0).cloneRange() : null,
      selectedText: selection?.toString() ?? '',
      selectionDirection: null,
      selectionEnd: null,
      selectionStart: null,
      target: this.resolveContentEditableTarget(target) ?? (target instanceof HTMLElement ? target : null)
    };
  }

  private createTextControlSnapshot(target: TextControlElement): ContextMenuSelectionSnapshot {
    return {
      range: null,
      selectedText: this.getTextControlSelection(target),
      selectionDirection: target.selectionDirection,
      selectionEnd: target.selectionEnd,
      selectionStart: target.selectionStart,
      target
    };
  }

  private buildBrowserContextMenuParams(event: MouseEvent): ContextMenuParams | null {
    const target = this.getTargetElement(event.target);
    const editableTarget = this.resolveEditableTarget(target);
    const selectionText = this.selectionSnapshot?.selectedText ?? '';
    const linkURL = this.resolveLinkUrl(target);
    const srcURL = this.resolveImageUrl(target);
    const isEditable = !!editableTarget && !this.isDisabledTarget(editableTarget);

    if (!isEditable && !selectionText && !linkURL && !srcURL) {
      return null;
    }

    return {
      posX: event.clientX,
      posY: event.clientY,
      isEditable,
      selectionText,
      linkURL,
      mediaType: srcURL ? 'image' : '',
      srcURL,
      editFlags: {
        canCut: !!selectionText && !!editableTarget && this.canWriteToTarget(editableTarget),
        canCopy: !!selectionText,
        canPaste: !!editableTarget && this.canWriteToTarget(editableTarget) && this.canReadClipboard(),
        canSelectAll: !!editableTarget && !this.isDisabledTarget(editableTarget)
      }
    };
  }

  private restoreSelectionSnapshot(): ContextMenuTarget | null {
    const snapshot = this.selectionSnapshot;

    if (!snapshot?.target || !snapshot.target.isConnected) {
      return null;
    }

    if (this.isTextControl(snapshot.target)) {
      snapshot.target.focus({ preventScroll: true });

      if (snapshot.selectionStart !== null && snapshot.selectionEnd !== null) {
        snapshot.target.setSelectionRange(
          snapshot.selectionStart,
          snapshot.selectionEnd,
          snapshot.selectionDirection ?? undefined
        );
      }

      return snapshot.target;
    }

    if (this.isContentEditableTarget(snapshot.target)) {
      snapshot.target.focus({ preventScroll: true });
    }

    const selection = this.document.getSelection();

    if (selection && snapshot.range) {
      try {
        selection.removeAllRanges();
        selection.addRange(snapshot.range);
      } catch {
        return snapshot.target;
      }
    }

    return snapshot.target;
  }

  private async copySelection(): Promise<boolean> {
    const text = this.selectionSnapshot?.selectedText ?? '';

    if (!text) {
      return false;
    }

    return await this.writeTextToClipboard(text);
  }

  private async cutSelection(): Promise<boolean> {
    const target = this.restoreSelectionSnapshot();

    if (!target || !this.canWriteToTarget(target)) {
      return false;
    }

    const text = this.selectionSnapshot?.selectedText ?? '';

    if (!text || !(await this.writeTextToClipboard(text))) {
      return false;
    }

    if (this.isTextControl(target)) {
      const selectionStart = target.selectionStart ?? this.selectionSnapshot?.selectionStart ?? 0;
      const selectionEnd = target.selectionEnd ?? this.selectionSnapshot?.selectionEnd ?? selectionStart;

      target.setRangeText('', selectionStart, selectionEnd, 'start');
      this.dispatchInputEvent(target);
      this.selectionSnapshot = this.createTextControlSnapshot(target);
      return true;
    }

    const selection = this.document.getSelection();

    if (!selection?.rangeCount) {
      return false;
    }

    const range = selection.getRangeAt(0);

    range.deleteContents();
    selection.removeAllRanges();
    selection.addRange(range);
    this.dispatchInputEvent(target);
    this.selectionSnapshot = {
      range: range.cloneRange(),
      selectedText: '',
      selectionDirection: null,
      selectionEnd: null,
      selectionStart: null,
      target
    };

    return true;
  }

  private async pasteSelection(): Promise<boolean> {
    const target = this.restoreSelectionSnapshot();

    if (!target || !this.canWriteToTarget(target)) {
      return false;
    }

    const text = await this.readClipboardText();

    if (text === null) {
      return false;
    }

    if (this.isTextControl(target)) {
      const selectionStart = target.selectionStart ?? target.value.length;
      const selectionEnd = target.selectionEnd ?? selectionStart;

      target.setRangeText(text, selectionStart, selectionEnd, 'end');
      this.dispatchInputEvent(target);
      this.selectionSnapshot = this.createTextControlSnapshot(target);
      return true;
    }

    const selection = this.document.getSelection();

    if (!selection) {
      return false;
    }

    if (!selection.rangeCount) {
      const range = this.document.createRange();

      range.selectNodeContents(target);
      range.collapse(false);
      selection.addRange(range);
    }

    const range = selection.getRangeAt(0);
    const textNode = this.document.createTextNode(text);

    range.deleteContents();
    range.insertNode(textNode);
    range.setStartAfter(textNode);
    range.collapse(true);
    selection.removeAllRanges();
    selection.addRange(range);
    this.dispatchInputEvent(target);
    this.selectionSnapshot = {
      range: range.cloneRange(),
      selectedText: '',
      selectionDirection: null,
      selectionEnd: null,
      selectionStart: null,
      target
    };

    return true;
  }

  private selectAllSelection(): boolean {
    const target = this.selectionSnapshot?.target;

    if (!target || !target.isConnected || this.isDisabledTarget(target)) {
      return false;
    }

    if (this.isTextControl(target)) {
      target.focus({ preventScroll: true });
      target.select();
      this.selectionSnapshot = this.createTextControlSnapshot(target);
      return true;
    }

    if (!this.isContentEditableTarget(target)) {
      return false;
    }

    const selection = this.document.getSelection();

    if (!selection) {
      return false;
    }

    const range = this.document.createRange();

    target.focus({ preventScroll: true });
    range.selectNodeContents(target);
    selection.removeAllRanges();
    selection.addRange(range);
    this.selectionSnapshot = {
      range: range.cloneRange(),
      selectedText: selection.toString(),
      selectionDirection: null,
      selectionEnd: null,
      selectionStart: null,
      target
    };

    return true;
  }

  private async writeTextToClipboard(value: string): Promise<boolean> {
    if (navigator.clipboard?.writeText) {
      try {
        await navigator.clipboard.writeText(value);
        return true;
      } catch {}
    }

    const body = this.document.body;

    if (!body) {
      return false;
    }

    const textarea = this.document.createElement('textarea');

    textarea.value = value;
    textarea.setAttribute('readonly', 'true');
    textarea.style.position = 'fixed';
    textarea.style.opacity = '0';
    textarea.style.pointerEvents = 'none';
    body.appendChild(textarea);
    textarea.select();

    try {
      return this.document.execCommand('copy');
    } catch {
      return false;
    } finally {
      body.removeChild(textarea);
    }
  }

  private async readClipboardText(): Promise<string | null> {
    if (navigator.clipboard?.readText) {
      try {
        return await navigator.clipboard.readText();
      } catch {}
    }

    return null;
  }

  private async copyImageToBrowserClipboard(srcURL: string): Promise<boolean> {
    if (!navigator.clipboard?.write || typeof ClipboardItem === 'undefined') {
      return false;
    }

    try {
      const response = await fetch(srcURL);

      if (!response.ok) {
        return false;
      }

      const blob = await response.blob();

      if (!blob.type.startsWith('image/')) {
        return false;
      }

      await navigator.clipboard.write([
        new ClipboardItem({
          [blob.type || 'image/png']: blob
        })
      ]);

      return true;
    } catch {
      return false;
    }
  }

  private dispatchInputEvent(target: ContextMenuTarget): void {
    target.dispatchEvent(new Event('input', { bubbles: true }));
  }

  private getTextControlSelection(target: TextControlElement): string {
    const selectionStart = target.selectionStart ?? 0;
    const selectionEnd = target.selectionEnd ?? selectionStart;

    return target.value.slice(selectionStart, selectionEnd);
  }

  private getTargetElement(target: EventTarget | null): Element | null {
    if (target && typeof (target as Element).closest === 'function') {
      return target as Element;
    }

    const parentElement = (target as Node | null)?.parentElement;

    return parentElement && typeof parentElement.closest === 'function'
      ? parentElement
      : null;
  }

  private resolveEditableTarget(target: Element | null): ContextMenuTarget | null {
    return this.resolveTextControlTarget(target) ?? this.resolveContentEditableTarget(target);
  }

  private resolveTextControlTarget(target: Element | null): TextControlElement | null {
    const textControl = target?.closest('input, textarea');

    if (textControl instanceof HTMLTextAreaElement) {
      return textControl;
    }

    if (textControl instanceof HTMLInputElement && !NON_TEXT_INPUT_TYPES.has(textControl.type)) {
      return textControl;
    }

    return null;
  }

  private resolveContentEditableTarget(target: Element | null): HTMLElement | null {
    const editable = target?.closest('[contenteditable]:not([contenteditable="false"])');

    return editable instanceof HTMLElement && editable.isContentEditable ? editable : null;
  }

  private resolveLinkUrl(target: Element | null): string {
    const link = target?.closest('a[href]');

    return link instanceof HTMLAnchorElement ? link.href : '';
  }

  private resolveImageUrl(target: Element | null): string {
    const imageTarget = this.asImageLikeElement(target) ?? this.asImageLikeElement(target?.closest('img') ?? null);

    return imageTarget?.currentSrc || imageTarget?.src || '';
  }

  private asImageLikeElement(target: Element | null): { currentSrc?: string; src?: string } | null {
    if (!target || !('src' in target) || typeof target.src !== 'string') {
      return null;
    }

    return target as { currentSrc?: string; src: string };
  }

  private isTextControl(target: ContextMenuTarget | null): target is TextControlElement {
    return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement;
  }

  private isContentEditableTarget(target: ContextMenuTarget | null): target is HTMLElement {
    return target instanceof HTMLElement && target.isContentEditable;
  }

  private isDisabledTarget(target: ContextMenuTarget | null): boolean {
    return this.isTextControl(target) ? target.disabled : false;
  }

  private canWriteToTarget(target: ContextMenuTarget | null): boolean {
    if (this.isTextControl(target)) {
      return !target.disabled && !target.readOnly;
    }

    return this.isContentEditableTarget(target);
  }

  private canReadClipboard(): boolean {
    return typeof navigator !== 'undefined'
      && (!!navigator.clipboard?.readText || this.electronBridge.isAvailable);
  }
}
