/*
    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 serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, fmt::Display, str::FromStr};

pub type TrackNum = usize;
pub type FormatNum = usize;
pub type IndexNum = usize;

#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum StreamSpec {
    // Whep {
    //     track: TrackNum,
    //     seek: u64,
    // },
    // WhepControl {
    //     token: String,
    // },
    Remux {
        tracks: Vec<usize>,
        container: StreamContainer,
    },
    Original {
        track: TrackNum,
    },
    HlsMultiVariant {
        container: StreamContainer,
    },
    HlsVariant {
        track: TrackNum,
        container: StreamContainer,
        format: FormatNum,
    },
    Info,
    FragmentIndex {
        track: TrackNum,
    },
    Fragment {
        track: TrackNum,
        index: IndexNum,
        container: StreamContainer,
        format: FormatNum,
    },
    // Track {
    //     segment: SegmentNum,
    //     track: TrackNum,
    //     container: StreamContainer,
    //     foramt: FormatNum,
    // },
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct StreamInfo {
    pub name: Option<String>,
    pub duration: f64,
    pub tracks: Vec<StreamTrackInfo>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct StreamTrackInfo {
    pub name: Option<String>,
    pub kind: TrackKind,
    pub formats: Vec<StreamFormatInfo>,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum TrackKind {
    Video,
    Audio,
    Subtitle,
}

#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct StreamFormatInfo {
    pub codec: String,
    pub bitrate: f64,
    pub remux: bool,
    pub containers: Vec<StreamContainer>,

    pub width: Option<u64>,
    pub height: Option<u64>,
    pub samplerate: Option<f64>,
    pub channels: Option<usize>,
    pub bit_depth: Option<u8>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum StreamContainer {
    WebM,
    Matroska,
    WebVTT,
    MPEG4,
    JVTT,
}

impl Display for TrackKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            TrackKind::Video => "video",
            TrackKind::Audio => "audio",
            TrackKind::Subtitle => "subtitle",
        })
    }
}

impl StreamSpec {
    pub fn to_query(&self) -> String {
        match self {
            StreamSpec::Remux { tracks, container } => {
                format!(
                    "?remux&tracks={}&container={container}",
                    tracks
                        .iter()
                        .map(|t| t.to_string())
                        .collect::<Vec<String>>()
                        .join(",")
                )
            }
            StreamSpec::Original { track } => format!("?original&track={track}"),
            StreamSpec::HlsMultiVariant { container } => {
                format!("?hlsmultivariant&container={container}")
            }
            StreamSpec::HlsVariant {
                track,
                container,
                format,
            } => format!("?hlsvariant&track={track}&container={container}&format={format}"),
            StreamSpec::Info => "?info".to_string(),
            StreamSpec::FragmentIndex { track } => {
                format!("?fragmentindex&track={track}")
            }
            StreamSpec::Fragment {
                track,
                index,
                container,
                format,
            } => format!(
                "?fragment&track={track}&index={index}&container={container}&format={format}"
            ),
        }
    }
    pub fn to_query_short(&self) -> String {
        match self {
            StreamSpec::Remux { tracks, container } => {
                format!(
                    "?remux&ts={}&c={container}",
                    tracks
                        .iter()
                        .map(|t| t.to_string())
                        .collect::<Vec<String>>()
                        .join(",")
                )
            }
            StreamSpec::Original { track } => format!("?original&t={track}"),
            StreamSpec::HlsMultiVariant { container } => {
                format!("?hlsmultivariant&c={container}")
            }
            StreamSpec::HlsVariant {
                track,
                container,
                format,
            } => format!("?hlsvariant&t={track}&c={container}&f={format}"),
            StreamSpec::Info => "?info".to_string(),
            StreamSpec::FragmentIndex { track } => {
                format!("?fragmentindex&t={track}")
            }
            StreamSpec::Fragment {
                track,
                index,
                container,
                format,
            } => format!("?fragment&t={track}&i={index}&c={container}&f={format}"),
        }
    }
    pub fn from_query_kv(query: &BTreeMap<String, String>) -> Result<Self, &'static str> {
        let get_num = |k: &'static str, ks: &'static str| {
            query
                .get(k)
                .or(query.get(ks))
                .ok_or(k)
                .and_then(|a| a.parse::<usize>().map_err(|_| "invalid number"))
        };
        let get_container = || {
            query
                .get("container")
                .or(query.get("c"))
                .ok_or("container")
                .and_then(|s| s.parse().map_err(|()| "unknown container"))
        };
        if query.contains_key("info") {
            Ok(Self::Info)
        } else if query.contains_key("hlsmultivariant") {
            Ok(Self::HlsMultiVariant {
                container: get_container()?,
            })
        } else if query.contains_key("hlsvariant") {
            Ok(Self::HlsVariant {
                track: get_num("track", "t")? as TrackNum,
                format: get_num("format", "f")? as FormatNum,
                container: get_container()?,
            })
        } else if query.contains_key("fragment") {
            Ok(Self::Fragment {
                track: get_num("track", "t")? as TrackNum,
                format: get_num("format", "f")? as FormatNum,
                index: get_num("index", "i")? as IndexNum,
                container: get_container()?,
            })
        } else if query.contains_key("fragmentindex") {
            Ok(Self::FragmentIndex {
                track: get_num("track", "t")? as TrackNum,
            })
        } else {
            Err("invalid stream spec")
        }
    }
}

impl Display for StreamContainer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            StreamContainer::WebM => "webm",
            StreamContainer::Matroska => "matroska",
            StreamContainer::WebVTT => "webvtt",
            StreamContainer::JVTT => "jvtt",
            StreamContainer::MPEG4 => "mpeg4",
        })
    }
}
impl FromStr for StreamContainer {
    type Err = ();
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(match s {
            "webm" => StreamContainer::WebM,
            "matroska" => StreamContainer::Matroska,
            "webvtt" => StreamContainer::WebVTT,
            "jvtt" => StreamContainer::JVTT,
            "mpeg4" => StreamContainer::MPEG4,
            _ => return Err(()),
        })
    }
}
