import React, { MutableRefObject, useCallback, useRef, useState } from 'react';
import { useAtom } from 'jotai';
import { IContent, MatrixClient } from 'matrix-js-sdk';
import { Box, IconButton, Scroll } from 'folds';
import { Icon, Icons } from '../../components/icons';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useFilePicker } from '../../hooks/useFilePicker';
import { safeFile } from '../../utils/mimeTypes';
import { fulfilledPromiseSettledResult } from '../../utils/common';
import {
  TUploadContent,
  TUploadItem,
  TUploadMetadata,
  roomIdToUploadItemsAtomFamily,
  roomUploadAtomFamily,
} from '../../state/room/roomInputDrafts';
import {
  Upload,
  UploadStatus,
  UploadSuccess,
  createUploadFamilyObserverAtom,
} from '../../state/upload';
import { UploadCardRenderer } from '../../components/upload-card';
import {
  UploadBoard,
  UploadBoardContent,
  UploadBoardHeader,
  UploadBoardImperativeHandlers,
} from '../../components/upload-board';
import {
  getAudioMsgContent,
  getFileMsgContent,
  getImageMsgContent,
  getVideoMsgContent,
} from '../room/msgContent';

export async function buildContentsFromUploads(
  mx: MatrixClient,
  items: TUploadItem[],
  uploads: UploadSuccess[]
): Promise<IContent[]> {
  const contentsPromises = uploads.map(async (upload) => {
    const fileItem = items.find((f) => f.file === upload.file);
    if (!fileItem) throw new Error('Broken upload');

    if (fileItem.file.type.startsWith('image')) {
      return getImageMsgContent(mx, fileItem, upload.mxc);
    }
    if (fileItem.file.type.startsWith('video')) {
      return getVideoMsgContent(mx, fileItem, upload.mxc);
    }
    if (fileItem.file.type.startsWith('audio')) {
      return getAudioMsgContent(fileItem, upload.mxc);
    }
    return getFileMsgContent(fileItem, upload.mxc);
  });
  return fulfilledPromiseSettledResult(await Promise.allSettled(contentsPromises));
}

type ForumPostUploadFieldProps = {
  roomId: string;
  disabled?: boolean;
  uploadBoardHandlers: MutableRefObject<UploadBoardImperativeHandlers | undefined>;
  onUploadsReady: (uploads: UploadSuccess[]) => Promise<void>;
};

