/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2026 metamuffin <metamuffin.org>
*/
use crate::{
    cues::{generate_cues, GeneratedCue},
    stream_info, SMediaInfo,
};
use anyhow::{anyhow, Result};
use jellycache::HashKey;
use jellyremuxer::{
    demuxers::create_demuxer_autodetect,
    matroska::{self, Segment},
    muxers::write_fragment,
    ContainerFormat,
};
use jellystream_types::{FormatNum, IndexNum, StreamContainer, TrackNum};
use jellytranscoder::fragment::transcode;
use std::{
    fs::File,
    io::{Cursor, Read},
    sync::Arc,
};

pub fn fragment_stream(
    info: Arc<SMediaInfo>,
    track: TrackNum,
    index: IndexNum,
    format_num: FormatNum,
    container: StreamContainer,
) -> Result<Box<dyn Read + Send + Sync>> {
    let (iinfo, info) = stream_info(info)?;

    let (file_index, track_num) = *iinfo
        .track_to_file
        .get(track)
        .ok_or(anyhow!("track not found"))?;
    let media_path = iinfo.paths[file_index].clone();
    let track = info.tracks.get(track).ok_or(anyhow!("track not found"))?;
    let format = track
        .formats
        .get(format_num)
        .ok_or(anyhow!("format not found"))?;

    let mk_track = iinfo.metadata[file_index]
        .tracks
        .as_ref()
        .unwrap()
        .entries
        .iter()
        .find(|t| t.track_number == track_num)
        .unwrap();

    let timestamp_scale = iinfo.metadata[file_index].info.timestamp_scale;
    let total_duration = iinfo.metadata[file_index].info.duration;
    let cue_stat = generate_cues(&media_path)?;
    let start_cue = cue_stat
        .cues
        .get(index)
        .ok_or(anyhow!("fragment index out of range"))?;
    let end_cue = cue_stat
        .cues
        .get(index + 1)
        .copied()
        .unwrap_or(GeneratedCue {
            position: 0,
            time: total_duration.unwrap_or_default() as u64 * timestamp_scale, // TODO rounding?
        });
    let cluster_offset = start_cue.position;
    let duration = (end_cue.time - start_cue.time) as f64 / timestamp_scale as f64;

    let mk_info = matroska::Info {
        duration: Some(duration),
        timestamp_scale,
        ..Default::default()
    };
    let mk_tracks = matroska::Tracks {
        entries: vec![mk_track.to_owned()],
    };

    let (mut cluster, next_cluster) = {
        let media_file = File::open(&media_path)?;
        let mut media = create_demuxer_autodetect(Box::new(media_file))?
            .ok_or(anyhow!("media container unknown"))?;
        media.seek_cluster(Some(cluster_offset))?;
        (
            media
                .read_cluster()?
                .ok_or(anyhow!("cluster unexpectedly missing"))?
                .1,
            media.read_cluster()?.map(|(_, x)| x),
        )
    };

    cluster.simple_blocks.retain(|b| b.track == track_num);
    cluster.block_groups.retain(|b| b.block.track == track_num);
    let next_kf = next_cluster.and_then(|x| {
        x.simple_blocks
            .iter()
            .find(|b| b.track == track_num)
            .cloned()
    });

    let jr_container = match container {
        StreamContainer::WebM => ContainerFormat::Webm,
        StreamContainer::Matroska => ContainerFormat::Matroska,
        StreamContainer::WebVTT => todo!(),
        StreamContainer::MPEG4 => ContainerFormat::Mpeg4,
        StreamContainer::JVTT => todo!(),
    };

    let mut segment = Segment {
        info: mk_info,
        tracks: Some(mk_tracks),
        clusters: vec![cluster],
        ..Default::default()
    };
    segment.info.writing_app =
        concat!(env!("CARGO_PKG_NAME"), "-", env!("CARGO_PKG_VERSION")).to_string();

    if !format.remux {
        segment = transcode(
            track.kind,
            &format!("{}-T{track_num}-I{index}", HashKey(media_path)),
            format,
            segment,
            next_kf,
        )?;
    }

    let mut out = Vec::new();
    write_fragment(jr_container, &mut out, segment)?;
    Ok(Box::new(Cursor::new(out)))
}
