export function fileToBase64(file: File): Promise<string> {
  return blobToBase64(file);
}

export function blobToBase64(blob: Blob, fast = true): Promise<string> {
  return fast
    ? blob
        .arrayBuffer()
        .then((buffer) => new Uint8Array(buffer))
        .then((bytes) => {
          let binary = '';
          for (const byte of bytes) binary += String.fromCharCode(byte);
          return `data:${blob.type};base64,${btoa(binary)}`;
        })
    : new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result as string);
        reader.onerror = reject;
        reader.readAsDataURL(blob);
      });
}

export function blobToFile(blob: Blob, filename?: string): File {
  return new File([blob], filename ?? 'file', { type: blob.type });
}

export function base64ToBlob(base64: string): Blob {
  const [header, data] = base64.split(',');

  const mimeMatch = header.match(/:(.*?);/);
  const mime = mimeMatch ? mimeMatch[1] : '';

  const binary = atob(data);
  const len = binary.length;
  const buffer = new Uint8Array(len);

  for (let i = 0; i < len; i++) buffer[i] = binary.charCodeAt(i);

  return new Blob([buffer], { type: mime });
}

export function base64ToFile(base64: string, filename?: string): File {
  const blob = base64ToBlob(base64);
  return blobToFile(blob, filename);
}

export type TargetType = 'file' | 'blob' | 'base64';

type ConvertResult<T extends TargetType> = T extends 'file' ? File : T extends 'blob' ? Blob : T extends 'base64' ? string : never;

async function convert<T extends TargetType>(input: File | Blob | string, type: T, options?: { fast?: boolean; filename?: string }): Promise<ConvertResult<T>> {
  switch (type) {
    case 'blob': {
      if (typeof input === 'string') input = base64ToBlob(input);
      break;
    }
    case 'file': {
      if (input instanceof File) break;
      if (typeof input === 'string') input = base64ToFile(input, options?.filename);
      else input = blobToFile(input, options?.filename);
      break;
    }
    case 'base64': {
      if (typeof input === 'string') break;
      if (input instanceof File) input = await fileToBase64(input);
      else if (input instanceof Blob) input = await blobToBase64(input, options?.fast);
      break;
    }
    default:
      throw new Error('Unsupported type');
  }
  return input as ConvertResult<T>;
}

export default convert;
