import * as mimeTypes from 'mime-types';
import Blob from '../pages/categories/blob';
import {getTextMetadata} from './text';
import {getImageMetadata} from './image';
import {getAudioMetadata} from './audio';
import {getVideoMetadata} from '@/utils/video';

const MIME_TYPE_DESCRIPTIONS = {
  'image/gif': 'GIF Image',
  'image/jpeg': 'JPEG Image',
  'image/png': 'PNG Image',
  'image/tiff': 'TIFF Image',
  'image/svg+xml': 'SVG Image',
  'audio/wav': 'WAVE Audio',
  'audio/ogg': 'OGG Audio',
  'audio/mpeg': 'MPEG Audio',
  'audio/mid': 'MIDI Track',
  'application/json': 'JSON Data',
  'application/xml': 'XML Data',
  'application/x-bittorrent': 'Torrent File',
  'application/x-zip-compressed': 'Compressed ZIP Archive',
  'application/x-x509-ca-cert': 'Certificate File',
  'application/x-tar': 'Compressed TAR Archive',
  'application/x-rar': 'Compressed RAR Archive',
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'Workbook',
  'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'Slideshow Presentation',
  'application/msword': 'Microsoft Word Document',
  'application/pdf': 'PDF Document',
  'application/postscript': 'PostScript Document',
  'application/epub+zip': 'EPUB Document',
  'message/rfc822': 'Email Message',
  'video/mp4': 'MP4 Video',
} as const;

const EXTENSION_DESCRIPTIONS = {
  'rar': 'Compressed RAR Archive',
  '7z': 'Compressed 7-Zip Archive',
  'psd': 'Adobe Photoshop Document',
  'dmg': 'Disk Image',
  'fb2k-component': 'foobar2000 Component',
} as const;

export const getMimeTypeDescription = (type?: string, filename?: string) => {
  if (typeof (type as unknown) !== 'string') {
    return '';
  }

  if (type === 'application/octet-stream' || type === '') {
    if (typeof filename === 'string' && filename.includes('.')) {
      const extension = filename.slice(filename.lastIndexOf('.') + '.'.length).toLowerCase();
      const {
        [extension as keyof typeof EXTENSION_DESCRIPTIONS]: extensionDescription = `${extension.toUpperCase()} File`,
      } = EXTENSION_DESCRIPTIONS;

      return extensionDescription;
    }
    return `${type} File`;
  }

  const {
    [type as keyof typeof MIME_TYPE_DESCRIPTIONS]: description = type,
  } = MIME_TYPE_DESCRIPTIONS;

  return description;
}

export enum ContentType {
  TEXT = 'text',
  AUDIO = 'audio',
  VIDEO = 'video',
  IMAGE = 'image',
  BINARY = 'binary',
}

export const getContentType = (mimeType?: string, filename?: string) => {
  let effectiveMimeType: string;
  if (typeof mimeType !== 'string') {
    if (typeof filename !== 'string') {
      return ContentType.BINARY;
    }
    const lookupMimeType = mimeTypes.lookup(filename);

    if (typeof lookupMimeType !== 'string') {
      return ContentType.BINARY;
    }

    effectiveMimeType = lookupMimeType;
  } else {
    effectiveMimeType = mimeType;
  }

  if (
    effectiveMimeType === 'application/json'
    || effectiveMimeType === 'application/xml'
    || effectiveMimeType.startsWith('text/')
  ) {
    return ContentType.TEXT;
  }

  if (effectiveMimeType.startsWith('video/')) {
    return ContentType.VIDEO;
  }

  if (effectiveMimeType.startsWith('audio/')) {
    return ContentType.AUDIO;
  }

  if (effectiveMimeType.startsWith('image/')) {
    return ContentType.IMAGE;
  }

  return ContentType.BINARY;
}

export const readAsText = (blob: Blob) => blob.text();

export const readAsDataURL = (blob: Blob) => new Promise<string>((resolve, reject) => {
  const reader = new FileReader();
  reader.addEventListener('error', () => {
    reject(new Error('Could not read file as data URL'));
  });

  reader.addEventListener('load', (e) => {
    if (!e.target) {
      reject();
      return;
    }
    resolve(e.target.result as string);
  });

  reader.readAsDataURL(blob);
});

export const readAsArrayBuffer = (blob: Blob) => blob.arrayBuffer();