export function ForumPostUploadField({
  roomId,
  disabled,
  uploadBoardHandlers,
  onUploadsReady,
}: ForumPostUploadFieldProps) {
  const mx = useMatrixClient();
  const [selectedFiles, setSelectedFiles] = useAtom(roomIdToUploadItemsAtomFamily(roomId));
  const [uploadBoard, setUploadBoard] = useState(true);
  const uploadFamilyObserverAtom = createUploadFamilyObserverAtom(
    roomUploadAtomFamily,
    selectedFiles.map((f) => f.file)
  );

  const handleFiles = useCallback(
    (files: File[]) => {
      if (disabled) return;
      const safe = files.map(safeFile).filter((f): f is File => f !== null);
      if (safe.length === 0) return;
      setSelectedFiles({
        type: 'PUT',
        item: safe.map((file) => ({
          file,
          originalFile: file,
          metadata: { markedAsSpoiler: false } satisfies TUploadMetadata,
          encInfo: undefined,
        })),
      });
    },
    [disabled, setSelectedFiles]
  );

  const pickFiles = useFilePicker(handleFiles, true);

  const handleFileMetadata = useCallback(
    (fileItem: TUploadItem, metadata: TUploadMetadata) => {
      setSelectedFiles({
        type: 'REPLACE',
        item: fileItem,
        replacement: { ...fileItem, metadata },
      });
    },
    [setSelectedFiles]
  );

  const handleRemoveUpload = useCallback(
    (upload: TUploadContent | TUploadContent[]) => {
      const uploads = Array.isArray(upload) ? upload : [upload];
      setSelectedFiles({
        type: 'DELETE',
        item: selectedFiles.filter((f) => uploads.find((u) => u === f.file)),
      });
      uploads.forEach((u) => roomUploadAtomFamily.remove(u));
    },
    [selectedFiles, setSelectedFiles]
  );

  const handleCancelUpload = (uploads: Upload[]) => {
    uploads.forEach((upload) => {
      if (upload.status === UploadStatus.Loading) {
        mx.cancelUpload(upload.promise);
      }
    });
    handleRemoveUpload(uploads.map((upload) => upload.file));
  };

  if (selectedFiles.length === 0) {
    return (
      <IconButton
        type="button"
        variant="SurfaceVariant"
        size="300"
        radii="300"
        onClick={pickFiles}
        disabled={disabled}
        aria-label="Attach files"
      >
        <Icon src={Icons.Attachment} size="200" />
      </IconButton>
    );
  }

  return (
    <Box direction="Column" gap="200">
      <Box direction="Row" gap="200" alignItems="Center">
        <IconButton
          type="button"
          variant="SurfaceVariant"
          size="300"
          radii="300"
          onClick={pickFiles}
          disabled={disabled}
          aria-label="Attach more files"
        >
          <Icon src={Icons.Attachment} size="200" />
        </IconButton>
      </Box>
      <UploadBoard
        header={
          <UploadBoardHeader
            open={uploadBoard}
            onToggle={() => setUploadBoard(!uploadBoard)}
            uploadFamilyObserverAtom={uploadFamilyObserverAtom}
            onSend={onUploadsReady}
            imperativeHandlerRef={uploadBoardHandlers}
            onCancel={handleCancelUpload}
          />
        }
      >
        {uploadBoard && (
          <Scroll size="300" hideTrack visibility="Hover">
            <UploadBoardContent>
              {Array.from(selectedFiles)
                .reverse()
                .map((fileItem, index) => (
                  <UploadCardRenderer
                    // eslint-disable-next-line react/no-array-index-key
                    key={index}
                    isEncrypted={!!fileItem.encInfo}
                    fileItem={fileItem}
                    setMetadata={handleFileMetadata}
                    onRemove={handleRemoveUpload}
                  />
                ))}
            </UploadBoardContent>
          </Scroll>
        )}
      </UploadBoard>
    </Box>
  );
}

export function useForumPostUploads(roomId: string) {
  const mx = useMatrixClient();
  const [selectedFiles, setSelectedFiles] = useAtom(roomIdToUploadItemsAtomFamily(roomId));
  const uploadBoardHandlers = useRef<UploadBoardImperativeHandlers>();
  const uploadResolverRef = useRef<{
    resolve: (contents: IContent[]) => void;
    reject: (err: unknown) => void;
  } | null>(null);

  const clearUploads = useCallback(() => {
    if (selectedFiles.length > 0) {
      setSelectedFiles({ type: 'DELETE', item: selectedFiles });
      selectedFiles.forEach((item) => roomUploadAtomFamily.remove(item.file));
    }
  }, [selectedFiles, setSelectedFiles]);

  const handleUploadsReady = useCallback(
    async (uploads: UploadSuccess[]) => {
      try {
        const contents = await buildContentsFromUploads(mx, selectedFiles, uploads);
        uploads.forEach((upload) => roomUploadAtomFamily.remove(upload.file));
        if (selectedFiles.length > 0) {
          setSelectedFiles({ type: 'DELETE', item: selectedFiles });
        }
        uploadResolverRef.current?.resolve(contents);
      } catch (err) {
        uploadResolverRef.current?.reject(err);
      } finally {
        uploadResolverRef.current = null;
      }
    },
    [mx, selectedFiles, setSelectedFiles]
  );

  const collectUploadContents = useCallback(async (): Promise<IContent[]> => {
    if (selectedFiles.length === 0) return [];
    return new Promise<IContent[]>((resolve, reject) => {
      uploadResolverRef.current = { resolve, reject };
      uploadBoardHandlers.current?.handleSend().catch(reject);
    });
  }, [selectedFiles.length]);

  return {
    selectedFiles,
    uploadBoardHandlers,
    handleUploadsReady,
    collectUploadContents,
    clearUploads,
  };
}
