import type { Media as MediaV1, Maybe } from '@nacelle/types';
import type { Media as MediaV2 } from 'storefrontSdkV1';
import type { ContentfulEntry, SourceName } from '.';

export type ContentfulMedia = ContentfulEntry & {
  fields: {
    description?: string;
    file: {
      url: string;
      details: object;
      fileName: string;
      contentType: string;
    };
  };
};

export interface SanityMedia {
  _type: 'image' | 'video';
  asset: {
    _createdAt: string;
    _id: string;
    _rev: string;
    _type: string;
    _updatedAt: string;
    _depth?: number;
    assetId: string;
    extension: string;
    metadata?: {
      _type: string;
      palette?: Record<string, string | number>;
    };
    mimeType: string;
    originalFilename: string;
    path: string;
    sha1hash: string;
    size: number;
    uploadId: string;
    url: string;
  };
}

export const isContentfulMedia = (x: unknown): x is ContentfulMedia =>
  typeof (x as ContentfulMedia)?.sys === 'object' &&
  (x as ContentfulMedia).sys.type === 'Asset';

export const isSanityMedia = (x: unknown): x is SanityMedia =>
  typeof (x as SanityMedia)?._type === 'string' &&
  typeof (x as SanityMedia)?.asset === 'object';

export function flattenContentfulMedia(entry: ContentfulMedia): MediaV2 {
  const { file = { url: '', contentType: '' }, description = '' } =
    entry.fields;
  const { url = '', contentType = '' } = file;
  const { id = '' } = entry.sys;

  return {
    id,
    src: url,
    thumbnailSrc: url,
    type: contentType,
    altText: description
  };
}

export function flattenSanityMedia(media: SanityMedia): MediaV2 {
  const {
    asset: { assetId, mimeType, url },
    _type
  } = media;

  let thumbnailSrc = url;

  if (_type === 'image') {
    // In the v1 Sanity connector, `?w=100` was added to `url` to create `thumbnailSrc`.
    // No such transformation was made in the v1 Contentful connector.
    try {
      const imageUrl = new URL(url);

      if (!imageUrl.searchParams.has('w')) {
        imageUrl.searchParams.append('w', '100');
        thumbnailSrc = imageUrl.toString();
      }
    } catch {
      // Do nothing
    }
  }

  return {
    id: assetId,
    src: url,
    thumbnailSrc,
    type: mimeType,
    altText: ''
  };
}

export interface UnformattedMedia {
  [key: string]: string | null;
}

function getImageMimeType(extension: string): string {
  // Extensions for which we will make an effort to infer a MIME type
  const commonImageExtensions = [
    'jpg',
    'jpeg',
    'png',
    'gif',
    'svg',
    'webp',
    'avif'
  ];
  const isSupportedExtension = commonImageExtensions.indexOf(extension) !== -1;

  if (!isSupportedExtension) {
    return '';
  }

  switch (extension) {
    case 'jpg': {
      return 'image/jpeg';
    }
    case 'svg': {
      return 'image/svg+xml';
    }
    default: {
      // this works for all other commonImageExtensions
      return 'image/' + extension;
    }
  }
}

function getVideoMimeType(extension: string): string {
  // Extensions for which we will make an effort to infer a MIME type
  const commonVideoExtensions = ['mp4', 'mov', 'mpeg', 'webm'];
  const isSupportedExtension = commonVideoExtensions.indexOf(extension) !== -1;

  if (!isSupportedExtension) {
    return '';
  }

  if (extension === 'mov') {
    return 'video/quicktime';
  }

  // this works for all non-'mov' commonVideoExtensions
  return 'video/' + extension;
}

/**
 * Reshapes CMS-sources media assets to a consistent format.
 * @param mediaInput
 * @returns - either original input if not Contentful or Sanity media, or a flattened version of the media that makes the necessary fields more top-level/readily available
 */
function formatMedia(
  mediaInput: Maybe<MediaV2> | UnformattedMedia,
  cms?: SourceName
): Maybe<MediaV2> | UnformattedMedia {
  if (!cms || !mediaInput) {
    return mediaInput;
  }

  if (cms === 'CONTENTFUL' && isContentfulMedia(mediaInput)) {
    return flattenContentfulMedia(mediaInput) as MediaV2 | UnformattedMedia;
  } else if (cms === 'SANITY' && isSanityMedia(mediaInput)) {
    return flattenSanityMedia(mediaInput) as MediaV2 | UnformattedMedia;
  } else {
    return mediaInput;
  }
}

export function transformMedia(
  mediaInput: Maybe<MediaV2> | UnformattedMedia,
  cms?: SourceName
): Maybe<MediaV1> {
  if (
    !mediaInput ||
    typeof mediaInput !== 'object' ||
    Array.isArray(mediaInput)
  ) {
    return null;
  }

  const newMedia: MediaV1 = {
    altText: null,
    id: null,
    src: '',
    thumbnailSrc: '',
    type: ''
  };
  // because CMSs have complex file structures for media, we need to flatten media objects before transforming them
  const formattedMedia = (formatMedia(mediaInput, cms) as MediaV2) ?? {};

  for (const [key, value] of Object.entries(formattedMedia)) {
    if (value) {
      switch (key) {
        case 'id': {
          if (typeof value !== 'object') {
            newMedia.id = value;
          }
          break;
        }
        case 'type': {
          if (typeof value === 'string') {
            newMedia.type = value.toLowerCase();
          }
          break;
        }
        default: {
          if (typeof value === 'string') {
            newMedia[key as keyof MediaV1] = value;
          }
        }
      }
    }
  }

  if (!newMedia.thumbnailSrc) {
    newMedia.thumbnailSrc = newMedia.src;
  }

  // Attempt to infer the MIME type
  if (!newMedia.type) {
    let extension = '';

    try {
      const mediaUrl = new URL(newMedia.src);
      extension = mediaUrl.pathname.split('.').pop() as string;
    } catch (e) {
      // Do nothing
    }

    if (extension) {
      const imageExtension = getImageMimeType(extension);
      const videoExtension = getVideoMimeType(extension);
      const mimeType = imageExtension || videoExtension;

      if (mimeType) {
        newMedia.type = mimeType;
      }
    }
  }

  return newMedia;
}
