import { format } from 'date-fns';
import i18next from 'i18next';
import {
  type FileAsset,
  type Attachment as HuAttachment,
} from '@material-hu/types/attachments';

import {
  MAX_HEIGHTS,
  MAX_HEIGHTS_POSTS,
  PLAYBACKRATE,
} from 'src/constants/attachments';
import { MAX_ATTACHMENTS_B } from 'src/constants/posts';
import { getDimensions, getVideoMetadata } from 'src/services/attachments';
import {
  type Attachment,
  type AttachmentsSplit,
  FileTypes,
  type FormFile,
} from 'src/types/attachments';
import { sizeToBytes } from 'src/utils/bytes';
import { sortByPosition } from 'src/utils/position';

import { getUuid, removeUUID } from './text';

export const splitByTypes = (attachments: Attachment[]): AttachmentsSplit => {
  const videos: Attachment[] = [];
  const images: Attachment[] = [];
  const files: Attachment[] = [];

  attachments?.forEach(attachment => {
    switch (attachment.type) {
      case FileTypes.VIDEO:
        videos.push(attachment);
        break;
      case FileTypes.IMAGE:
        images.push(attachment);
        break;
      default:
        files.push(attachment);
    }
  });

  return {
    videos,
    images,
    files,
  };
};

// return: total size of files in bytes.
export const getFilesTotalSize = (files: FormFile[]): number => {
  if (!files) return 0;

  return files.reduce((count, current) => count + (current.size || 0), 0);
};

export const isValidFilesSize = (
  files: FormFile[],
  maxBytes: number = MAX_ATTACHMENTS_B,
): boolean => {
  const bytes = getFilesTotalSize(files);

  return bytes <= maxBytes;
};

// return: total size of attachments in bytes.
export const getAttachmentsTotalSize = (attachments: Attachment[]): number => {
  if (!attachments) return 0;

  return attachments.reduce(
    (count, current) => count + sizeToBytes(current.size || '0'),
    0,
  );
};

export const isValidVideosSize = (
  video: FormFile,
  maxAttachmentB: number = MAX_ATTACHMENTS_B,
) => {
  const bytes = getFilesTotalSize([video]);

  return bytes <= maxAttachmentB;
};

export const formatToSecondsDurationAudio = (duration: number): number =>
  Math.floor(duration / 1000);

/**
 * KNOWN ISSUE / TECH DEBT:
 * The backend `Attachment` model does not currently have a dedicated `durationInMs`
 * field for audio attachments, so the duration is persisted in the `width` column
 * when uploading audios (see `uploadAudioFile` in `src/services/attachments.ts`).
 *
 * This helper centralizes that workaround so every consumer reads the audio
 * duration through a single, explicit API instead of accessing `attachment.width`
 * directly. Once the backend exposes a proper `durationInMs` / `metadata` field,
 * only this function (and the upload service) need to be updated.
 *
 * TODO: Replace with `attachment.metadata?.durationInMs` when backend support lands.
 */
export const getAudioDurationMs = (
  attachment: Attachment,
): number | undefined => attachment.width ?? undefined;

export const formatAudioTime = (time: number): string => {
  const date = new Date(0);
  date.setSeconds(time);

  return format(date, 'm:ss');
};

export const formatAudioSliderValue = (value: number, max: number): string => {
  const valueText = formatAudioTime(value);
  const maxText = formatAudioTime(max);

  return `${valueText}/${maxText}`;
};

export const getDownloadAudioName = () => {
  const date = format(new Date(), 'yyyy-MM-dd_HH-mm-ss');
  const title = 'humand_audio_';
  return title + date;
};

export const getAudioButtonLabel = (
  isLoading = false,
  isPlaying = false,
): string => {
  if (isLoading) {
    return i18next.t('loading');
  }

  if (isPlaying) {
    return i18next.t('pause');
  }

  return i18next.t('play');
};

export const hasFileToPaste = (
  event: React.ClipboardEvent<HTMLInputElement> | ClipboardEvent,
) => {
  if (!event?.clipboardData?.items) return false;

  return Array.from(event.clipboardData.items).some(
    item => item.kind === 'file',
  );
};

export const hasRichTextToPaste = (
  event: React.ClipboardEvent<HTMLInputElement> | ClipboardEvent,
) => {
  if (!event?.clipboardData?.items) return false;

  return Array.from(event.clipboardData.items).some(item => {
    return item.type === 'text/rtf';
  });
};

