/**
 * Lexical Embed Node
 *
 * Custom DecoratorNode for inserting sanitized HTML embeds (iframes, etc.)
 * into Lexical editors. Embeds are rendered as non-editable blocks.
 *
 * Security: All HTML is sanitized via sanitizeHtmlRelaxed() which allows
 * iframes but strips scripts, event handlers, and dangerous CSS.
 */

'use client';

import { useCallback, useEffect, useRef, useState, type ReactElement } from 'react';
import {
  $getNodeByKey,
  $getRoot,
  $getSelection,
  createCommand,
  DecoratorNode,
  DOMConversionMap,
  DOMConversionOutput,
  DOMExportOutput,
  LexicalCommand,
  LexicalNode,
  NodeKey,
  SerializedLexicalNode,
  Spread,
  COMMAND_PRIORITY_EDITOR,
} from 'lexical';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { $insertNodeToNearestRoot } from '@lexical/utils';
import { sanitizeHtmlRelaxed } from './sanitize-client';

export interface EmbedPayload {
  html: string;
  key?: NodeKey;
}

export type SerializedEmbedNode = Spread<
  {
    html: string;
  },
  SerializedLexicalNode
>;

export const INSERT_EMBED_COMMAND: LexicalCommand<EmbedPayload> =
  createCommand('INSERT_EMBED_COMMAND');

// --- EmbedComponent (rendered inside the editor) ---

function EmbedComponent({ html, nodeKey }: { html: string; nodeKey: NodeKey }) {
  const [editor] = useLexicalComposerContext();
  const [isSelected, setIsSelected] = useState(false);
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const handleClickOutside = (e: MouseEvent) => {
      if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
        setIsSelected(false);
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, []);

  const handleClick = useCallback(() => {
    setIsSelected(true);
  }, []);

  const handleDelete = useCallback(() => {
    editor.update(() => {
      const node = $getNodeByKey(nodeKey);
      if (node) {
        node.remove();
      }
    });
  }, [editor, nodeKey]);

  const overlayBtnStyle: React.CSSProperties = {
    padding: '3px 6px',
    border: '1px solid #d1d5db',
    borderRadius: '3px',
    background: 'white',
    cursor: 'pointer',
    fontSize: '11px',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    color: '#374151',
  };

  return (
    <div
      ref={containerRef}
      onClick={handleClick}
      style={{
        position: 'relative',
        cursor: 'pointer',
        padding: '4px 0',
        border: isSelected ? '2px solid #3b82f6' : '2px solid transparent',
        borderRadius: '4px',
      }}
    >
      <div
        dangerouslySetInnerHTML={{ __html: sanitizeHtmlRelaxed(html) }}
        style={{ pointerEvents: 'none' }}
      />
      {isSelected && (
        <div
          style={{
            position: 'absolute',
            top: '-36px',
            right: '4px',
            display: 'flex',
            gap: '2px',
            background: 'white',
            border: '1px solid #d1d5db',
            borderRadius: '6px',
            padding: '3px',
            boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
            zIndex: 10,
          }}
        >
          <span
            style={{
              ...overlayBtnStyle,
              cursor: 'default',
              color: '#6b7280',
              fontSize: '10px',
            }}
          >
            Embed
          </span>
          <button
            type="button"
            onClick={(e) => {
              e.stopPropagation();
              handleDelete();
            }}
            style={{
              ...overlayBtnStyle,
              color: '#ef4444',
            }}
            title="Remove embed"
          >
            <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
              <line x1="3" y1="3" x2="11" y2="11" stroke="currentColor" strokeWidth="2" />
              <line x1="11" y1="3" x2="3" y2="11" stroke="currentColor" strokeWidth="2" />
            </svg>
          </button>
        </div>
      )}
    </div>
  );
}

// --- DOM conversion (HTML -> EmbedNode) ---
// Converts iframes found in imported HTML into EmbedNodes

function convertIframeElement(domNode: Node): DOMConversionOutput | null {
  if (domNode instanceof HTMLIFrameElement) {
    const html = domNode.outerHTML;
    const node = $createEmbedNode({ html });
    return { node };
  }
  return null;
}

// --- EmbedNode class ---

export class EmbedNode extends DecoratorNode<ReactElement> {
  __html: string;

  static getType(): string {
    return 'embed';
  }

  static clone(node: EmbedNode): EmbedNode {
    return new EmbedNode(node.__html, node.__key);
  }

  constructor(html: string, key?: NodeKey) {
    super(key);
    this.__html = html;
  }

  static importJSON(serializedNode: SerializedEmbedNode): EmbedNode {
    return $createEmbedNode({ html: serializedNode.html });
  }

  exportJSON(): SerializedEmbedNode {
    return {
      type: 'embed',
      version: 1,
      html: this.__html,
    };
  }

  exportDOM(): DOMExportOutput {
    const div = document.createElement('div');
    div.innerHTML = sanitizeHtmlRelaxed(this.__html);
    // Return the first child if it's an element (e.g., the iframe itself),
    // otherwise wrap in the div
    const firstChild = div.firstElementChild;
    if (firstChild) {
      return { element: firstChild as HTMLElement };
    }
    return { element: div };
  }

  static importDOM(): DOMConversionMap | null {
    return {
      iframe: () => ({
        conversion: convertIframeElement,
        priority: 0,
      }),
    };
  }

  createDOM(): HTMLElement {
    const div = document.createElement('div');
    return div;
  }

  updateDOM(): boolean {
    return false;
  }

  isInline(): boolean {
    return false;
  }

  decorate(): ReactElement {
    return <EmbedComponent html={this.__html} nodeKey={this.__key} />;
  }
}

// --- Helper functions ---

export function $createEmbedNode(payload: EmbedPayload): EmbedNode {
  return new EmbedNode(payload.html, payload.key);
}

export function $isEmbedNode(node: LexicalNode | null | undefined): node is EmbedNode {
  return node instanceof EmbedNode;
}

// --- Plugin (registers INSERT_EMBED_COMMAND handler) ---

export function EmbedPlugin(): null {
  const [editor] = useLexicalComposerContext();

  useEffect(() => {
    return editor.registerCommand<EmbedPayload>(
      INSERT_EMBED_COMMAND,
      (payload) => {
        const embedNode = $createEmbedNode(payload);
        const selection = $getSelection();
        if (selection) {
          $insertNodeToNearestRoot(embedNode);
        } else {
          $getRoot().append(embedNode);
        }
        return true;
      },
      COMMAND_PRIORITY_EDITOR
    );
  }, [editor]);

  return null;
}
