import type { NodeKey } from 'lexical';
import type { JSX } from 'react';

import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
import { mergeRegister } from '@lexical/utils';
import {
  $setSelection,
  CLICK_COMMAND,
  COMMAND_PRIORITY_LOW,
  KEY_ESCAPE_COMMAND,
  SELECTION_CHANGE_COMMAND,
} from 'lexical';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';

import { getDownloadPresignedURL } from '../api';
import type { DownloadPresignedURLResponse } from '../types';

function formatFileSize(bytes?: number): string | undefined {
  if (bytes === undefined) {
    return undefined;
  }
  if (bytes === 0) {
    return '0 B';
  }
  const units = ['B', 'KB', 'MB', 'GB', 'TB'];
  const order = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
  const size = bytes / Math.pow(1024, order);
  return `${size.toFixed(size >= 10 || order === 0 ? 0 : 1)} ${units[order]}`;
}

interface ArchiveComponentProps {
  nodeKey: NodeKey;
  fileName: string;
  fileSize?: number;
  fileKey?: string;
  uploadProgress?: number;
  uploadError?: string;
  uploadId?: string;
}

export default function ArchiveComponent({
  nodeKey,
  fileName,
  fileSize,
  fileKey,
  uploadProgress,
  uploadError,
}: ArchiveComponentProps): JSX.Element {
  const [editor] = useLexicalComposerContext();
  const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey);
  const containerRef = useRef<HTMLDivElement | null>(null);
  const [downloadInfo, setDownloadInfo] = useState<DownloadPresignedURLResponse | null>(null);
  const [isFetchingDownload, setIsFetchingDownload] = useState(false);
  const [downloadError, setDownloadError] = useState<string | undefined>(undefined);

  const readableSize = useMemo(() => formatFileSize(fileSize), [fileSize]);
  const isUploading = uploadProgress !== undefined && uploadProgress < 100 && !uploadError;
  const downloadButtonDisabled = !fileKey || isUploading || Boolean(uploadError) || isFetchingDownload;
  const downloadButtonLabel = useMemo(() => {
    if (!fileKey) {
      return 'Preparing link...';
    }
    if (uploadError) {
      return 'Unavailable';
    }
    if (isFetchingDownload) {
      return 'Preparing...';
    }
    return 'Download';
  }, [fileKey, isFetchingDownload, uploadError]);

  const handleEscape = useCallback(() => {
    if (!isSelected) {
      return false;
    }
    $setSelection(null);
    editor.update(() => {
      setSelected(true);
      editor.getRootElement()?.focus();
    });
    return true;
  }, [editor, isSelected, setSelected]);

  const handleClick = useCallback(
    (event: MouseEvent) => {
      if (event.target === null) {
        return false;
      }
      if (containerRef.current && containerRef.current.contains(event.target as Node)) {
        if (event.shiftKey) {
          setSelected(!isSelected);
        } else {
          clearSelection();
          setSelected(true);
        }
        return true;
      }
      return false;
    },
    [clearSelection, isSelected, setSelected],
  );

  useEffect(() => {
    return mergeRegister(
      editor.registerCommand(
        SELECTION_CHANGE_COMMAND,
        () => false,
        COMMAND_PRIORITY_LOW,
      ),
      editor.registerCommand(
        CLICK_COMMAND,
        handleClick,
        COMMAND_PRIORITY_LOW,
      ),
      editor.registerCommand(
        KEY_ESCAPE_COMMAND,
        handleEscape,
        COMMAND_PRIORITY_LOW,
      ),
    );
  }, [editor, handleClick, handleEscape]);

  useEffect(() => {
    setDownloadInfo(null);
    setDownloadError(undefined);
    setIsFetchingDownload(false);
  }, [fileKey]);

  const triggerDownload = useCallback(
    (url: string) => {
      const link = document.createElement('a');
      link.href = url;
      link.download = fileName;
      link.target = '_blank';
      link.rel = 'noopener noreferrer';
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    },
    [fileName],
  );

  const handleDownload = useCallback(async () => {
    if (!fileKey || isFetchingDownload) {
      return;
    }

    try {
      setIsFetchingDownload(true);
      setDownloadError(undefined);

      const isInfoValid = (info: DownloadPresignedURLResponse | null): boolean => {
        if (!info) {
          return false;
        }
        const expireAtMs = info.expireAt > 1_000_000_000_000 ? info.expireAt : info.expireAt * 1000;
        return Date.now() < expireAtMs - 5_000;
      };

      let info = downloadInfo;
      if (!isInfoValid(info)) {
        info = await getDownloadPresignedURL(fileKey);
        setDownloadInfo(info);
      }

      if (!info) {
        throw new Error('Download link unavailable');
      }

      triggerDownload(info.url);
    } catch (error) {
      setDownloadError(
        error instanceof Error ? error.message : 'Unable to prepare download link',
      );
    } finally {
      setIsFetchingDownload(false);
    }
  }, [downloadInfo, fileKey, isFetchingDownload, triggerDownload]);

  const borderColor = uploadError
    ? 'rgba(244, 67, 54, 0.6)'
    : isSelected
    ? 'var(--accent-color, #4CAF50)'
    : 'var(--border-color, #ccc)';

  return (
    <div
      ref={containerRef}
      className="editor-archive-card"
      style={{
        borderColor,
      }}
      tabIndex={0}
      onFocus={() => {
        clearSelection();
        setSelected(true);
      }}
    >
      <div className="editor-archive-icon" aria-hidden>
        ZIP
      </div>
      <div className="editor-archive-details">
        <div className="editor-archive-title">{fileName}</div>
        <div className="editor-archive-meta">Archive{readableSize ? ` • ${readableSize}` : ''}</div>
        <button
          type="button"
          className={`editor-archive-link${downloadButtonDisabled ? ' disabled' : ''}`}
          onClick={handleDownload}
          disabled={downloadButtonDisabled}
        >
          {downloadButtonLabel}
        </button>
        {isUploading && (
          <div className="editor-archive-progress">
            <div className="editor-archive-progress-bar">
              <div
                className="editor-archive-progress-value"
                style={{ width: `${uploadProgress || 0}%` }}
              />
            </div>
            <div className="editor-archive-progress-label">
              Uploading... {Math.round(uploadProgress || 0)}%
            </div>
          </div>
        )}
        {uploadError && (
          <div className="editor-archive-error">❌ {uploadError}</div>
        )}
        {downloadError && (
          <div className="editor-archive-error">❌ {downloadError}</div>
        )}
      </div>
    </div>
  );
}
