import {CreateTrackPayload} from '@app/admin/tracks-datatable-page/requests/use-create-track';
import {
  ExtractedTrackMetadata,
  useExtractTackFileMetadata,
} from '@app/admin/tracks-datatable-page/requests/use-extract-track-file-metadata';
import {UploadType} from '@app/site-config';
import {generateWaveformData} from '@app/web-player/tracks/waveform/generate-waveform-data';
import {FileEntry} from '@common/uploads/file-entry';
import {restrictionsFromConfig} from '@common/uploads/uploader/create-file-upload';
import {useFileUploadStore} from '@common/uploads/uploader/file-upload-provider';
import {UploadStrategyConfig} from '@common/uploads/uploader/strategy/upload-strategy';
import {message} from '@ui/i18n/message';
import {toast} from '@ui/toast/toast';
import {openUploadWindow} from '@ui/utils/files/open-upload-window';
import {UploadedFile} from '@ui/utils/files/uploaded-file';
import {validateFile} from '@ui/utils/files/validate-file';
import {useCallback, useMemo, useRef} from 'react';

const FiftyMB = 50 * 1024 * 1024;

export interface TrackUploadMeta {
  isGeneratingWave?: boolean;
  isExtractingMetadata?: boolean;
}

export type TrackUploadPayload = CreateTrackPayload & {uploadId: string};

interface Options {
  autoMatchAlbum?: boolean;
  onUploadStart?: (data: TrackUploadPayload) => void;
  onMetadataChange: (
    file: UploadedFile,
    newData: ExtractedTrackMetadata & {waveData?: number[][]},
  ) => void;
}
export function useTrackUploader(options: Options) {
  const extractMetadata = useExtractTackFileMetadata();
  const optionsRef = useRef<Options>(options);
  optionsRef.current = options;

  const uploadMultiple = useFileUploadStore(s => s.uploadMultiple);
  const updateFileUpload = useFileUploadStore(s => s.updateFileUpload);
  const getUpload = useFileUploadStore(s => s.getUpload);
  const updateUpload = useCallback(
    (uploadId: string, newMeta: TrackUploadMeta) => {
      updateFileUpload(uploadId, {
        meta: {
          // @ts-ignore
          ...(getUpload(uploadId)?.meta || {}),
          ...newMeta,
        },
      });
    },
    [updateFileUpload, getUpload],
  );

  const restrictions = restrictionsFromConfig({uploadType: UploadType.media});
  const uploadOptions: UploadStrategyConfig = useMemo(() => {
    return {
      restrictions,
      uploadType: UploadType.media,
      onSuccess: (entry: FileEntry, file) => {
        updateUpload(file.id, {isExtractingMetadata: true});
        extractMetadata.mutate(
          {
            fileEntryId: entry.id,
            autoMatchAlbum: optionsRef.current.autoMatchAlbum,
          },
          {
            onSuccess: formValues => {
              updateUpload(file.id, {isExtractingMetadata: false});
              const newValues: ExtractedTrackMetadata = {
                ...formValues,
                src: entry.url,
              };
              optionsRef.current.onMetadataChange(file, newValues);
            },
            onError: () => {
              updateUpload(file.id, {isExtractingMetadata: false});
            },
          },
        );
      },
      onError: message => {
        if (message) {
          toast.danger(message);
        }
      },
    };
  }, [extractMetadata, updateUpload, restrictions]);

  const validateUploads = useCallback(
    (files: UploadedFile[]) => {
      const validFiles = files.filter(
        file => !validateFile(file, restrictions),
      );
      // show error message, if some files did not pass validation
      if (files.length !== validFiles.length) {
        toast.danger(
          message(':count of your files is not supported.', {
            values: {count: files.length - validFiles.length},
          }),
        );
      }
      return validFiles;
    },
    [restrictions],
  );

  const uploadTracks = useCallback(
    async (files: UploadedFile[]) => {
      const validFiles = validateUploads(files);
      if (!validFiles.length) return;
      files.forEach(file => {
        optionsRef.current.onUploadStart?.({
          name: file.name,
          uploadId: file.id,
        });
      });
      uploadMultiple(files, uploadOptions);
      for (const file of files) {
        updateUpload(file.id, {isGeneratingWave: true});
        const waveData = await generateWaveformData(file.native);
        if (waveData) {
          optionsRef.current.onMetadataChange(file, {waveData});
        }
        updateUpload(file.id, {isGeneratingWave: false});
      }
    },
    [uploadOptions, uploadMultiple, updateUpload, validateUploads],
  );

  const openFilePicker = useCallback(async () => {
    const files = await openUploadWindow({
      multiple: true,
      types: restrictions?.allowedFileTypes,
    });
    await uploadTracks(files);
  }, [uploadTracks, restrictions]);

  return {openFilePicker, uploadTracks, validateUploads};
}
