// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import MP4Box from 'mp4box';
import { VIDEO_MP4, isVideo } from '../types/MIME.std.ts';
import {
  getRenderDetailsForLimit,
  isAttachmentTooLargeToSend,
} from '../types/AttachmentSize.std.ts';
import { explodePromise } from './explodePromise.std.ts';

const MAX_VIDEO_DURATION_IN_SEC = 30;

type MP4ArrayBuffer = ArrayBuffer & { fileStart: number };

export enum ReasonVideoNotGood {
  AllGoodNevermind = 'AllGoodNevermind',
  CouldNotReadFile = 'CouldNotReadFile',
  TooLong = 'TooLong',
  TooBig = 'TooBig',
  UnsupportedCodec = 'UnsupportedCodec',
  UnsupportedContainer = 'UnsupportedContainer',
}

function createMp4ArrayBuffer(src: ArrayBuffer): MP4ArrayBuffer {
  const arrayBuffer = new ArrayBuffer(src.byteLength);
  new Uint8Array(arrayBuffer).set(new Uint8Array(src));

  (arrayBuffer as MP4ArrayBuffer).fileStart = 0;
  return arrayBuffer as MP4ArrayBuffer;
}

export type IsVideoGoodForStoriesResultType = Readonly<
  | {
      reason: Exclude<
        ReasonVideoNotGood,
        ReasonVideoNotGood.TooLong | ReasonVideoNotGood.TooBig
      >;
    }
  | {
      reason: ReasonVideoNotGood.TooLong;
      maxDurationInSec: number;
    }
  | {
      reason: ReasonVideoNotGood.TooBig;
      renderDetails: ReturnType<typeof getRenderDetailsForLimit>;
    }
>;

export type IsVideoGoodForStoriesOptionsType = Readonly<{
  maxAttachmentVideoSize: number;
}>;

export async function isVideoGoodForStories(
  file: File,
  { maxAttachmentVideoSize }: IsVideoGoodForStoriesOptionsType
): Promise<IsVideoGoodForStoriesResultType> {
  if (!isVideo(file.type)) {
    return { reason: ReasonVideoNotGood.AllGoodNevermind };
  }

  if (file.type !== VIDEO_MP4) {
    return { reason: ReasonVideoNotGood.UnsupportedContainer };
  }

  let src: ArrayBuffer;

  {
    const { promise, resolve } = explodePromise<ArrayBuffer | undefined>();

    // oxlint-disable-next-line no-undef FIXME
    const reader = new FileReader();
    reader.onload = () => {
      if (reader.result) {
        resolve(reader.result as ArrayBuffer);
      } else {
        resolve(undefined);
      }
    };
    reader.readAsArrayBuffer(file);

    const maybeSrc = await promise;
    if (maybeSrc === undefined) {
      return { reason: ReasonVideoNotGood.CouldNotReadFile };
    }

    src = maybeSrc;
  }

  if (
    isAttachmentTooLargeToSend({
      plaintextSize: src.byteLength,
      limit: maxAttachmentVideoSize,
    })
  ) {
    return {
      reason: ReasonVideoNotGood.TooBig,
      renderDetails: getRenderDetailsForLimit(maxAttachmentVideoSize),
    };
  }

  const arrayBuffer = createMp4ArrayBuffer(src);

  const { promise, resolve } =
    explodePromise<IsVideoGoodForStoriesResultType>();

  const mp4 = MP4Box.createFile();
  mp4.onReady = info => {
    // mp4box returns a `duration` in `timescale` units
    const seconds = info.duration / info.timescale;

    if (seconds > MAX_VIDEO_DURATION_IN_SEC) {
      resolve({
        reason: ReasonVideoNotGood.TooLong,
        maxDurationInSec: MAX_VIDEO_DURATION_IN_SEC,
      });
      return;
    }

    const codecs = /codecs="([\w,.]+)"/.exec(info.mime);
    if (!codecs || !codecs[1]) {
      resolve({ reason: ReasonVideoNotGood.UnsupportedCodec });
      return;
    }

    const isH264 = codecs[1].split(',').some(codec => codec.startsWith('avc1'));

    if (!isH264) {
      resolve({ reason: ReasonVideoNotGood.UnsupportedCodec });
      return;
    }

    resolve({ reason: ReasonVideoNotGood.AllGoodNevermind });
  };
  mp4.appendBuffer(arrayBuffer);
  try {
    return await promise;
  } finally {
    mp4.flush();
  }
}
