import {
  fixOptionalBoolean,
  isArray,
  isNonNegativeFiniteNumber,
  isObject,
  isString,
  isValidFiniteNumber,
  makeFixer,
} from './utils';

export type AutomationPoint = {
  beats: number;
  value: number;
  curve: number;
};

export type FollowTrackTiming = {
  type: 'follow-track';
  trackId: string;
  fallbackBPS: number;
};

export type ManualTiming = {
  type: 'manual';
  lockBPS?: boolean;
  bps: number;
  bpsAutomation: AutomationPoint[];
};

export type StudioTiming = FollowTrackTiming | ManualTiming;

export const isValidAutomationPoint = (
  automationPoint: unknown
): automationPoint is AutomationPoint => {
  if (!isObject(automationPoint)) {
    return false;
  }

  const ap = automationPoint as {
    beats: unknown;
    value: unknown;
    curve: unknown;
  };

  // All fields must be valid finite numbers
  // Value and curve must be non-negative
  return (
    isValidFiniteNumber(ap.beats) &&
    isNonNegativeFiniteNumber(ap.value) &&
    isNonNegativeFiniteNumber(ap.curve)
  );
};

export const fixBpsAutomation = (bpsAutomation: unknown): AutomationPoint[] => {
  if (!isArray(bpsAutomation)) {
    return [];
  }

  // Filter out invalid automation points
  return bpsAutomation.filter(isValidAutomationPoint);
};

const fixFallbackBPS = makeFixer(isNonNegativeFiniteNumber, 2);
const fixBPS = makeFixer(isNonNegativeFiniteNumber, 2);

export default function fixTiming(
  timing: unknown,
  validTrackIds: string[]
): StudioTiming {
  if (!isObject(timing)) {
    return {
      type: 'manual',
      bps: 2,
      bpsAutomation: [],
    };
  }

  const t = timing as { type: unknown };

  if (t.type === 'follow-track') {
    const ft = timing as {
      type: 'follow-track';
      trackId: unknown;
      fallbackBPS: unknown;
    };

    // Validate trackId exists in the list of valid track IDs
    if (isString(ft.trackId) && validTrackIds.includes(ft.trackId)) {
      ft.fallbackBPS = fixFallbackBPS(ft.fallbackBPS);
      return timing as FollowTrackTiming;
    }

    // Track ID is invalid - convert to manual timing using fallback BPS
    const fallbackBPS = fixFallbackBPS(ft.fallbackBPS);
    return {
      type: 'manual',
      bps: fallbackBPS,
      bpsAutomation: [],
    };
  }

  // Default to manual timing
  const mt = timing as {
    type: unknown;
    lockBPS: unknown;
    bps: unknown;
    bpsAutomation: unknown;
  };
  mt.type = 'manual';
  mt.lockBPS = fixOptionalBoolean(mt.lockBPS);
  mt.bps = fixBPS(mt.bps);
  mt.bpsAutomation = fixBpsAutomation(mt.bpsAutomation);

  return timing as ManualTiming;
}
