import type {
  DOMConversionMap,
  DOMConversionOutput,
  DOMExportOutput,
  EditorConfig,
  LexicalNode,
  NodeKey,
  SerializedLexicalNode,
  Spread,
} from 'lexical';
import type { JSX } from 'react';

import {
  $applyNodeReplacement,
  DecoratorNode,
} from 'lexical';
import { Suspense, lazy } from 'react';

const ImageComponent = lazy(() => import('./ImageComponent'));

export interface ImagePayload {
  altText: string;
  height?: number;
  key?: NodeKey;
  maxWidth?: number;
  src: string;
  width?: number;
  fileKey?: string; // S3 file key
  uploadProgress?: number; // Upload progress (0-100)
  uploadError?: string; // Upload error message
}

function $convertImageElement(domNode: Node): null | DOMConversionOutput {
  const img = domNode as HTMLImageElement;
  const src = img.getAttribute('src');
  if (!src || src.startsWith('file:///')) {
    return null;
  }
  const { alt: altText, width, height } = img;
  const fileKey = img.getAttribute('data-file-key') || undefined;
  const node = $createImageNode({ altText, height, src, width, fileKey });
  return { node };
}

export type SerializedImageNode = Spread<
  {
    altText: string;
    height?: number;
    maxWidth: number;
    width?: number;
    fileKey?: string;
    // uploadProgress and uploadError are runtime-only, not serialized
  },
  SerializedLexicalNode
>;

export class ImageNode extends DecoratorNode<JSX.Element> {
  __src: string;
  __altText: string;
  __width: 'inherit' | number;
  __height: 'inherit' | number;
  __maxWidth: number;
  __fileKey?: string;
  __uploadProgress?: number;
  __uploadError?: string;

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

  static clone(node: ImageNode): ImageNode {
    return new ImageNode(
      node.__src,
      node.__altText,
      node.__maxWidth,
      node.__width,
      node.__height,
      node.__fileKey,
      node.__uploadProgress,
      node.__uploadError,
      node.__key,
    );
  }

  static importJSON(serializedNode: SerializedImageNode): ImageNode {
    const { altText, height, width, maxWidth, fileKey } = serializedNode;
    
    // Generate src from fileKey if available, otherwise use empty string
    const src = fileKey ? '' : ''; // src will be generated from fileKey when rendering
    
    return $createImageNode({
      altText,
      height,
      maxWidth,
      src,
      width,
      fileKey,
      // uploadProgress and uploadError are not imported - they're runtime-only
    });
  }

  exportDOM(): DOMExportOutput {
    const element = document.createElement('img');
    element.setAttribute('src', this.__src);
    element.setAttribute('alt', this.__altText);
    if (this.__fileKey) {
      element.setAttribute('data-file-key', this.__fileKey);
    }
    if (this.__width !== 'inherit') {
      element.setAttribute('width', this.__width.toString());
    }
    if (this.__height !== 'inherit') {
      element.setAttribute('height', this.__height.toString());
    }
    return { element };
  }

  static importDOM(): DOMConversionMap | null {
    return {
      img: () => ({
        conversion: $convertImageElement,
        priority: 0,
      }),
    };
  }

  constructor(
    src: string,
    altText: string,
    maxWidth: number,
    width?: 'inherit' | number,
    height?: 'inherit' | number,
    fileKey?: string,
    uploadProgress?: number,
    uploadError?: string,
    key?: NodeKey,
  ) {
    super(key);
    this.__src = src;
    this.__altText = altText;
    this.__maxWidth = maxWidth;
    this.__width = width || 'inherit';
    this.__height = height || 'inherit';
    this.__fileKey = fileKey;
    this.__uploadProgress = uploadProgress;
    this.__uploadError = uploadError;
  }

  exportJSON(): SerializedImageNode {
    return {
      altText: this.getAltText(),
      height: this.__height === 'inherit' ? 0 : this.__height,
      maxWidth: this.__maxWidth,
      // Don't export src - it's mutable and will be generated from fileKey
      type: 'image',
      version: 1,
      width: this.__width === 'inherit' ? 0 : this.__width,
      fileKey: this.__fileKey,
      // Don't export uploadProgress or uploadError - they're runtime-only UI state
    };
  }

  setWidthAndHeight(
    width: 'inherit' | number,
    height: 'inherit' | number,
  ): void {
    const writable = this.getWritable();
    writable.__width = width;
    writable.__height = height;
  }

  setUploadProgress(progress: number): void {
    const writable = this.getWritable();
    writable.__uploadProgress = progress;
  }

  setUploadError(error: string | undefined): void {
    const writable = this.getWritable();
    writable.__uploadError = error;
  }

  setFileKey(fileKey: string): void {
    const writable = this.getWritable();
    writable.__fileKey = fileKey;
  }

  setSrc(src: string): void {
    const writable = this.getWritable();
    writable.__src = src;
  }

  createDOM(config: EditorConfig): HTMLElement {
    const span = document.createElement('span');
    const theme = config.theme;
    const className = theme.image;
    if (className !== undefined) {
      span.className = className;
    }
    return span;
  }

  updateDOM(): false {
    return false;
  }

  getSrc(): string {
    return this.__src;
  }

  getAltText(): string {
    return this.__altText;
  }

  getFileKey(): string | undefined {
    return this.__fileKey;
  }

  getUploadProgress(): number | undefined {
    return this.__uploadProgress;
  }

  getUploadError(): string | undefined {
    return this.__uploadError;
  }

  decorate(): JSX.Element {
    return (
      <Suspense fallback={null}>
        <ImageComponent
          src={this.__src}
          altText={this.__altText}
          width={this.__width}
          height={this.__height}
          maxWidth={this.__maxWidth}
          nodeKey={this.getKey()}
          resizable={true}
          fileKey={this.__fileKey}
          uploadProgress={this.__uploadProgress}
          uploadError={this.__uploadError}
        />
      </Suspense>
    );
  }
}

export function $createImageNode({
  altText,
  height,
  maxWidth = 800,
  src,
  width,
  key,
  fileKey,
  uploadProgress,
  uploadError,
}: ImagePayload): ImageNode {
  return $applyNodeReplacement(
    new ImageNode(
      src,
      altText,
      maxWidth,
      width,
      height,
      fileKey,
      uploadProgress,
      uploadError,
      key,
    ),
  );
}

export function $isImageNode(
  node: LexicalNode | null | undefined,
): node is ImageNode {
  return node instanceof ImageNode;
}