interface FileWithResolvedType<T extends ContentType> extends Partial<File> {
  resolvedType: T;
  originalFile?: File;
}

export interface TextFileMetadata {
  contents?: string;
  scheme?: string;
  schemeTitle?: string;
  language?: string;
  languageProbability?: number;
}

export interface TextFile extends FileWithResolvedType<ContentType.TEXT> {
  metadata?: TextFileMetadata;
}

const augmentTextFile = async (f: File): Promise<TextFile> => {
  const contents = await readAsText(f);
  const metadata = getTextMetadata(contents, f.name) as TextFileMetadata;
  return {
    ...f,
    name: f.name,
    type: f.type,
    size: f.size,
    lastModified: f.lastModified,
    resolvedType: ContentType.TEXT,
    originalFile: f,
    metadata: {
      contents,
      language: metadata.language,
      languageProbability: metadata.languageProbability,
      scheme: metadata.scheme,
      schemeTitle: metadata.schemeTitle,
    },
  };
};

export interface ImageFileMetadata {
  previewUrl?: string;
  width?: number;
  height?: number;
}

export interface ImageFile extends FileWithResolvedType<ContentType.IMAGE> {
  metadata?: ImageFileMetadata;
}

export const augmentImageFile = async (f: File): Promise<ImageFile> => {
  const previewUrl = await readAsDataURL(f);
  const imageMetadata = await getImageMetadata(previewUrl) as ImageFileMetadata;
  return {
    name: f.name,
    type: f.type,
    size: f.size,
    lastModified: f.lastModified,
    resolvedType: ContentType.IMAGE,
    originalFile: f,
    metadata: {
      previewUrl,
      width: imageMetadata.width,
      height: imageMetadata.height,
    },
  };
};

export interface AudioFileMetadata {
  previewUrl?: string;
  duration?: number;
}

export interface AudioFile extends FileWithResolvedType<ContentType.AUDIO> {
  metadata?: AudioFileMetadata;
}

export const augmentAudioFile = async (f: File): Promise<AudioFile> => {
  const previewUrl = await readAsDataURL(f);
  const audioExtensions = await getAudioMetadata(previewUrl, f.type) as AudioFileMetadata;
  return {
    name: f.name,
    type: f.type,
    size: f.size,
    lastModified: f.lastModified,
    resolvedType: ContentType.AUDIO,
    originalFile: f,
    metadata: {
      previewUrl,
      duration: audioExtensions.duration,
    },
  };
};

export interface BinaryFileMetadata {
  contents: ArrayBuffer;
}

export interface BinaryFile extends FileWithResolvedType<ContentType.BINARY> {
  metadata?: BinaryFileMetadata;
}

const augmentBinaryFile = async (f: File): Promise<BinaryFile> => {
  const arrayBuffer = await readAsArrayBuffer(f);
  return {
    name: f.name,
    type: f.type,
    size: f.size,
    lastModified: f.lastModified,
    resolvedType: ContentType.BINARY,
    originalFile: f,
    metadata: {
      contents: arrayBuffer,
    },
  }
};

export interface VideoFileMetadata {
  previewUrl?: string;
  width?: number;
  height?: number;
}

export interface VideoFile extends FileWithResolvedType<ContentType.VIDEO> {
  metadata?: VideoFileMetadata;
}

export const augmentVideoFile = async (f: File): Promise<VideoFile> => {
  const previewUrl = await readAsDataURL(f);
  const videoMetadata = await getVideoMetadata(previewUrl);
  return {
    name: f.name,
    type: f.type,
    size: f.size,
    lastModified: f.lastModified,
    resolvedType: ContentType.VIDEO,
    originalFile: f,
    metadata: {
      previewUrl,
      width: videoMetadata.width as number,
      height: videoMetadata.height as number,
    },
  };
};

export type AugmentedFile = TextFile | ImageFile | AudioFile | VideoFile | BinaryFile;

export const augmentFile = async (f: File): Promise<AugmentedFile> => {
  const contentType = getContentType(f.type, f.name);
  switch (contentType) {
    case ContentType.TEXT:
      return augmentTextFile(f);
    case ContentType.IMAGE:
      return augmentImageFile(f);
    case ContentType.AUDIO:
      return augmentAudioFile(f);
    case ContentType.VIDEO:
      return augmentVideoFile(f);
    default:
      break;
  }
  return augmentBinaryFile(f);
};