export const processFile = async (file: File): Promise<FormFile> => {
  let type: FileTypes;
  let width: number | undefined;
  let height: number | undefined;

  if (file.type.includes('image/jpeg') || file.type.includes('image/png')) {
    type = FileTypes.IMAGE;

    try {
      const dimensions = await getDimensions(file);
      width = dimensions.width;
      height = dimensions.height;
    } catch (err) {
      width = undefined;
      height = undefined;
    }
  } else if (file.type.includes('video/')) {
    try {
      const metadata = await getVideoMetadata(file);
      width = metadata.width;
      height = metadata.height;
    } catch (err) {
      width = undefined;
      height = undefined;
    }
    type = FileTypes.VIDEO;
  } else if (file.type.includes('audio/')) {
    type = FileTypes.AUDIO;
  } else {
    type = FileTypes.FILE;
  }

  const newFile: FormFile = {
    name: `${file.name || 'file'}-${getUuid()}`,
    size: file.size,
    mime: file.type,
    width,
    height,
    fileObject: file,
    type,
    toUpload: true,
  };

  return newFile;
};

export const getAttachmentFromFileAsset = (file: FileAsset): HuAttachment => {
  return {
    name: removeUUID(file.name).split('-')[0],
    type: file.type as FileTypes,
    url: file.url,
    size: file.size,
    bytes: sizeToBytes(file.size),
  };
};

export const getFilesToPaste = async (
  event: React.ClipboardEvent<HTMLInputElement> | ClipboardEvent,
): Promise<FormFile[]> => {
  if (!event.clipboardData?.items) {
    return [];
  }

  const { items } = event.clipboardData;
  const filePromises: Promise<FormFile>[] = [];

  for (let i = 0; i < items.length; i++) {
    if (items[i].kind === 'file') {
      const file = items[i].getAsFile();
      if (file) {
        filePromises.push(processFile(file));
      }
    }
  }

  return Promise.all(filePromises);
};

export const getFileToPaste = async (
  event: React.ClipboardEvent<HTMLInputElement> | ClipboardEvent,
) => {
  const files = await getFilesToPaste(event);
  return files.length > 0 ? files[0] : null;
};

export const getMaxHeight = (mediaList: Attachment[]): number => {
  const maxMediaHeight = mediaList.reduce((maxHeight, media) => {
    let { height } = media;

    if (!height) {
      height =
        media.type === FileTypes.VIDEO ? MAX_HEIGHTS.VIDEO : MAX_HEIGHTS.IMAGE;
    }

    return Math.max(height, maxHeight);
  }, 0.0);

  return Math.min(maxMediaHeight, MAX_HEIGHTS.IMAGE);
};

export const getMaxHeightByContainer = (
  mediaList: Attachment[],
  parentContainerId?: string,
  mediaMaxHeight?: number,
) => {
  const postWidth = document.getElementById(
    parentContainerId || '',
  )?.offsetWidth;

  if (!postWidth) return 0;

  const height = mediaList.reduce((acc, media) => {
    const defaultHeight =
      media.type === FileTypes.VIDEO ? MAX_HEIGHTS.VIDEO : MAX_HEIGHTS.IMAGE;

    const mediaHeight = media.height || defaultHeight;

    const proportionalHeight =
      (postWidth * mediaHeight) / (media.width || postWidth);

    return Math.max(proportionalHeight, acc);
  }, 0);
  const maxHeightLimit =
    mediaMaxHeight ??
    (mediaList.some(media => media.type === FileTypes.VIDEO)
      ? MAX_HEIGHTS_POSTS.VIDEO
      : MAX_HEIGHTS_POSTS.IMAGE);

  return Math.min(height, maxHeightLimit);
};

export const getMediaHeight = (
  mediaList: Attachment[],
  parentContainerId?: string,
  mediaMaxHeight?: number,
): number =>
  parentContainerId
    ? getMaxHeightByContainer(mediaList, parentContainerId, mediaMaxHeight)
    : getMaxHeight(mediaList);

type ImageDimensionsArgs = {
  isMediaLoading?: boolean;
  zoom?: boolean;
  mediaWidth?: number | null;
  mediaHeight?: number | null;
  width?: number | null;
  height?: number | null;
  initialZoom?: boolean;
};

export const getImageDimensions = ({
  isMediaLoading = false,
  zoom = false,
  mediaWidth,
  mediaHeight,
  width,
  height,
  initialZoom = false,
}: ImageDimensionsArgs) => {
  const pickValid = (...values: Array<number | null | undefined>) => {
    for (const v of values) {
      if (typeof v === 'number' && v > 0) return v;
    }
    return undefined;
  };

  const baseWidth = pickValid(width, mediaWidth);
  const baseHeight = pickValid(height, mediaHeight);

  if (isMediaLoading) {
    return {
      imageWidth: 0,
      imageHeight: 0,
    };
  } else if (zoom) {
    return {
      imageWidth: baseWidth ? baseWidth * 2 : undefined,
      imageHeight: baseHeight ? baseHeight * 2 : undefined,
    };
  }
  return {
    imageWidth: initialZoom ? baseWidth : pickValid(mediaWidth, width),
    imageHeight: initialZoom ? baseHeight : pickValid(mediaHeight, height),
  };
};

export const getMaxHeightCardContent = (zoom: boolean) => {
  if (!zoom) {
    return '80vh';
  }
  return '96vh';
};

