use dioxus::prelude::*;
use js_sys::{decode_uri_component, encode_uri_component, Function, Reflect, Promise};
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use web_sys::{Event, MediaSource, Request, RequestInit, Response, SourceBuffer, Url};

use bytes::Bytes;
use ec_direct::{decode_direct_link, encode_direct_link, DirectCodeV1};
use just_webrtc::types::{ICEServer, PeerConfiguration};
use just_webrtc::{DataChannelExt, PeerConnectionBuilder, PeerConnectionExt};

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
struct StreamDescriptor {
    id: String,
    title: String,
    number: Option<String>,
    source: String,
    metadata: Vec<StreamMetadata>,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
struct StreamMetadata {
    key: String,
    value: String,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
struct PlaybackInfo {
    stream_id: String,
    url: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
struct EmptyArgs {}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct StartArgs {
    stream_id: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MoqStartArgs {
    remote: String,
    broadcast_name: String,
    stream_id: Option<String>,
    track_name: Option<String>,
    auto_quality: Option<bool>,
    variant: Option<String>,
    network_secret: Option<String>,
    discovery: Option<String>,
}

#[derive(Clone, Debug)]
struct DirectSession {
    reply_link: String,
}

#[derive(Clone, Debug, Deserialize)]
struct DirectoryList {
    now_ms: u64,
    entries: Vec<DirectoryEntry>,
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
struct DirectoryEntry {
    stream_id: String,
    title: String,
    offer: String,
    updated_ms: u64,
    expires_ms: u64,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ShareArgs {
    stream_id: String,
    network_secret: Option<String>,
    chunk_ms: Option<u64>,
    announce: bool,
    gossip_peers: Vec<String>,
    discovery: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct ShareInfo {
    stream_id: String,
    endpoint_addr: String,
    endpoint_id: Option<String>,
    broadcast_name: String,
    track_name: String,
    discovery: Option<String>,
    announce_status: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
struct SourceDescriptor {
    id: String,
    kind: String,
    name: String,
    ip: Option<String>,
    tuner_count: Option<u8>,
    status: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CatalogWatchArgs {
    peers: Vec<String>,
    discovery: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct AddStreamArgs {
    input: String,
    options: Option<ManualSourceOptions>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ProbeStreamArgs {
    input: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct BootstrapNbcAuthArgs {
    input: Option<String>,
    stream_id: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct BootstrapNbcAuthResult {
    input_url: String,
    stream_id: Option<String>,
    hidden_mode: bool,
    surfaced_auth: bool,
    data_dir: Option<String>,
    status: String,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
struct ManualSourceOptions {
    ytdlp_format: Option<String>,
    ytdlp_live_from_start: bool,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct YtdlpFormatOption {
    format_id: String,
    label: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct YtdlpProbe {
    title: Option<String>,
    formats: Vec<YtdlpFormatOption>,
    default_format: Option<String>,
    supports_live_from_start: bool,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct StreamProbe {
    kind: String,
    live: bool,
    requires_options: bool,
    message: Option<String>,
    ytdlp: Option<YtdlpProbe>,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct LinuxDvbAdapterInfo {
    adapter: u32,
    dvrs: Vec<u32>,
    frontends: Vec<u32>,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct LinuxDvbChannelsInfo {
    channels_conf: Option<String>,
    channels: Vec<String>,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct LinuxDvbBuildUrlArgs {
    adapter: u32,
    dvr: u32,
    channel: Option<String>,
    channels_conf: Option<String>,
    tune_wait_ms: Option<u64>,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct LinuxDvbListChannelsArgs {
    channels_conf: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct AddStreamResult {
    kind: String,
    added: usize,
    title: Option<String>,
}

fn discovery_string(dht: bool, mdns: bool, dns: bool) -> Option<String> {
    let mut modes = Vec::new();
    if dht {
        modes.push("dht");
    }
    if mdns {
        modes.push("mdns");
    }
    if dns {
        modes.push("dns");
    }
    if modes.is_empty() {
        None
    } else {
        Some(modes.join(","))
    }
}

#[derive(Clone, Debug)]
struct ParsedWatchLink {
    remote: String,
    broadcast: String,
    track: Option<String>,
    stream_id: Option<String>,
    network_secret: Option<String>,
    discovery: Option<String>,
}

fn encode_component(value: &str) -> String {
    encode_uri_component(value).as_string().unwrap_or_else(|| value.to_string())
}

fn decode_component(value: &str) -> Option<String> {
    decode_uri_component(value).ok()?.as_string()
}

fn build_watch_link(
    remote: &str,
    broadcast: &str,
    track: Option<&str>,
    stream_id: Option<&str>,
    network_secret: Option<&str>,
    discovery: Option<&str>,
) -> String {
    let mut parts = Vec::new();
    parts.push(format!("remote={}", encode_component(remote)));
    parts.push(format!("broadcast={}", encode_component(broadcast)));
    if let Some(track) = track {
        if !track.trim().is_empty() {
            parts.push(format!("track={}", encode_component(track)));
        }
    }
    if let Some(stream_id) = stream_id {
        if !stream_id.trim().is_empty() {
            parts.push(format!("stream_id={}", encode_component(stream_id)));
        }
    }
    if let Some(secret) = network_secret {
        if !secret.trim().is_empty() {
            parts.push(format!("secret={}", encode_component(secret)));
        }
    }
    if let Some(discovery) = discovery {
        if !discovery.trim().is_empty() {
            parts.push(format!("discovery={}", encode_component(discovery)));
        }
    }
    format!("every.channel://watch?{}", parts.join("&"))
}

fn parse_watch_link(link: &str) -> Option<ParsedWatchLink> {
    let link = link.trim();
    let prefix = "every.channel://";
    if !link.starts_with(prefix) {
        return None;
    }
    let rest = &link[prefix.len()..];
    let (path, query) = rest.split_once('?')?;
    if !path.eq_ignore_ascii_case("watch") {
        return None;
    }

    let mut remote = None::<String>;
    let mut broadcast = None::<String>;
    let mut track = None::<String>;
    let mut stream_id = None::<String>;
    let mut secret = None::<String>;
    let mut discovery = None::<String>;

    for pair in query.split('&') {
        let pair = pair.trim();
        if pair.is_empty() {
            continue;
        }
        let (k, v) = pair.split_once('=').unwrap_or((pair, ""));
        let k = decode_component(k).unwrap_or_else(|| k.to_string());
        let v = decode_component(v).unwrap_or_else(|| v.to_string());
        match k.as_str() {
            "remote" => remote = Some(v),
            "broadcast" => broadcast = Some(v),
            "track" => track = Some(v),
            "stream_id" => stream_id = Some(v),
            "secret" => secret = Some(v),
            "discovery" => discovery = Some(v),
            _ => {}
        }
    }

    Some(ParsedWatchLink {
        remote: remote?,
        broadcast: broadcast?,
        track,
        stream_id,
        network_secret: secret,
        discovery,
    })
}

fn parse_direct_link(link: &str) -> Option<DirectCodeV1> {
    decode_direct_link(link).ok()
}

#[derive(Clone, Debug, Deserialize)]
struct ObjectMeta {
    created_unix_ms: u64,
    content_type: String,
    size_bytes: u64,
    timing: Option<TimingMeta>,
    #[allow(dead_code)]
    encryption: Option<EncryptionMeta>,
    #[allow(dead_code)]
    chunk_hash: Option<String>,
    #[allow(dead_code)]
    chunk_hash_alg: Option<String>,
    #[allow(dead_code)]
    chunk_proof: Option<Vec<String>>,
    #[allow(dead_code)]
    chunk_proof_alg: Option<String>,
    #[allow(dead_code)]
    manifest_id: Option<String>,
}

#[derive(Clone, Debug, Deserialize)]
struct TimingMeta {
    chunk_index: u64,
    chunk_start_27mhz: u64,
    chunk_duration_27mhz: u64,
    utc_start_unix: Option<i64>,
    sync_status: String,
}

#[derive(Clone, Debug, Deserialize)]
struct EncryptionMeta {
    alg: String,
    key_id: String,
    nonce_hex: String,
}

const DIRECT_WIRE_TAG_FRAME: u8 = 0x00;
const DIRECT_WIRE_TAG_STREAM: u8 = 0x01;
const DIRECT_WIRE_TAG_PING: u8 = 0x02;

#[derive(Default)]
struct DirectWireDecoder {
    buf: Vec<u8>,
    pos: usize,
    want: Option<usize>,
}

impl DirectWireDecoder {
    fn push(&mut self, msg: &[u8]) -> Vec<Vec<u8>> {
        if msg.is_empty() {
            return Vec::new();
        }

        match msg[0] {
            DIRECT_WIRE_TAG_FRAME => return vec![msg[1..].to_vec()],
            DIRECT_WIRE_TAG_STREAM => {
                self.buf.extend_from_slice(&msg[1..]);
            }
            DIRECT_WIRE_TAG_PING => {
                // Control message; ignore.
                return Vec::new();
            }
            _ => {
                // Unknown tag: assume legacy (whole frame per message).
                return vec![msg.to_vec()];
            }
        }

        let mut out = Vec::new();
        loop {
            if self.want.is_none() {
                if self.buf.len().saturating_sub(self.pos) < 4 {
                    break;
                }
                let start = self.pos;
                let len = u32::from_be_bytes([
                    self.buf[start],
                    self.buf[start + 1],
                    self.buf[start + 2],
                    self.buf[start + 3],
                ]) as usize;
                self.pos += 4;
                self.want = Some(len);
            }

            let Some(want) = self.want else { break };
            if self.buf.len().saturating_sub(self.pos) < want {
                break;
            }
            let start = self.pos;
            let end = start + want;
            out.push(self.buf[start..end].to_vec());
            self.pos = end;
            self.want = None;

            if self.pos > 64 * 1024 {
                self.buf.drain(0..self.pos);
                self.pos = 0;
            }
        }

        out
    }
}

fn decode_object_frame(bytes: &[u8]) -> Option<(ObjectMeta, Vec<u8>)> {
    if bytes.len() < 4 {
        return None;
    }
    let meta_len = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
    if bytes.len() < 4 + meta_len {
        return None;
    }
    let meta: ObjectMeta = serde_json::from_slice(&bytes[4..4 + meta_len]).ok()?;
    let data = bytes[4 + meta_len..].to_vec();
    Some((meta, data))
}

fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option<usize> {
    haystack
        .windows(needle.len())
        .position(|window| window == needle)
}

fn codec_string_from_init_mp4(init: &[u8]) -> Option<String> {
    // Minimal: locate avcC and read AVCProfileIndication/profile_compatibility/AVCLevelIndication.
    let pos = find_subslice(init, b"avcC")?;
    let start = pos + 4;
    if init.len() < start + 4 {
        return None;
    }
    let config_version = init[start];
    if config_version != 1 {
        return None;
    }
    let profile = init[start + 1];
    let compat = init[start + 2];
    let level = init[start + 3];
    Some(format!("avc1.{profile:02x}{compat:02x}{level:02x}"))
}

fn ws_url_for_path(path_and_query: &str) -> Result<String, String> {
    let window = web_sys::window().ok_or_else(|| "missing window".to_string())?;
    let loc = window.location();
    let origin = loc.origin().map_err(|_| "location.origin failed".to_string())?;
    let ws_origin = if origin.starts_with("https://") {
        origin.replacen("https://", "wss://", 1)
    } else if origin.starts_with("http://") {
        origin.replacen("http://", "ws://", 1)
    } else {
        return Err("unknown origin scheme".to_string());
    };
    Ok(format!("{ws_origin}{path_and_query}"))
}

struct MseSink {
    ms: MediaSource,
    sb: Option<SourceBuffer>,
    is_open: bool,
    updateend_installed: bool,
    queue: VecDeque<Vec<u8>>,
}

fn create_mse_url() -> Result<(MediaSource, String), String> {
    let ms = MediaSource::new().map_err(|_| "MediaSource unsupported".to_string())?;
    let url = Url::create_object_url_with_source(&ms).map_err(|_| "URL create failed".to_string())?;
    Ok((ms, url))
}

fn install_sourceopen(ms: &MediaSource, state: Rc<RefCell<MseSink>>) {
    let ms_for_listener = ms.clone();
    let cb = Closure::<dyn FnMut(Event)>::new(move |_evt: Event| {
        let mut st = state.borrow_mut();
        st.is_open = true;
        // Keep MediaSource alive for the listener lifetime.
        let _ = &ms_for_listener;
    });
    let _ = ms.add_event_listener_with_callback("sourceopen", cb.as_ref().unchecked_ref());
    cb.forget();
}

fn try_create_source_buffer(state: Rc<RefCell<MseSink>>, init: &[u8]) -> Result<(), JsValue> {
    let mut st = state.borrow_mut();
    if st.sb.is_some() || !st.is_open {
        return Ok(());
    }
    let v = codec_string_from_init_mp4(init).unwrap_or_else(|| "avc1.42e01e".to_string());
    let with_audio = format!("video/mp4; codecs=\"{v}, mp4a.40.2\"");
    let sb = st
        .ms
        .add_source_buffer(&with_audio)
        .or_else(|_| st.ms.add_source_buffer(&format!("video/mp4; codecs=\"{v}\"")))?;
    st.sb = Some(sb);
    if !st.updateend_installed {
        if let Some(sb) = st.sb.as_ref() {
            let state2 = state.clone();
            let cb = Closure::<dyn FnMut(Event)>::new(move |_evt: Event| {
                let mut st = state2.borrow_mut();
                flush_queue(&mut st);
            });
            let _ = sb.add_event_listener_with_callback("updateend", cb.as_ref().unchecked_ref());
            cb.forget();
            st.updateend_installed = true;
        }
    }
    Ok(())
}

fn append_bytes(st: &mut MseSink, bytes: Vec<u8>) {
    st.queue.push_back(bytes);
    flush_queue(st);
}

fn flush_queue(st: &mut MseSink) {
    let Some(sb) = st.sb.as_ref() else { return; };
    if sb.updating() {
        return;
    }
    let Some(mut next) = st.queue.pop_front() else { return; };
    let _ = sb.append_buffer_with_u8_array(&mut next);
}

async fn copy_to_clipboard(text: String) -> Result<(), String> {
    let window = web_sys::window().ok_or_else(|| "window unavailable".to_string())?;
    let clipboard = window.navigator().clipboard();
    let promise = clipboard.write_text(&text);
    JsFuture::from(promise)
        .await
        .map_err(|err| format!("clipboard write rejected: {err:?}"))?;
    Ok(())
}

async fn fetch_json<T: for<'de> Deserialize<'de>>(url: &str) -> Result<T, String> {
    let window = web_sys::window().ok_or_else(|| "window unavailable".to_string())?;
    let p = window.fetch_with_str(url);
    let resp = JsFuture::from(p)
        .await
        .map_err(|err| format!("fetch rejected: {err:?}"))?;
    let resp: Response = resp
        .dyn_into()
        .map_err(|_| "fetch response type mismatch".to_string())?;
    let p = resp.json().map_err(|_| "response.json failed".to_string())?;
    let value = JsFuture::from(p)
        .await
        .map_err(|err| format!("response.json rejected: {err:?}"))?;
    serde_wasm_bindgen::from_value(value).map_err(|err| format!("invalid json: {err}"))
}

async fn post_json<T: Serialize>(url: &str, body: &T) -> Result<(), String> {
    let window = web_sys::window().ok_or_else(|| "window unavailable".to_string())?;
    let mut init = RequestInit::new();
    init.method("POST");
    let json = serde_json::to_string(body).map_err(|err| err.to_string())?;
    init.body(Some(&JsValue::from_str(&json)));
    let req = Request::new_with_str_and_init(url, &init)
        .map_err(|_| "request build failed".to_string())?;
    req.headers()
        .set("content-type", "application/json")
        .map_err(|_| "header set failed".to_string())?;
    let p = window.fetch_with_request(&req);
    let resp = JsFuture::from(p)
        .await
        .map_err(|err| format!("fetch rejected: {err:?}"))?;
    let resp: Response = resp
        .dyn_into()
        .map_err(|_| "fetch response type mismatch".to_string())?;
    if !resp.ok() {
        return Err(format!("server error: {}", resp.status()));
    }
    Ok(())
}

#[derive(Deserialize)]
struct TurnResp {
    ice_servers: Vec<ICEServer>,
}

async fn fetch_turn_peer_config() -> PeerConfiguration {
    let mut cfg = PeerConfiguration::default();
    if let Ok(resp) = fetch_json::<TurnResp>("/api/turn").await {
        // Prefer server-provided ICE servers when present.
        if !resp.ice_servers.is_empty() {
            cfg.ice_servers = resp.ice_servers;
        }
    }
    cfg
}

fn main() {
    dioxus::launch(App);
}

fn tauri_available() -> bool {
    find_tauri_invoke().is_some()
}

fn find_tauri_invoke() -> Option<(JsValue, Function)> {
    let window = web_sys::window()?;
    let tauri = Reflect::get(&window, &JsValue::from_str("__TAURI__")).ok();
    if let Some(tauri) = tauri {
        if let Ok(invoke) = Reflect::get(&tauri, &JsValue::from_str("invoke")) {
            if let Ok(func) = invoke.dyn_into::<Function>() {
                return Some((tauri, func));
            }
        }
        if let Ok(core) = Reflect::get(&tauri, &JsValue::from_str("core")) {
            if let Ok(invoke) = Reflect::get(&core, &JsValue::from_str("invoke")) {
                if let Ok(func) = invoke.dyn_into::<Function>() {
                    return Some((core, func));
                }
            }
        }
    }
    if let Ok(internals) = Reflect::get(&window, &JsValue::from_str("__TAURI_INTERNALS__")) {
        if let Ok(invoke) = Reflect::get(&internals, &JsValue::from_str("invoke")) {
            if let Ok(func) = invoke.dyn_into::<Function>() {
                return Some((internals, func));
            }
        }
    }
    None
}

#[component]
fn App() -> Element {
    let mut streams = use_signal(Vec::<StreamDescriptor>::new);
    let mut selected = use_signal(|| None::<StreamDescriptor>);
    let mut playback = use_signal(|| None::<PlaybackInfo>);
    let mut status = use_signal(|| "Discovering local streams".to_string());
    let mut share_info = use_signal(|| None::<ShareInfo>);
    let mut share_announce = use_signal(|| true);
    let mut share_peers = use_signal(|| "".to_string());
    let mut share_secret = use_signal(|| "".to_string());
    let mut sources = use_signal(Vec::<SourceDescriptor>::new);
    let mut source_status = use_signal(|| "Idle".to_string());
    let mut catalog_peers = use_signal(|| "".to_string());
    let mut source_menu_open = use_signal(|| false);
    let mut source_loading = use_signal(|| false);
    let mut moq_link = use_signal(|| "".to_string());
    let mut moq_link_advanced = use_signal(|| false);
    let mut moq_remote = use_signal(|| "".to_string());
    let mut moq_broadcast = use_signal(|| "".to_string());
    let mut moq_stream_id = use_signal(|| "".to_string());
    let mut moq_track = use_signal(|| "chunks".to_string());
    let mut moq_secret = use_signal(|| "".to_string());
    let mut moq_auto_quality = use_signal(|| true);
    let mut moq_quality = use_signal(|| "720p".to_string());
    let mut direct_offer = use_signal(|| None::<DirectCodeV1>);
    let mut direct_reply = use_signal(|| "".to_string());
    let mut direct_answer_stream_id = use_signal(|| None::<String>);
    let mut discovery_dht = use_signal(|| false);
    let mut discovery_mdns = use_signal(|| false);
    let mut discovery_dns = use_signal(|| false);
    let mut add_input = use_signal(|| "".to_string());
    let mut add_probe = use_signal(|| None::<StreamProbe>);
    let mut add_probe_input = use_signal(|| "".to_string());
    let mut add_format = use_signal(|| "".to_string());
    let mut add_live_from_start = use_signal(|| false);
    let mut linux_adapters = use_signal(Vec::<LinuxDvbAdapterInfo>::new);
    let mut linux_adapter = use_signal(|| "0".to_string());
    let mut linux_dvr = use_signal(|| "0".to_string());
    let mut linux_channels_conf = use_signal(|| "".to_string());
    let mut linux_channels = use_signal(Vec::<String>::new);
    let mut linux_channel = use_signal(|| "".to_string());
    let mut linux_tune_wait = use_signal(|| "800".to_string());
    let mut stream_filter = use_signal(|| "all".to_string());
    let mut page = use_signal(|| 0usize);
    let mut global_directory = use_signal(Vec::<DirectoryEntry>::new);
    let mut global_directory_status = use_signal(|| "Loading...".to_string());
    let mut global_directory_loading = use_signal(|| false);
    let page_size = 14usize;

    let _loader = use_resource(move || {
        let mut streams = streams.clone();
        let mut status = status.clone();
        let mut sources = sources.clone();
        let mut source_status = source_status.clone();
        let mut global_directory = global_directory.clone();
        let mut global_directory_status = global_directory_status.clone();
        let mut global_directory_loading = global_directory_loading.clone();
        async move {
            if !tauri_available() {
                status.set("Web mode: browse live channels".to_string());
                source_status.set("Web mode".to_string());
                global_directory_loading.set(true);
                match fetch_json::<DirectoryList>("/api/directory").await {
                    Ok(list) => {
                        global_directory_status.set(format!("{} live", list.entries.len()));
                        global_directory.set(list.entries);
                    }
                    Err(err) => {
                        global_directory_status.set(format!("Live list error: {err}"));
                    }
                }
                global_directory_loading.set(false);
                return;
            }
            match tauri_invoke::<Vec<StreamDescriptor>, _>("list_streams", &EmptyArgs {})
                .await
            {
                Ok(list) => {
                    status.set(format!("{} streams ready", list.len()));
                    streams.set(list);
                }
                Err(err) => {
                    status.set(format!("Discovery error: {err}"));
                }
            }
            match tauri_invoke::<Vec<SourceDescriptor>, _>("list_sources", &EmptyArgs {})
                .await
            {
                Ok(list) => {
                    source_status.set(format!("{} sources online", list.len()));
                    sources.set(list);
                }
                Err(err) => {
                    source_status.set(format!("Source error: {err}"));
                }
            }
        }
    });

    let stream_list = streams.read().clone();
    let filter_value = stream_filter.read().clone();
    let filtered_streams = if filter_value == "all" {
        stream_list
    } else {
        stream_list
            .into_iter()
            .filter(|stream| stream_source_kind(stream) == filter_value)
            .collect::<Vec<_>>()
    };
    let total_streams = filtered_streams.len();
    let page_index = *page.read();
    let page_count = if total_streams == 0 {
        1
    } else {
        (total_streams + page_size - 1) / page_size
    };
    let clamped_page = if page_index >= page_count { 0 } else { page_index };
    let start = clamped_page * page_size;
    let paged_streams = filtered_streams
        .iter()
        .skip(start)
        .take(page_size)
        .cloned()
        .collect::<Vec<_>>();
    if clamped_page != page_index {
        page.set(clamped_page);
    }
    let active_id = selected.read().as_ref().map(|s| s.id.clone());
    let playback_url = playback.read().as_ref().map(|p| p.url.clone());
    let now_playing = selected.read().clone();
    let bootstrap_stream = selected
        .read()
        .as_ref()
        .filter(|stream| stream_source_kind(stream) == "nbc")
        .cloned();
    let bootstrap_input_value = add_input.read().clone();
    let bootstrap_input_is_nbc = looks_like_nbc_input(&bootstrap_input_value);
    let current_share = share_info.read().clone();
    let source_list = sources.read().clone();

    let mut refresh_sources = {
        let mut sources = sources.clone();
        let mut source_status = source_status.clone();
        let mut source_loading = source_loading.clone();
        move || {
            if !tauri_available() {
                source_status.set("Tauri backend not available".to_string());
                return;
            }
            source_loading.set(true);
            let mut sources = sources.clone();
            let mut source_status = source_status.clone();
            let mut source_loading = source_loading.clone();
            spawn(async move {
                match tauri_invoke::<Vec<SourceDescriptor>, _>("list_sources", &EmptyArgs {})
                    .await
                {
                    Ok(list) => {
                        source_status.set(format!("{} sources online", list.len()));
                        sources.set(list);
                    }
                    Err(err) => {
                        source_status.set(format!("Source error: {err}"));
                    }
                }
                source_loading.set(false);
            });
        }
    };

    let mut refresh_streams = {
        let mut streams = streams.clone();
        let mut status = status.clone();
        move || {
            if !tauri_available() {
                status.set("Tauri backend not available (open the Tauri app)".to_string());
                return;
            }
            let mut streams = streams.clone();
            let mut status = status.clone();
            spawn(async move {
                match tauri_invoke::<Vec<StreamDescriptor>, _>("refresh_streams", &EmptyArgs {})
                    .await
                {
                    Ok(list) => {
                        status.set(format!("{} streams ready", list.len()));
                        streams.set(list);
                    }
                    Err(err) => {
                        status.set(format!("Discovery error: {err}"));
                    }
                }
            });
        }
    };

    let mut refresh_global_directory = {
        let mut global_directory = global_directory.clone();
        let mut global_directory_status = global_directory_status.clone();
        let mut global_directory_loading = global_directory_loading.clone();
        move || {
            global_directory_loading.set(true);
            let mut global_directory = global_directory.clone();
            let mut global_directory_status = global_directory_status.clone();
            let mut global_directory_loading = global_directory_loading.clone();
            spawn(async move {
                match fetch_json::<DirectoryList>("/api/directory").await {
                    Ok(list) => {
                        global_directory_status.set(format!("{} live", list.entries.len()));
                        global_directory.set(list.entries);
                    }
                    Err(err) => {
                        global_directory_status.set(format!("Live list error: {err}"));
                    }
                }
                global_directory_loading.set(false);
            });
        }
    };

    rsx! {
        div { class: "app",
            header { class: "topbar",
                div { class: "brand",
                    div { class: "brand-title", "every.channel" }
                    div { class: "brand-subtitle", "Your signal, everywhere." }
                }
                div { class: "topbar-actions",
                    button {
                        class: "add-source",
                        onclick: move |_| {
                            let open = *source_menu_open.read();
                            source_menu_open.set(!open);
                            if !open {
                                refresh_sources();
                            }
                        },
                        if *source_loading.read() { "Finding..." } else { "+ Add" }
                    }
                    if *source_menu_open.read() {
                        div { class: "source-menu",
                            div { class: "source-menu-title", "Devices" }
                            div { class: "source-menu-status", "{source_status.read()}" }
                            for source in source_list.clone() {
                                div { class: "source-menu-item",
                                    span { "{source.name}" }
                                    span { "{source.ip.clone().unwrap_or_default()}" }
                                }
                            }
                            button {
                                class: "source-menu-action",
                                onclick: move |_| refresh_sources(),
                                "Refresh"
                            }
                            if bootstrap_stream.is_some() || bootstrap_input_is_nbc {
                                div { class: "source-menu-divider" }
                                div { class: "source-menu-section",
                                    div { class: "source-menu-title", "NBC auth" }
                                    div { class: "source-menu-status",
                                        "Warm a hidden NBC session. The window only appears if MVPD auth is needed."
                                    }
                                    if let Some(stream) = bootstrap_stream.clone() {
                                        button {
                                            class: "source-menu-action",
                                            onclick: move |_| {
                                                if !tauri_available() {
                                                    status.set("Tauri backend not available (open the Tauri app)".to_string());
                                                    return;
                                                }
                                                let stream_id = stream.id.clone();
                                                let stream_title = stream.title.clone();
                                                let args = BootstrapNbcAuthArgs {
                                                    input: None,
                                                    stream_id: Some(stream_id),
                                                };
                                                let mut status = status.clone();
                                                spawn(async move {
                                                    status.set(format!("Bootstrapping NBC auth for {}", stream_title));
                                                    match tauri_invoke::<BootstrapNbcAuthResult, _>("bootstrap_nbc_auth", &args).await {
                                                        Ok(result) => {
                                                            if result.surfaced_auth {
                                                                status.set("NBC auth ready after interactive sign-in".to_string());
                                                            } else {
                                                                status.set("NBC auth ready in hidden mode".to_string());
                                                            }
                                                        }
                                                        Err(err) => {
                                                            status.set(format!("NBC bootstrap error: {err}"));
                                                        }
                                                    }
                                                });
                                            },
                                            "Bootstrap selected NBC"
                                        }
                                    }
                                    if bootstrap_input_is_nbc {
                                        button {
                                            class: "source-menu-action",
                                            onclick: move |_| {
                                                if !tauri_available() {
                                                    status.set("Tauri backend not available (open the Tauri app)".to_string());
                                                    return;
                                                }
                                                let input = bootstrap_input_value.clone();
                                                let args = BootstrapNbcAuthArgs {
                                                    input: Some(input.clone()),
                                                    stream_id: None,
                                                };
                                                let mut status = status.clone();
                                                spawn(async move {
                                                    status.set(format!("Bootstrapping NBC auth for {}", input));
                                                    match tauri_invoke::<BootstrapNbcAuthResult, _>("bootstrap_nbc_auth", &args).await {
                                                        Ok(result) => {
                                                            if result.surfaced_auth {
                                                                status.set("NBC auth ready after interactive sign-in".to_string());
                                                            } else {
                                                                status.set("NBC auth ready in hidden mode".to_string());
                                                            }
                                                        }
                                                        Err(err) => {
                                                            status.set(format!("NBC bootstrap error: {err}"));
                                                        }
                                                    }
                                                });
                                            },
                                            "Bootstrap pasted NBC URL"
                                        }
                                    }
                                }
                            }
                            div { class: "source-menu-divider" }
                            div { class: "source-menu-section",
                                div { class: "source-menu-title", "Add stream" }
                                label { class: "source-menu-label", "Paste a link or device address" }
                                input {
                                    class: "source-menu-input",
                                    placeholder: "HDHomeRun IP, https://…, or linux-dvb",
                                    value: "{add_input.read()}",
                                    oninput: move |evt| {
                                        add_input.set(evt.value());
                                        add_probe.set(None);
                                        add_probe_input.set("".to_string());
                                    },
                                }
                                if let Some(probe) = add_probe.read().as_ref() {
                                    if probe.kind == "ytdlp" {
                                        div { class: "source-menu-subsection",
                                            label { class: "source-menu-label", "Format" }
                                            select {
                                                class: "source-menu-input",
                                                onchange: move |evt| add_format.set(evt.value()),
                                                {
                                                    if let Some(ytdlp) = probe.ytdlp.as_ref() {
                                                        if ytdlp.formats.is_empty() {
                                                            rsx! { option { value: "", "Default" } }
                                                        } else {
                                                            rsx!({
                                                                ytdlp.formats.iter().map(|format| {
                                                                    let selected = add_format.read().as_str() == format.format_id;
                                                                    rsx! {
                                                                        option {
                                                                            value: "{format.format_id}",
                                                                            selected: selected,
                                                                            "{format.label}"
                                                                        }
                                                                    }
                                                                })
                                                            })
                                                        }
                                                    } else {
                                                        rsx! { option { value: "", "Default" } }
                                                    }
                                                }
                                            }
                                            if probe
                                                .ytdlp
                                                .as_ref()
                                                .map(|ytdlp| ytdlp.supports_live_from_start)
                                                .unwrap_or(false)
                                            {
                                                label { class: "source-menu-toggle",
                                                    input {
                                                        r#type: "checkbox",
                                                        checked: *add_live_from_start.read(),
                                                        onchange: move |_| {
                                                            let current = *add_live_from_start.read();
                                                            add_live_from_start.set(!current);
                                                        }
                                                    }
                                                    span { "Live from start (when supported)" }
                                                }
                                            }
                                        }
                                    }
                                    if probe.kind == "linux-dvb" {
                                        div { class: "source-menu-subsection",
                                            if linux_adapters.read().is_empty() {
                                                div { class: "source-menu-status", "No Linux DVB adapters found on this machine" }
                                            }
                                            label { class: "source-menu-label", "Adapter" }
                                            select {
                                                class: "source-menu-input",
                                                onchange: move |evt| {
                                                    linux_adapter.set(evt.value());
                                                    linux_dvr.set("0".to_string());
                                                },
                                                {
                                                    linux_adapters.read().iter().map(|info| {
                                                        let value = info.adapter.to_string();
                                                        let selected = linux_adapter.read().as_str() == value;
                                                        rsx! {
                                                            option { value: "{value}", selected: selected, "adapter{info.adapter}" }
                                                        }
                                                    })
                                                }
                                            }
                                            label { class: "source-menu-label", "DVR" }
                                            select {
                                                class: "source-menu-input",
                                                onchange: move |evt| linux_dvr.set(evt.value()),
                                                {
                                                    let current_adapter = linux_adapter.read().parse::<u32>().unwrap_or(0);
                                                    if let Some(info) = linux_adapters.read().iter().find(|info| info.adapter == current_adapter) {
                                                        rsx!({
                                                            info.dvrs.iter().map(|dvr| {
                                                                let value = dvr.to_string();
                                                                let selected = linux_dvr.read().as_str() == value;
                                                                rsx! { option { value: "{value}", selected: selected, "dvr{dvr}" } }
                                                            })
                                                        })
                                                    } else {
                                                        rsx! { option { value: "0", "dvr0" } }
                                                    }
                                                }
                                            }
                                            label { class: "source-menu-label", "channels.conf (optional)" }
                                            input {
                                                class: "source-menu-input",
                                                placeholder: "/path/to/channels.conf",
                                                value: "{linux_channels_conf.read()}",
                                                oninput: move |evt| linux_channels_conf.set(evt.value()),
                                            }
                                            button {
                                                class: "source-menu-action",
                                                onclick: move |_| {
                                                    if !tauri_available() {
                                                        status.set("Tauri backend not available (open the Tauri app)".to_string());
                                                        return;
                                                    }
                                                    let conf = linux_channels_conf.read().trim().to_string();
                                                    let args = LinuxDvbListChannelsArgs {
                                                        channels_conf: if conf.is_empty() { None } else { Some(conf) },
                                                    };
                                                    let mut status = status.clone();
                                                    let mut linux_channels = linux_channels.clone();
                                                    let mut linux_channel = linux_channel.clone();
                                                    let mut linux_channels_conf = linux_channels_conf.clone();
                                                    spawn(async move {
                                                        match tauri_invoke::<LinuxDvbChannelsInfo, _>("linux_dvb_list_channels", &args).await {
                                                            Ok(info) => {
                                                                if let Some(path) = info.channels_conf.clone() {
                                                                    if linux_channels_conf.read().trim().is_empty() {
                                                                        linux_channels_conf.set(path);
                                                                    }
                                                                }
                                                                if linux_channel.read().trim().is_empty() && !info.channels.is_empty() {
                                                                    linux_channel.set(info.channels[0].clone());
                                                                }
                                                                linux_channels.set(info.channels);
                                                                status.set("Linux DVB channels loaded".to_string());
                                                            }
                                                            Err(err) => {
                                                                status.set(format!("Linux DVB channels error: {err}"));
                                                            }
                                                        }
                                                    });
                                                },
                                                "Load channels"
                                            }
                                            if !linux_channels.read().is_empty() {
                                                label { class: "source-menu-label", "Channel" }
                                                select {
                                                    class: "source-menu-input",
                                                    onchange: move |evt| linux_channel.set(evt.value()),
                                                    {
                                                        linux_channels.read().iter().map(|channel| {
                                                            let selected = linux_channel.read().as_str() == channel.as_str();
                                                            rsx! { option { value: "{channel}", selected: selected, "{channel}" } }
                                                        })
                                                    }
                                                }
                                            }
                                            label { class: "source-menu-label", "Tune wait (ms)" }
                                            input {
                                                class: "source-menu-input",
                                                placeholder: "800",
                                                value: "{linux_tune_wait.read()}",
                                                oninput: move |evt| linux_tune_wait.set(evt.value()),
                                            }
                                        }
                                    }
                                }
                                button {
                                    class: "source-menu-button",
                                    onclick: move |_| {
                                        let input = add_input.read().trim().to_string();
                                        if input.is_empty() {
                                            status.set("Stream input is required".to_string());
                                            return;
                                        }
                                        if !tauri_available() {
                                            status.set("Tauri backend not available (open the Tauri app)".to_string());
                                            return;
                                        }
                                        let mut status = status.clone();
                                        let mut streams = streams.clone();
                                        let mut sources = sources.clone();
                                        let mut source_status = source_status.clone();
                                        let mut add_input = add_input.clone();
                                        let mut add_probe = add_probe.clone();
                                        let mut add_probe_input = add_probe_input.clone();
                                        let mut add_format = add_format.clone();
                                        let mut add_live_from_start = add_live_from_start.clone();
                                        let mut linux_adapters = linux_adapters.clone();
                                        let mut linux_adapter = linux_adapter.clone();
                                        let mut linux_dvr = linux_dvr.clone();
                                        let mut linux_channels_conf = linux_channels_conf.clone();
                                        let mut linux_channels = linux_channels.clone();
                                        let mut linux_channel = linux_channel.clone();
                                        let mut linux_tune_wait = linux_tune_wait.clone();
                                        let input_clone = input.clone();
                                        spawn(async move {
                                            let existing_probe = add_probe.read().clone();
                                            if let Some(probe) = existing_probe {
                                                if probe.kind == "ytdlp"
                                                    && probe.requires_options
                                                    && add_probe_input.read().as_str() == input_clone
                                                {
                                                    let format_value = add_format.read().trim().to_string();
                                                    let options = ManualSourceOptions {
                                                        ytdlp_format: if format_value.is_empty() { None } else { Some(format_value) },
                                                        ytdlp_live_from_start: *add_live_from_start.read(),
                                                    };
                                                    status.set("Adding stream".to_string());
                                                    let args = AddStreamArgs {
                                                        input: input_clone.clone(),
                                                        options: Some(options),
                                                    };
                                                    match tauri_invoke::<AddStreamResult, _>("add_stream", &args).await {
                                                        Ok(result) => {
                                                            status.set(format!("Added {} ({})", result.added, result.kind));
                                                            add_input.set("".to_string());
                                                            add_probe.set(None);
                                                            add_probe_input.set("".to_string());
                                                            add_format.set("".to_string());
                                                            add_live_from_start.set(false);
                                                            if let Ok(list) = tauri_invoke::<Vec<StreamDescriptor>, _>("list_streams", &EmptyArgs {}).await {
                                                                streams.set(list);
                                                            }
                                                            if let Ok(list) = tauri_invoke::<Vec<SourceDescriptor>, _>("list_sources", &EmptyArgs {}).await {
                                                                source_status.set(format!("{} sources online", list.len()));
                                                                sources.set(list);
                                                            }
                                                        }
                                                        Err(err) => {
                                                            status.set(format!("Add stream error: {err}"));
                                                        }
                                                    }
                                                    return;
                                                }
                                                if probe.kind == "linux-dvb"
                                                    && probe.requires_options
                                                    && add_probe_input.read().as_str() == input_clone
                                                {
                                                    let adapter = linux_adapter
                                                        .read()
                                                        .trim()
                                                        .parse::<u32>()
                                                        .unwrap_or(0);
                                                    let dvr = linux_dvr
                                                        .read()
                                                        .trim()
                                                        .parse::<u32>()
                                                        .unwrap_or(0);
                                                    let conf = linux_channels_conf.read().trim().to_string();
                                                    let channel = linux_channel.read().trim().to_string();
                                                    let tune_wait_ms = linux_tune_wait
                                                        .read()
                                                        .trim()
                                                        .parse::<u64>()
                                                        .ok();
                                                    let build_args = LinuxDvbBuildUrlArgs {
                                                        adapter,
                                                        dvr,
                                                        channel: if channel.is_empty() { None } else { Some(channel) },
                                                        channels_conf: if conf.is_empty() { None } else { Some(conf) },
                                                        tune_wait_ms,
                                                    };
                                                    status.set("Building Linux DVB stream".to_string());
                                                    let url = match tauri_invoke::<String, _>("linux_dvb_build_url", &build_args).await {
                                                        Ok(url) => url,
                                                        Err(err) => {
                                                            status.set(format!("Linux DVB build error: {err}"));
                                                            return;
                                                        }
                                                    };
                                                    status.set("Adding stream".to_string());
                                                    let args = AddStreamArgs {
                                                        input: url,
                                                        options: None,
                                                    };
                                                    match tauri_invoke::<AddStreamResult, _>("add_stream", &args).await {
                                                        Ok(result) => {
                                                            status.set(format!("Added {} ({})", result.added, result.kind));
                                                            add_input.set("".to_string());
                                                            add_probe.set(None);
                                                            add_probe_input.set("".to_string());
                                                            add_format.set("".to_string());
                                                            add_live_from_start.set(false);
                                                            if let Ok(list) = tauri_invoke::<Vec<StreamDescriptor>, _>("list_streams", &EmptyArgs {}).await {
                                                                streams.set(list);
                                                            }
                                                            if let Ok(list) = tauri_invoke::<Vec<SourceDescriptor>, _>("list_sources", &EmptyArgs {}).await {
                                                                source_status.set(format!("{} sources online", list.len()));
                                                                sources.set(list);
                                                            }
                                                        }
                                                        Err(err) => {
                                                            status.set(format!("Add stream error: {err}"));
                                                        }
                                                    }
                                                    return;
                                                }
                                            }
                                            status.set("Checking stream".to_string());
                                            let probe_args = ProbeStreamArgs { input: input_clone.clone() };
                                            match tauri_invoke::<StreamProbe, _>("probe_stream", &probe_args).await {
                                                Ok(probe) => {
                                                    if probe.kind == "ytdlp" && probe.requires_options {
                                                        if let Some(ytdlp) = probe.ytdlp.as_ref() {
                                                            if let Some(default_format) = ytdlp.default_format.as_ref() {
                                                                add_format.set(default_format.clone());
                                                            } else {
                                                                add_format.set("".to_string());
                                                            }
                                                            add_live_from_start.set(false);
                                                        }
                                                        add_probe_input.set(input_clone);
                                                        add_probe.set(Some(probe));
                                                        status.set("Select yt-dlp options".to_string());
                                                        return;
                                                    }
                                                    if probe.kind == "linux-dvb" && probe.requires_options {
                                                        status.set("Loading Linux DVB devices".to_string());
                                                        match tauri_invoke::<Vec<LinuxDvbAdapterInfo>, _>("linux_dvb_list_adapters", &EmptyArgs {}).await {
                                                            Ok(list) => {
                                                                linux_adapters.set(list.clone());
                                                                if let Some(first) = list.first() {
                                                                    linux_adapter.set(first.adapter.to_string());
                                                                    if let Some(dvr) = first.dvrs.first() {
                                                                        linux_dvr.set(dvr.to_string());
                                                                    } else {
                                                                        linux_dvr.set("0".to_string());
                                                                    }
                                                                }
                                                            }
                                                            Err(err) => {
                                                                status.set(format!("Linux DVB devices error: {err}"));
                                                            }
                                                        }
                                                        let conf_args = LinuxDvbListChannelsArgs { channels_conf: None };
                                                        match tauri_invoke::<LinuxDvbChannelsInfo, _>("linux_dvb_list_channels", &conf_args).await {
                                                            Ok(info) => {
                                                                if let Some(path) = info.channels_conf.clone() {
                                                                    linux_channels_conf.set(path);
                                                                }
                                                                if !info.channels.is_empty() {
                                                                    linux_channel.set(info.channels[0].clone());
                                                                }
                                                                linux_channels.set(info.channels);
                                                            }
                                                            Err(err) => {
                                                                status.set(format!("Linux DVB channels error: {err}"));
                                                            }
                                                        }
                                                        add_probe_input.set(input_clone);
                                                        add_probe.set(Some(probe));
                                                        status.set("Select Linux DVB options".to_string());
                                                        return;
                                                    }

                                                    let args = AddStreamArgs {
                                                        input: input_clone.clone(),
                                                        options: None,
                                                    };
                                                    match tauri_invoke::<AddStreamResult, _>("add_stream", &args).await {
                                                        Ok(result) => {
                                                            status.set(format!("Added {} ({})", result.added, result.kind));
                                                            add_input.set("".to_string());
                                                            add_probe.set(None);
                                                            add_probe_input.set("".to_string());
                                                            add_format.set("".to_string());
                                                            add_live_from_start.set(false);
                                                            if let Ok(list) = tauri_invoke::<Vec<StreamDescriptor>, _>("list_streams", &EmptyArgs {}).await {
                                                                streams.set(list);
                                                            }
                                                            if let Ok(list) = tauri_invoke::<Vec<SourceDescriptor>, _>("list_sources", &EmptyArgs {}).await {
                                                                source_status.set(format!("{} sources online", list.len()));
                                                                sources.set(list);
                                                            }
                                                        }
                                                        Err(err) => {
                                                            status.set(format!("Add stream error: {err}"));
                                                        }
                                                    }
                                                }
                                                Err(err) => {
                                                    status.set(format!("Probe error: {err}"));
                                                    add_probe.set(None);
                                                    add_probe_input.set("".to_string());
                                                }
                                            }
                                        });
                                    },
                                    if add_probe.read().is_some() { "Start" } else { "Add" }
                                }
                            }
                            div { class: "source-menu-divider" }
                            div { class: "source-menu-section",
                                div { class: "source-menu-title", "Watch a link" }
                                label { class: "source-menu-label", "Link" }
                                input {
                                    class: "source-menu-input",
                                    placeholder: "every.channel://watch?...",
                                    value: "{moq_link.read()}",
                                    oninput: move |evt| moq_link.set(evt.value()),
                                }
                                button {
                                    class: "source-menu-action",
                                    onclick: move |_| {
                                            let link = moq_link.read().trim().to_string();
                                            direct_offer.set(None);
                                            direct_reply.set("".to_string());
                                            direct_answer_stream_id.set(None);
                                            if let Some(parsed) = parse_watch_link(&link) {
                                                moq_remote.set(parsed.remote);
                                                moq_broadcast.set(parsed.broadcast);
                                                if let Some(track) = parsed.track {
                                                    moq_track.set(track);
                                                }
                                                if let Some(stream_id) = parsed.stream_id {
                                                    moq_stream_id.set(stream_id);
                                                }
                                                if let Some(secret) = parsed.network_secret {
                                                    moq_secret.set(secret);
                                                }
                                                if let Some(discovery) = parsed.discovery {
                                                    let d = discovery.to_ascii_lowercase();
                                                    discovery_dht.set(d.contains("dht"));
                                                    discovery_mdns.set(d.contains("mdns"));
                                                    discovery_dns.set(d.contains("dns"));
                                                }
                                                status.set("Link parsed (ready to watch)".to_string());
                                                return;
                                            }
                                            if let Some(offer) = parse_direct_link(&link) {
                                                direct_offer.set(Some(offer));
                                                status.set("Link parsed (reply required)".to_string());
                                                return;
                                            }
                                            status.set("That link does not look valid".to_string());
                                        },
                                    "Parse link"
                                }
                                label { class: "toggle",
                                    input {
                                        r#type: "checkbox",
                                        checked: {*moq_link_advanced.read()},
                                        onclick: move |_| {
                                            let next = !*moq_link_advanced.read();
                                            moq_link_advanced.set(next);
                                        },
                                    }
                                    span { "Show details" }
                                }
                                if *moq_link_advanced.read() {
                                    label { class: "source-menu-label", "Address" }
                                    input {
                                        class: "source-menu-input",
                                        placeholder: "auto-filled from link",
                                        value: "{moq_remote.read()}",
                                        oninput: move |evt| moq_remote.set(evt.value()),
                                    }
                                    label { class: "source-menu-label", "Channel" }
                                    input {
                                        class: "source-menu-input",
                                        placeholder: "auto-filled from link",
                                        value: "{moq_broadcast.read()}",
                                        oninput: move |evt| moq_broadcast.set(evt.value()),
                                    }
                                    label { class: "source-menu-label", "Channel ID (optional)" }
                                    input {
                                        class: "source-menu-input",
                                        placeholder: "optional",
                                        value: "{moq_stream_id.read()}",
                                        oninput: move |evt| moq_stream_id.set(evt.value()),
                                    }
                                    label { class: "source-menu-label", "Segment (optional)" }
                                    input {
                                        class: "source-menu-input",
                                        placeholder: "optional",
                                        value: "{moq_track.read()}",
                                        oninput: move |evt| moq_track.set(evt.value()),
                                    }
                                    label { class: "source-menu-label", "Sharing key (optional)" }
                                    input {
                                        class: "source-menu-input",
                                        placeholder: "optional",
                                        value: "{moq_secret.read()}",
                                        oninput: move |evt| moq_secret.set(evt.value()),
                                    }
                                }
                                label { class: "source-menu-label", "Auto quality" }
                                label { class: "toggle",
                                    input {
                                        r#type: "checkbox",
                                        checked: {*moq_auto_quality.read()},
                                        onclick: move |_| moq_auto_quality.toggle(),
                                    }
                                    span { "Let the player pick quality" }
                                }
                                label { class: "source-menu-label", "Quality" }
                                select {
                                    class: "source-menu-input",
                                    value: "{moq_quality.read()}",
                                    disabled: {*moq_auto_quality.read()},
                                    onchange: move |evt| moq_quality.set(evt.value()),
                                    option { value: "1080p", "1080p" }
                                    option { value: "720p", "720p" }
                                    option { value: "480p", "480p" }
                                }
                                button {
                                    class: "source-menu-button",
                                    onclick: move |_| {
                                        let remote = moq_remote.read().clone();
                                        let broadcast = moq_broadcast.read().clone();
                                        // Direct connect does not require these fields.
                                        if direct_offer.read().is_none() && (remote.is_empty() || broadcast.is_empty()) {
                                            status.set("Missing info: paste a link first".to_string());
                                            return;
                                        }
                                        let stream_override = moq_stream_id.read().clone();
                                        let track = moq_track.read().clone();
                                        let auto_quality = *moq_auto_quality.read();
                                        let quality = moq_quality.read().clone();
                                        let secret = moq_secret.read().clone();
                                        let discovery = discovery_string(
                                            *discovery_dht.read(),
                                            *discovery_mdns.read(),
                                            *discovery_dns.read(),
                                        );
                                        let mut selected = selected.clone();
                                        let mut playback = playback.clone();
                                        let mut status = status.clone();
                                        let mut direct_reply = direct_reply.clone();
                                        let direct_answer_stream_id = direct_answer_stream_id.read().clone();
                                        let direct_offer = direct_offer.read().clone();
                                        spawn(async move {
                                            if let Some(offer) = direct_offer {
                                                status.set("Preparing reply".to_string());
                                                let cfg = fetch_turn_peer_config().await;
                                                let pc = match PeerConnectionBuilder::new()
                                                    .set_config(cfg)
                                                    .with_remote_offer(Some(offer.desc.clone()))
                                                {
                                                    Ok(b) => match b.build().await {
                                                        Ok(pc) => pc,
                                                        Err(err) => {
                                                            status.set(format!("Error: {err}"));
                                                            return;
                                                        }
                                                    },
                                                    Err(err) => {
                                                        status.set(format!("Error: {err}"));
                                                        return;
                                                    }
                                                };
                                                if let Err(err) = pc.add_ice_candidates(offer.candidates.clone()).await {
                                                    status.set(format!("Error: {err}"));
                                                    return;
                                                }
                                                let desc = match pc.get_local_description().await {
                                                    Some(d) => d,
                                                    None => {
                                                        status.set("Error: no reply description".to_string());
                                                        return;
                                                    }
                                                };
                                                let candidates = match pc.collect_ice_candidates().await {
                                                    Ok(c) => c,
                                                    Err(err) => {
                                                        status.set(format!("Error: {err}"));
                                                        return;
                                                    }
                                                };
                                                let reply = match encode_direct_link(&DirectCodeV1 { v: 1, desc, candidates, label: Some("every.channel0".to_string()) }) {
                                                    Ok(v) => v,
                                                    Err(err) => {
                                                        status.set(format!("Error: {err}"));
                                                        return;
                                                    }
                                                };
                                                direct_reply.set(reply);
                                                if let Some(stream_id) = direct_answer_stream_id.as_ref() {
                                                    // Send answer back to the directory/signaling API so the publisher can connect.
                                                    let payload = serde_json::json!({
                                                        "stream_id": stream_id,
                                                        "answer": direct_reply.read().clone(),
                                                    });
                                                    if let Err(err) = post_json("/api/answer", &payload).await {
                                                        status.set(format!("Reply send failed: {err}"));
                                                        return;
                                                    }
                                                }

                                                status.set("Waiting for video".to_string());
                                                let ch = match pc.receive_channel().await {
                                                    Ok(ch) => ch,
                                                    Err(err) => {
                                                        status.set(format!("Error: {err}"));
                                                        return;
                                                    }
                                                };
                                                ch.wait_ready().await;

                                                let (ms, url) = match create_mse_url() {
                                                    Ok(v) => v,
                                                    Err(err) => {
                                                        status.set(format!("Error: {err}"));
                                                        return;
                                                    }
                                                };
                                                let sink = Rc::new(RefCell::new(MseSink {
                                                    ms: ms.clone(),
                                                    sb: None,
                                                    is_open: false,
                                                    updateend_installed: false,
                                                    queue: VecDeque::new(),
                                                }));
                                                install_sourceopen(&ms, sink.clone());

                                                playback.set(Some(PlaybackInfo { stream_id: "direct".to_string(), url: url.clone() }));
                                                selected.set(Some(StreamDescriptor {
                                                    id: "direct".to_string(),
                                                    title: "Direct".to_string(),
                                                    number: None,
                                                    source: "direct".to_string(),
                                                    metadata: vec![],
                                                }));

                                                // Receive init + segments over the data channel and append to MSE.
                                                let mut ch = ch;
                                                spawn(async move {
                                                    use futures_util::future::FutureExt;
                                                    use gloo_timers::future::TimeoutFuture;

                                                    let mut decoder = DirectWireDecoder::default();
                                                    let mut next_ping = TimeoutFuture::new(1000).fuse();
                                                    loop {
                                                        let msg: Bytes = futures_util::select! {
                                                            _ = next_ping => {
                                                                let _ = ch.send(&Bytes::from(vec![DIRECT_WIRE_TAG_PING])).await;
                                                                next_ping = TimeoutFuture::new(1000).fuse();
                                                                continue;
                                                            }
                                                            msg = ch.receive().fuse() => {
                                                                match msg {
                                                                    Ok(b) => b,
                                                                    Err(_) => break,
                                                                }
                                                            }
                                                        };
                                                        for frame in decoder.push(&msg) {
                                                            let Some((meta, data)) =
                                                                decode_object_frame(&frame)
                                                            else {
                                                                continue;
                                                            };
                                                            if meta.content_type.starts_with("video/mp4") {
                                                                let _ = try_create_source_buffer(sink.clone(), &data);
                                                                let mut st = sink.borrow_mut();
                                                                append_bytes(&mut st, data);
                                                            } else if meta.content_type.contains("iso.segment") {
                                                                let mut st = sink.borrow_mut();
                                                                append_bytes(&mut st, data);
                                                            }
                                                        }
                                                    }
                                                });

                                                status.set("Live".to_string());
                                                return;
                                            }

                                            if !tauri_available() {
                                                status.set("Tauri backend not available (open the Tauri app)".to_string());
                                                return;
                                            }

                                            status.set("Connecting".to_string());
                                            let remote_meta = remote.clone();
                                            let args = MoqStartArgs {
                                                remote: remote.clone(),
                                                broadcast_name: broadcast.clone(),
                                                stream_id: if stream_override.is_empty() { None } else { Some(stream_override.clone()) },
                                                track_name: if track.is_empty() { None } else { Some(track.clone()) },
                                                auto_quality: Some(auto_quality),
                                                variant: if auto_quality { None } else { Some(quality.clone()) },
                                                network_secret: if secret.is_empty() { None } else { Some(secret.clone()) },
                                                discovery,
                                            };
                                            match tauri_invoke::<PlaybackInfo, _>("start_moq_stream", &args).await {
                                                Ok(info) => {
                                                    status.set("Live".to_string());
                                                    playback.set(Some(info));
                                                    let stream_id = if stream_override.is_empty() {
                                                        broadcast.clone()
                                                    } else {
                                                        stream_override.clone()
                                                    };
                                                    let descriptor = StreamDescriptor {
                                                        id: stream_id.clone(),
                                                        title: stream_id.clone(),
                                                        number: None,
                                                        source: "link".to_string(),
                                                        metadata: vec![
                                                            StreamMetadata { key: "broadcast".to_string(), value: broadcast.clone() },
                                                            StreamMetadata { key: "remote".to_string(), value: remote_meta.clone() },
                                                            StreamMetadata { key: "track".to_string(), value: track.clone() },
                                                        ],
                                                    };
                                                    selected.set(Some(descriptor));
                                                }
                                                Err(err) => {
                                                    status.set(format!("Error: {err}"));
                                                }
                                            }
                                        });
                                    },
                                    "Tune in"
                                }
                                if !direct_reply.read().is_empty() {
                                    div { class: "source-menu-item",
                                        span { "Reply" }
                                        span { "Send this back to the streamer" }
                                    }
                                    div { class: "source-menu-inline",
                                        input {
                                            class: "source-menu-input",
                                            value: "{direct_reply.read()}",
                                            readonly: true,
                                        }
                                        button {
                                            class: "source-menu-action small",
                                            onclick: move |_| {
                                                let value = direct_reply.read().clone();
                                                let mut status = status.clone();
                                                spawn(async move {
                                                    match copy_to_clipboard(value).await {
                                                        Ok(_) => status.set("Copied reply".to_string()),
                                                        Err(err) => status.set(format!("Copy failed: {err}")),
                                                    }
                                                });
                                            },
                                            "Copy"
                                        }
                                    }
                                }
                            }
                            div { class: "source-menu-divider" }
                            div { class: "source-menu-section",
                                div { class: "source-menu-title", "Reach" }
                                div { class: "source-menu-status", "How others can find you" }
                                label { class: "toggle",
                                    input {
                                        r#type: "checkbox",
                                        checked: {*discovery_dht.read()},
                                        onclick: move |_| {
                                            let next = !*discovery_dht.read();
                                            discovery_dht.set(next);
                                        },
                                    }
                                    span { "Public (internet)" }
                                }
                                label { class: "toggle",
                                    input {
                                        r#type: "checkbox",
                                        checked: {*discovery_mdns.read()},
                                        onclick: move |_| {
                                            let next = !*discovery_mdns.read();
                                            discovery_mdns.set(next);
                                        },
                                    }
                                    span { "Nearby (same Wi-Fi)" }
                                }
                                label { class: "toggle",
                                    input {
                                        r#type: "checkbox",
                                        checked: {*discovery_dns.read()},
                                        onclick: move |_| {
                                            let next = !*discovery_dns.read();
                                            discovery_dns.set(next);
                                        },
                                    }
                                    span { "Experimental DNS" }
                                }
                            }
                        }
                    }
                    div { class: "status-pill",
                        div { class: "status-dot" }
                        span { "{status.read()}" }
                    }
                }
            }
            main { class: "grid",
                div { class: "left-column",
                    section { class: "panel",
                            div { class: "panel-header",
                                div { class: "panel-title", "Channels" }
                                div { class: "panel-actions",
                                    select {
                                        class: "panel-select",
                                        value: "{stream_filter.read()}",
                                        onchange: move |evt| {
                                            stream_filter.set(evt.value());
                                            page.set(0);
                                        },
                                        option { value: "all", "All" }
                                        option { value: "hdhr", "HDHR" }
                                        option { value: "linux-dvb", "Linux DVB" }
                                        option { value: "hls", "HLS" }
                                        option { value: "ytdlp", "yt-dlp" }
                                        option { value: "nbc", "NBC" }
                                        option { value: "moq", "Link" }
                                    }
                                    button {
                                        class: "panel-button",
                                        onclick: move |_| refresh_streams(),
                                        "Refresh"
                                }
                                div { class: "pager",
                                    button {
                                        class: "pager-button",
                                        onclick: move |_| {
                                            if clamped_page > 0 {
                                                page.set(clamped_page - 1);
                                            }
                                        },
                                        "Prev"
                                    }
                                    span { class: "pager-label", "{clamped_page + 1} / {page_count}" }
                                    button {
                                        class: "pager-button",
                                        onclick: move |_| {
                                            if clamped_page + 1 < page_count {
                                                page.set(clamped_page + 1);
                                            }
                                        },
                                        "Next"
                                    }
                                }
                            }
                        }
                        div { class: "channel-list",
                            {
                                let active_id = active_id.clone();
                                let mut selected = selected.clone();
                                let mut playback = playback.clone();
                                let mut status = status.clone();
                                let mut share_info = share_info.clone();
                                let discovery_dht = discovery_dht.clone();
                                let discovery_mdns = discovery_mdns.clone();
                                let discovery_dns = discovery_dns.clone();
                                paged_streams.iter().map(move |stream| {
                                    let is_active = active_id
                                        .as_ref()
                                        .map(|id| id == &stream.id)
                                        .unwrap_or(false);
                                    let card_class = if is_active { "channel-card active" } else { "channel-card" };
                                    let channel_subtitle = stream_display_subtitle(stream);
                                    let channel_detail = stream_display_detail(stream);
                                    let moq_endpoint = stream
                                        .metadata
                                        .iter()
                                        .find(|m| m.key == "moq_endpoint")
                                        .map(|m| m.value.clone());
                                    let moq_broadcast = stream
                                        .metadata
                                        .iter()
                                        .find(|m| m.key == "moq_broadcast")
                                        .map(|m| m.value.clone());
                                    let moq_track = stream
                                        .metadata
                                        .iter()
                                        .find(|m| m.key == "moq_track")
                                        .map(|m| m.value.clone());
                                    let moq_key_id = stream
                                        .metadata
                                        .iter()
                                        .find(|m| m.key == "moq_key_id")
                                        .map(|m| m.value.clone());
                                    let stream_clone = stream.clone();
                                    let mut selected = selected.clone();
                                    let mut playback = playback.clone();
                                    let mut status = status.clone();
                                    let mut share_info = share_info.clone();
                                    let has_drm = stream_has_drm(&stream.metadata);
                                    rsx! {
                                        div {
                                            class: "{card_class}",
                                            onclick: move |_| {
                                                selected.set(Some(stream_clone.clone()));
                                                let stream_id = stream_clone.id.clone();
                                                let stream_title = stream_clone.title.clone();
                                                status.set(format!("Starting {}", stream_title));
                                                share_info.set(None);
                                                let mut playback = playback.clone();
                                                let mut status = status.clone();
                                                let moq_endpoint = moq_endpoint.clone();
                                                let moq_broadcast = moq_broadcast.clone();
                                                let moq_track = moq_track.clone();
                                                let moq_key_id = moq_key_id.clone();
                                                spawn(async move {
                                                    if let (Some(endpoint), Some(broadcast)) = (moq_endpoint, moq_broadcast) {
                                                        let discovery = discovery_string(
                                                            *discovery_dht.read(),
                                                            *discovery_mdns.read(),
                                                            *discovery_dns.read(),
                                                        );
                                                        let args = MoqStartArgs {
                                                            remote: endpoint.clone(),
                                                            broadcast_name: broadcast.clone(),
                                                            stream_id: moq_key_id.or(Some(stream_id.clone())),
                                                            track_name: moq_track,
                                                            auto_quality: Some(true),
                                                            variant: None,
                                                            network_secret: None,
                                                            discovery,
                                                        };
                                                        match tauri_invoke::<PlaybackInfo, _>("start_moq_stream", &args).await {
                                                            Ok(info) => {
                                                                status.set(format!("Live: {}", stream_title));
                                                                playback.set(Some(info));
                                                            }
                                                            Err(err) => {
                                                                status.set(format!("Link error: {err}"));
                                                            }
                                                        }
                                                    } else {
                                                        let args = StartArgs { stream_id: stream_id.clone() };
                                                        match tauri_invoke::<PlaybackInfo, _>("start_stream", &args).await {
                                                            Ok(info) => {
                                                                status.set(format!("Live: {}", stream_title));
                                                                playback.set(Some(info));
                                                            }
                                                            Err(err) => {
                                                                status.set(format!("Stream error: {err}"));
                                                            }
                                                        }
                                                    }
                                                });
                                            },
                                            div { class: "channel-title", "{stream.title}" }
                                            if let Some(channel_subtitle) = channel_subtitle.clone() {
                                                div { class: "channel-meta", "{channel_subtitle}" }
                                            }
                                            if let Some(channel_detail) = channel_detail.clone() {
                                                div { class: "channel-detail", "{channel_detail}" }
                                            }
                                            if !stream.source.is_empty() {
                                                div { class: "channel-badge source", "{stream.source}" }
                                            }
                                            if has_drm {
                                                div { class: "channel-badge drm", "Protected" }
                                            }
                                        }
                                    }
                                })
                            }
                        }
                    }
                    section { class: "panel",
                        div { class: "panel-title", "Devices" }
                        div { class: "source-status", "{source_status.read()}" }
                        div { class: "source-list",
                            {
                                source_list.iter().map(|source| {
                                    let status_label = format!(
                                        "{} - {}",
                                        source.kind.to_uppercase(),
                                        source.status
                                    );
                                    let ip_label = source
                                        .ip
                                        .clone()
                                        .unwrap_or_else(|| "Unknown IP".to_string());
                                    let tuner_label = source
                                        .tuner_count
                                        .map(|tuners| format!("{tuners} tuners"))
                                        .unwrap_or_else(|| "Tuners unknown".to_string());
                                    rsx! {
                                        div { class: "source-card",
                                            div { class: "source-name", "{source.name}" }
                                            div { class: "source-meta",
                                                "{status_label}"
                                            }
                                            div { class: "source-meta",
                                                "{ip_label}"
                                            }
                                            div { class: "source-meta",
                                                "{tuner_label}"
                                            }
                                        }
                                    }
                                })
                            }
                        }
                        div { class: "catalog-panel",
                            div { class: "moq-title", "Live channels" }
                            if tauri_available() {
                                label { class: "moq-label", "Friends (optional, comma separated)" }
                                input {
                                    class: "moq-input",
                                    placeholder: "optional",
                                    value: "{catalog_peers.read()}",
                                    oninput: move |evt| catalog_peers.set(evt.value()),
                                }
                                button {
                                    class: "moq-button",
                                    onclick: move |_| {
                                        let peers = catalog_peers
                                            .read()
                                            .split(',')
                                            .map(|p| p.trim().to_string())
                                            .filter(|p| !p.is_empty())
                                            .collect::<Vec<_>>();
                                        let discovery = discovery_string(
                                            *discovery_dht.read(),
                                            *discovery_mdns.read(),
                                            *discovery_dns.read(),
                                        );
                                        let mut status = status.clone();
                                        spawn(async move {
                                            let args = CatalogWatchArgs { peers, discovery };
                                            match tauri_invoke::<(), _>("start_catalog_watch", &args).await {
                                                Ok(_) => status.set("Searching for live channels...".to_string()),
                                                Err(err) => status.set(format!("Search error: {err}")),
                                            }
                                        });
                                    },
                                    "Search"
                                }
                            } else {
                                div { class: "source-status", "{global_directory_status.read()}" }
                                button {
                                    class: "moq-button",
                                    "data-testid": "global-refresh",
                                    onclick: move |_| refresh_global_directory(),
                                    if *global_directory_loading.read() { "Refreshing..." } else { "Refresh" }
                                }
                                div { class: "source-list",
                                    {global_directory.read().iter().map(|entry| {
                                        let stream_id = entry.stream_id.clone();
                                        let title = entry.title.clone();
                                        let mut status = status.clone();
                                        let mut playback = playback.clone();
                                        let mut selected = selected.clone();
                                        rsx! {
                                            div { class: "source-card",
                                                div { class: "source-name", "{title}" }
                                                div { class: "source-meta", "{stream_id}" }
                                                button {
                                                    class: "panel-button",
                                                    "data-stream-id": "{stream_id}",
                                                    "data-testid": "global-watch",
                                                    onclick: move |_| {
                                                        let mut status = status.clone();
                                                        let mut playback = playback.clone();
                                                        let mut selected = selected.clone();
                                                        let sid = stream_id.clone();
                                                        let stitle = title.clone();
                                                        spawn(async move {
                                                            status.set("Connecting".to_string());

                                                            let (ms, url) = match create_mse_url() {
                                                                Ok(v) => v,
                                                                Err(err) => {
                                                                    status.set(format!("Error: {err}"));
                                                                    return;
                                                                }
                                                            };
                                                            let sink = Rc::new(RefCell::new(MseSink {
                                                                ms: ms.clone(),
                                                                sb: None,
                                                                is_open: false,
                                                                updateend_installed: false,
                                                                queue: VecDeque::new(),
                                                            }));
                                                            install_sourceopen(&ms, sink.clone());

                                                            playback.set(Some(PlaybackInfo { stream_id: sid.clone(), url: url.clone() }));
                                                            selected.set(Some(StreamDescriptor {
                                                                id: sid.clone(),
                                                                title: stitle,
                                                                number: None,
                                                                source: "live".to_string(),
                                                                metadata: vec![],
                                                            }));

                                                            spawn(async move {
                                                                use futures_util::future::FutureExt;
                                                                use futures_util::{SinkExt, StreamExt};
                                                                use gloo_timers::future::TimeoutFuture;
                                                                use gloo_net::websocket::{futures::WebSocket, Message};

                                                                let sid_enc = js_sys::encode_uri_component(&sid)
                                                                    .as_string()
                                                                    .unwrap_or_else(|| sid.clone());
                                                                let ws_url = match ws_url_for_path(&format!(
                                                                    "/api/stream/ws?stream_id={}&role=sub",
                                                                    sid_enc
                                                                )) {
                                                                    Ok(u) => u,
                                                                    Err(err) => {
                                                                        status.set(format!("WebSocket URL error: {err}"));
                                                                        return;
                                                                    }
                                                                };
                                                                let mut ws = match WebSocket::open(&ws_url) {
                                                                    Ok(ws) => ws,
                                                                    Err(_) => {
                                                                        status.set("WebSocket connect failed".to_string());
                                                                        return;
                                                                    }
                                                                };

                                                                let mut decoder = DirectWireDecoder::default();
                                                                let mut next_ping = TimeoutFuture::new(1000).fuse();
                                                                loop {
                                                                    let msg: Vec<u8> = futures_util::select! {
                                                                        _ = next_ping => {
                                                                            // Keep the relay connection alive (optional).
                                                                            let _ = ws.send(Message::Bytes(vec![DIRECT_WIRE_TAG_PING])).await;
                                                                            next_ping = TimeoutFuture::new(1000).fuse();
                                                                            continue;
                                                                        }
                                                                        msg = ws.next().fuse() => {
                                                                            match msg {
                                                                                Some(Ok(Message::Bytes(b))) => b,
                                                                                Some(Ok(_)) => continue,
                                                                                Some(Err(_)) => break,
                                                                                None => break,
                                                                            }
                                                                        }
                                                                    };
                                                                    for frame in decoder.push(&msg) {
                                                                        let Some((meta, data)) =
                                                                            decode_object_frame(&frame)
                                                                        else {
                                                                            continue;
                                                                        };
                                                                        if meta.content_type.starts_with("video/mp4") {
                                                                            let _ = try_create_source_buffer(sink.clone(), &data);
                                                                            let mut st = sink.borrow_mut();
                                                                            append_bytes(&mut st, data);
                                                                        } else if meta.content_type.contains("iso.segment") {
                                                                            let mut st = sink.borrow_mut();
                                                                            append_bytes(&mut st, data);
                                                                        }
                                                                    }
                                                                }
                                                            });

                                                            status.set("Live".to_string());
                                                        });
                                                    },
                                                    "Watch"
                                                }
                                            }
                                        }
                                    })}
                                }
                            }
                        }
                    }
                }
                section { class: "panel",
                        div { class: "panel-header",
                            div { class: "panel-title", "Now Playing" }
                        if let Some(stream) = now_playing.clone() {
                            button {
                                class: "panel-button",
                                onclick: move |_| {
                                    if !tauri_available() {
                                        status.set("Tauri backend not available (open the Tauri app)".to_string());
                                        return;
                                    }
                                    let discovery = discovery_string(
                                        *discovery_dht.read(),
                                        *discovery_mdns.read(),
                                        *discovery_dns.read(),
                                    );
                                    let peers = share_peers
                                        .read()
                                        .split(',')
                                        .map(|peer| peer.trim().to_string())
                                        .filter(|peer| !peer.is_empty())
                                        .collect::<Vec<_>>();
                                    let secret = share_secret.read().trim().to_string();
                                    let args = ShareArgs {
                                        stream_id: stream.id.clone(),
                                        network_secret: if secret.is_empty() { None } else { Some(secret) },
                                        chunk_ms: None,
                                        announce: *share_announce.read(),
                                        gossip_peers: peers,
                                        discovery,
                                    };
                                    let mut share_info = share_info.clone();
                                    let mut status = status.clone();
                                    spawn(async move {
                                        status.set("Preparing link".to_string());
                                        match tauri_invoke::<ShareInfo, _>("start_moq_publish", &args).await {
                                            Ok(info) => {
                                                if let Some(announce) = info.announce_status.as_ref() {
                                                    status.set(format!("Ready ({announce})"));
                                                } else {
                                                    status.set("Ready".to_string());
                                                }
                                                share_info.set(Some(info));
                                            }
                                            Err(err) => {
                                                status.set(format!("Error: {err}"));
                                            }
                                        }
                                    });
                                },
                                "Share this channel"
                            }
                        }
                    }
                    div { class: "player-shell",
                        div { class: "video-frame",
                            if let Some(url) = playback_url {
                                video {
                                    src: "{url}",
                                    controls: true,
                                    autoplay: true,
                                    playsinline: true,
                                }
                            } else {
                                div { class: "placeholder", "Select a channel to start playback" }
                            }
                        }
                        if let Some(stream) = now_playing {
                            div { class: "meta-grid",
                                div { class: "meta-card",
                                    strong { "Channel" }
                                    span { "{stream.title}" }
                                }
                                div { class: "meta-card",
                                    strong { "Number" }
                                    span { {stream.number.clone().unwrap_or_else(|| "-".to_string())} }
                                }
                                if stream_has_drm(&stream.metadata) {
                                    div { class: "meta-card drm",
                                        strong { "Protected" }
                                        span { "Likely protected" }
                                    }
                                }
                                div { class: "meta-card",
                                    strong { "Sharing" }
                                    label { class: "toggle",
                                        input {
                                            r#type: "checkbox",
                                            checked: {*share_announce.read()},
                                            onclick: move |_| {
                                                let next = !*share_announce.read();
                                                share_announce.set(next);
                                            },
                                        }
                                        span { "Show in directory" }
                                    }
                                    label { class: "toggle",
                                        input {
                                            r#type: "checkbox",
                                            checked: {*discovery_mdns.read()},
                                            onclick: move |_| {
                                                let next = !*discovery_mdns.read();
                                                discovery_mdns.set(next);
                                            },
                                        }
                                        span { "Nearby (same Wi-Fi)" }
                                    }
                                    label { class: "toggle",
                                        input {
                                            r#type: "checkbox",
                                            checked: {*discovery_dht.read()},
                                            onclick: move |_| {
                                                let next = !*discovery_dht.read();
                                                discovery_dht.set(next);
                                            },
                                        }
                                        span { "Public (internet)" }
                                    }
                                    input {
                                        class: "share-input",
                                        placeholder: "contacts (comma separated, optional)",
                                        value: "{share_peers.read()}",
                                        oninput: move |evt| share_peers.set(evt.value()),
                                    }
                                    input {
                                        class: "share-input",
                                        placeholder: "sharing key (optional)",
                                        value: "{share_secret.read()}",
                                        oninput: move |evt| share_secret.set(evt.value()),
                                    }
                                }
                                for meta in stream.metadata.iter().take(6) {
                                    div { class: "meta-card",
                                        strong { "{meta.key.as_str()}" }
                                        span { "{meta.value.as_str()}" }
                                    }
                                }
                            }
                        }
                        if let Some(share) = current_share {
                            div { class: "share-card",
                                div { class: "share-title", "Share" }
                                div { class: "share-row",
                                    span { class: "share-label", "Link" }
                                    div { class: "share-link-row",
                                        input {
                                            class: "share-link",
                                            readonly: true,
                                            value: {
                                                let secret_for_link = share_secret.read().trim().to_string();
                                                build_watch_link(
                                                    &share.endpoint_addr,
                                                    &share.broadcast_name,
                                                    Some(&share.track_name),
                                                    None,
                                                    if secret_for_link.is_empty() { None } else { Some(secret_for_link.as_str()) },
                                                    share.discovery.as_deref(),
                                                )
                                            },
                                        }
                                        button {
                                            class: "share-copy",
                                            onclick: move |_| {
                                                let secret_for_link = share_secret.read().trim().to_string();
                                                let link = build_watch_link(
                                                    &share.endpoint_addr,
                                                    &share.broadcast_name,
                                                    Some(&share.track_name),
                                                    None,
                                                    if secret_for_link.is_empty() { None } else { Some(secret_for_link.as_str()) },
                                                    share.discovery.as_deref(),
                                                );
                                                let mut status = status.clone();
                                                spawn(async move {
                                                    match copy_to_clipboard(link).await {
                                                        Ok(_) => status.set("Copied link".to_string()),
                                                        Err(err) => status.set(format!("Copy failed: {err}")),
                                                    }
                                                });
                                            },
                                            "Copy"
                                        }
                                    }
                                }
                                if let Some(endpoint_id) = share.endpoint_id.clone() {
                                    div { class: "share-row",
                                        span { class: "share-label", "Your ID" }
                                        div { class: "share-link-row",
                                            input {
                                                class: "share-link",
                                                readonly: true,
                                                value: "{endpoint_id}",
                                            }
                                            button {
                                                class: "share-copy",
                                                onclick: move |_| {
                                                    let value = endpoint_id.clone();
                                                    let mut status = status.clone();
                                                    spawn(async move {
                                                        match copy_to_clipboard(value).await {
                                                            Ok(_) => status.set("Copied ID".to_string()),
                                                            Err(err) => status.set(format!("Copy failed: {err}")),
                                                        }
                                                    });
                                                },
                                                "Copy"
                                            }
                                        }
                                    }
                                }
                                if let Some(announce) = share.announce_status {
                                    div { class: "share-row",
                                        span { class: "share-label", "Status" }
                                        span { class: "share-value", "{announce}" }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

fn stream_has_drm(metadata: &[StreamMetadata]) -> bool {
    metadata.iter().any(|meta| {
        let key = meta.key.to_lowercase();
        let value = meta.value.to_lowercase();
        key == "drm"
            || key.contains("copy")
            || key.contains("protected")
            || value.contains("drm")
            || value.contains("encrypted")
            || value.contains("widevine")
    })
}

fn stream_metadata_value<'a>(stream: &'a StreamDescriptor, key: &str) -> Option<&'a str> {
    stream
        .metadata
        .iter()
        .find(|entry| entry.key == key)
        .map(|entry| entry.value.trim())
        .filter(|value| !value.is_empty())
}

fn stream_display_subtitle(stream: &StreamDescriptor) -> Option<String> {
    if stream_source_kind(stream) == "nbc" {
        if let Some(program) =
            stream_metadata_value(stream, "current_program").or_else(|| stream.number.as_deref())
        {
            return Some(format!("Now: {}", program.trim()));
        }
        if let Some(brand) = stream_metadata_value(stream, "nbc_brand") {
            return Some(brand.to_string());
        }
        return None;
    }

    stream
        .number
        .as_ref()
        .map(|value| value.trim())
        .filter(|value| !value.is_empty())
        .map(|value| value.to_string())
}

fn stream_display_detail(stream: &StreamDescriptor) -> Option<String> {
    if stream_source_kind(stream) != "nbc" {
        return None;
    }

    let mut parts = Vec::new();
    if let Some(badge) = stream_metadata_value(stream, "badge") {
        parts.push(badge.to_string());
    }
    if let Some(entitlement) = stream_metadata_value(stream, "entitlement") {
        if !entitlement.eq_ignore_ascii_case("free") {
            parts.push("Adobe auth".to_string());
        }
    } else if stream_has_drm(&stream.metadata) {
        parts.push("Adobe auth".to_string());
    }

    if parts.is_empty() {
        None
    } else {
        Some(parts.join(" • "))
    }
}

fn looks_like_nbc_input(value: &str) -> bool {
    let value = value.trim().to_ascii_lowercase();
    value.starts_with("https://www.nbc.com/") || value.starts_with("https://nbc.com/")
}

fn stream_source_kind(stream: &StreamDescriptor) -> String {
    let source = stream.source.trim();
    if !source.is_empty() {
        return source.to_ascii_lowercase();
    }
    if let Some(kind) = stream
        .metadata
        .iter()
        .find(|entry| entry.key == "source_kind")
        .map(|entry| entry.value.clone())
    {
        return kind.to_ascii_lowercase();
    }
    "unknown".to_string()
}

async fn tauri_invoke<T, A>(command: &str, args: &A) -> Result<T, String>
where
    T: for<'de> Deserialize<'de>,
    A: Serialize,
{
    let (tauri, invoke_fn) = find_tauri_invoke().ok_or_else(|| "tauri invoke not available".to_string())?;

    let args = serde_wasm_bindgen::to_value(args).map_err(|err| err.to_string())?;
    let promise = invoke_fn
        .call2(&tauri, &JsValue::from_str(command), &args)
        .map_err(|err| format!("invoke failed: {err:?}"))?;
    let promise = Promise::from(promise);
    let result = JsFuture::from(promise)
        .await
        .map_err(|err| format!("invoke rejected: {err:?}"))?;

    serde_wasm_bindgen::from_value(result).map_err(|err| err.to_string())
}
