import { pickRandomColor } from './colorUtils';
import fixClipLoop, { ClipLoop } from './fixClipLoop';
import fixWarp, { Warp } from './fixWarp';
import {
  fixOptionalBoolean,
  isBoolean,
  isNonNegativeFiniteNumber,
  isObject,
  isString,
  isValidFiniteNumber,
  makeFixer,
} from './utils';

export type StudioClip = {
  id: string;
  streaming: boolean;
  awaitingContentAlignment?: boolean;
  name: string;
  color: string;
  transposition: number;
  amplitude: number;
  startBeats: number;
  endBeats: number;
  readStartBeats: number;
  fadeInBeats: number;
  fadeOutBeats: number;
  mute: boolean;
  loop: ClipLoop;
  warp: Warp;
} & (
  | {
      clipId: string;
      uploadId: null;
    }
  | {
      clipId: null;
      uploadId: string;
    }
);

export const TRANSPOSE_MINIMUM_SEMITONES = -24;
export const TRANSPOSE_MAXIMUM_SEMITONES = 24;

const fixStreaming = makeFixer(isBoolean, false);
const fixMute = makeFixer(isBoolean, false);
const fixName = makeFixer(isString, 'Clip');
const fixAmplitude = makeFixer(isNonNegativeFiniteNumber, 1.0);
const fixFadeInBeats = makeFixer(isNonNegativeFiniteNumber, 0);
const fixFadeOutBeats = makeFixer(isNonNegativeFiniteNumber, 0);
const fixStartBeats = makeFixer(isValidFiniteNumber, 0);
const fixEndBeats = makeFixer(isValidFiniteNumber, 0);
const fixReadStartBeats = makeFixer(isValidFiniteNumber, 0);

const fixTransposition = (transposition: unknown): number => {
  if (!isValidFiniteNumber(transposition)) {
    return 0;
  }
  return Math.max(
    TRANSPOSE_MINIMUM_SEMITONES,
    Math.min(TRANSPOSE_MAXIMUM_SEMITONES, transposition)
  );
};

export default function fixClip(clip: unknown): StudioClip | null {
  if (!isObject(clip)) {
    return null;
  }

  const c = clip as {
    id: unknown;
    streaming: unknown;
    awaitingContentAlignment: unknown;
    name: unknown;
    color: unknown;
    transposition: unknown;
    amplitude: unknown;
    startBeats: unknown;
    endBeats: unknown;
    readStartBeats: unknown;
    fadeInBeats: unknown;
    fadeOutBeats: unknown;
    mute: unknown;
    loop: unknown;
    warp: unknown;
    clipId: unknown;
    uploadId: unknown;
  };

  // Validate that we have an id
  if (!isString(c.id)) {
    return null;
  }

  // Fix all fields
  c.streaming = fixStreaming(c.streaming);
  c.awaitingContentAlignment = fixOptionalBoolean(c.awaitingContentAlignment);

  c.name = fixName(c.name);
  c.color = isString(c.color) ? c.color : pickRandomColor(c.id as string);
  c.transposition = fixTransposition(c.transposition);
  c.amplitude = fixAmplitude(c.amplitude);
  c.startBeats = fixStartBeats(c.startBeats);
  c.endBeats = fixEndBeats(c.endBeats);
  c.readStartBeats = fixReadStartBeats(c.readStartBeats);
  c.fadeInBeats = fixFadeInBeats(c.fadeInBeats);
  c.fadeOutBeats = fixFadeOutBeats(c.fadeOutBeats);
  c.mute = fixMute(c.mute);

  c.loop = fixClipLoop(c.loop);
  c.warp = fixWarp(c.warp);

  // Validate endBeats > startBeats
  if ((c.endBeats as number) <= (c.startBeats as number)) {
    return null;
  }

  // Validate the discriminated union: must have either clipId OR uploadId
  const hasClipId = isString(c.clipId);
  const hasUploadId = isString(c.uploadId);

  if (hasClipId && hasUploadId) {
    // Both present - prefer clipId
    c.uploadId = null;
  } else if (!hasClipId && !hasUploadId) {
    // Neither present - invalid clip
    return null;
  } else if (hasClipId) {
    c.uploadId = null;
  } else {
    // hasUploadId
    c.clipId = null;
  }

  return clip as StudioClip;
}