export const formatVideoTimer = (seconds: number): string => {
  if (!Number.isFinite(seconds) || seconds < 0) {
    return '00:00';
  }

  const totalSeconds = Math.floor(seconds);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const secs = totalSeconds % 60;

  const mm = minutes.toString().padStart(2, '0');
  const ss = secs.toString().padStart(2, '0');

  return hours > 0 ? `${hours}:${mm}:${ss}` : `${mm}:${ss}`;
};

export const getVideoElapsedTime = (
  video?: HTMLVideoElement | null,
  durationVideo?: number,
): string => {
  if (!video) return '00:00';

  const elapsed = Math.floor(video.currentTime);
  const duration = Math.floor(durationVideo || video.duration);

  return formatVideoTimer(duration - elapsed);
};

export const getVideoRemainingTime = (
  video?: HTMLVideoElement | null,
): string => {
  if (!video) return '00:00';

  return formatVideoTimer(video.currentTime);
};

export const isPlaying = (video: HTMLVideoElement) =>
  !!video &&
  video.currentTime > 0 &&
  !video.paused &&
  !video.ended &&
  video.readyState > video.HAVE_CURRENT_DATA;

export const getFileType = (file: File | undefined) => {
  if (!file) return FileTypes.FILE;

  const splittedType = file?.type?.split('/');
  const type = splittedType?.length > 0 ? splittedType[0] : null;

  if (type === 'image') {
    return FileTypes.IMAGE;
  } else if (type === 'video') {
    return FileTypes.VIDEO;
  } else if (type === 'audio') {
    return FileTypes.AUDIO;
  } else {
    return FileTypes.FILE;
  }
};

export const getCorrectPositionOfFile = (
  images: unknown[],
  videos: unknown[],
  index = 0,
) => {
  if (images.length === 0 && videos.length === 0) {
    return index;
  }

  return images.length + videos.length;
};

export const deleteFileAndReorderAttchaments = (
  images: any[],
  videos: any[],
  itemDelete: any,
) => {
  const newImages: any[] = [];
  const newVideos: any[] = [];

  const list = sortByPosition([...images, ...videos]);

  list.forEach(elem => {
    if (elem.position === itemDelete.position) return;

    if (elem.position > itemDelete.position) {
      elem.position--;
    }

    if (elem.type === FileTypes.IMAGE) {
      newImages.push(elem);
    } else if (elem.type === FileTypes.VIDEO) {
      newVideos.push(elem);
    }
  });
  return { newImages, newVideos };
};

export const getNewPlaybackRate = (currentPlaybackRate: number) => {
  if (currentPlaybackRate === PLAYBACKRATE.NORMAL) {
    return PLAYBACKRATE.ONE_AND_HALF_SPEED;
  } else if (currentPlaybackRate === PLAYBACKRATE.ONE_AND_HALF_SPEED) {
    return PLAYBACKRATE.DOUBLE_SPEED;
  }
  return PLAYBACKRATE.NORMAL;
};

export const fileSizeToMB = (size: number) => size / (1024 * 1024);

export const findImageIndex = (images: any[], imageToFind: any) =>
  images.findIndex(
    image =>
      image.name === imageToFind.name &&
      image.fileObject.lastModified === imageToFind.lastModified,
  );

export const processAttachment = async ({
  file,
  withUrl = false,
}: {
  file: any;
  withUrl?: boolean;
}) => {
  const type = getFileType(file);

  try {
    if (type === FileTypes.IMAGE) {
      const dimensions = await getDimensions(file);
      file.width = dimensions.width;
      file.height = dimensions.height;
    }
  } catch (err) {
    file.width = undefined;
    file.height = undefined;
  }

  const newFile: FormFile = {
    name: file?.name,
    size: file?.size,
    mime: file?.type,
    width: file?.width,
    height: file?.height,
    fileObject: file,
    type,
    toUpload: true,
    ...(withUrl && { url: URL.createObjectURL(file) }),
  };

  return newFile;
};

export const getFilesTotalSizeChatsV2 = (files: any[]): number => {
  if (!files) return 0;

  return files.reduce(
    (count, current) => count + (current?.image_bytes || current?.size),
    0,
  );
};

export const isValidFilesSizeChatsV2 = (
  files: any[],
  maxBytes: number = MAX_ATTACHMENTS_B,
): boolean => {
  const bytes = getFilesTotalSizeChatsV2(files);
  return bytes <= maxBytes;
};

export const splitFileName = (str: string) => {
  const extensionIndex = str.lastIndexOf('.');
  if (extensionIndex === -1) {
    return {
      name: str,
      extension: '',
    };
  }

  return {
    name: str.slice(0, extensionIndex),
    extension: str.slice(extensionIndex + 1),
  };
};

export const getFileExtension = (str: string) =>
  str.slice(str.lastIndexOf('.') + 1);
