import * as Utils from "./utils.js";
import type {
  ManifestLoadedData,
  LevelUpdatedData,
  AudioTrackLoadedData,
  LevelParsed,
} from "hls.js";
import { Core, Segment, generateStreamShortId } from "p2p-media-loader-core";

export class SegmentManager {
  core: Core;

  constructor(core: Core) {
    this.core = core;
  }

  processMainManifest(data: ManifestLoadedData) {
    const { levels, audioTracks } = data;
    // in the case of audio only stream it is stored in levels

    for (const level of levels) {
      const { url, bitrate, maxBitrate, videoCodec, width, height } =
        level as LevelParsed & { maxBitrate?: number };
      // maxBitrate tracks the peak BANDWIDTH tag, whereas bitrate tracks AVERAGE-BANDWIDTH.
      // We prioritize maxBitrate to universally match Shaka's variant.bandwidth parsing.
      const b = maxBitrate ?? bitrate;
      const isMissingMetadata = b === 0;
      const frameRate = level.attrs["FRAME-RATE"];
      const videoRange = level.attrs["VIDEO-RANGE"];

      const index = generateStreamShortId({
        bitrate: b,
        codecs: isMissingMetadata ? undefined : videoCodec,
        width: isMissingMetadata ? undefined : width,
        height: isMissingMetadata ? undefined : height,
        frameRate: isMissingMetadata ? undefined : frameRate,
        videoRange: isMissingMetadata ? undefined : videoRange,
      });
      this.core.addStreamIfNoneExists({
        runtimeId: Array.isArray(url) ? (url as string[])[0] : url,
        type: "main",
        index,
      });
    }

    for (const track of audioTracks) {
      // Object properties vary across hls.js versions so we cast to any:
      const { url, audioCodec, lang, channels, name } = track;
      const index = generateStreamShortId({
        bitrate: 0, // Match Shaka behavior for audio stream without variant
        codecs: audioCodec,
        language: lang,
        channels,
        name,
      });
      this.core.addStreamIfNoneExists({
        runtimeId: Array.isArray(url) ? (url as string[])[0] : url,
        type: "secondary",
        index,
      });
    }
  }

  updatePlaylist(data: LevelUpdatedData | AudioTrackLoadedData) {
    const {
      details: { url, fragments, live },
    } = data;

    const playlist = this.core.getStream(url);
    if (!playlist) return;

    const segmentToRemoveIds = new Set(playlist.segments.keys());
    const newSegments: Segment[] = [];
    fragments.forEach((fragment, index) => {
      const {
        url: responseUrl,
        byteRange: fragByteRange,
        sn,
        start: startTime,
        end: endTime,
      } = fragment;

      const [start, end] = fragByteRange;
      const byteRange = Utils.getByteRange(
        start,
        end !== undefined ? end - 1 : undefined,
      );
      const runtimeId = Utils.getSegmentRuntimeId(responseUrl, byteRange);
      segmentToRemoveIds.delete(runtimeId);

      if (playlist.segments.has(runtimeId)) return;
      newSegments.push({
        runtimeId,
        url: responseUrl,
        externalId: live ? sn : index,
        byteRange,
        startTime,
        endTime,
      });
    });

    if (!newSegments.length && !segmentToRemoveIds.size) return;
    this.core.updateStream(url, newSegments, segmentToRemoveIds.values());
  }
}
