pub mod manager {
    use super::token::PandoraTokenTracker;
    use crate::{
        protocol::tracks::{
            LoadResult, PlaylistData, PlaylistInfo, SearchResult, Track, TrackInfo,
        },
        sources::{SourcePlugin, playable_track::BoxedTrack},
    };
    use async_trait::async_trait;
    use regex::Regex;
    use reqwest::header::{ACCEPT, CONTENT_TYPE, USER_AGENT};
    use serde_json::{Value, json};
    use std::sync::{Arc, OnceLock};
    use tracing::{debug, warn};
    const BASE_URL: &str = "https://www.pandora.com";
    const ENDPOINT_SEARCH: &str = "/api/v3/sod/search";
    const ENDPOINT_ANNOTATE: &str = "/api/v4/catalog/annotateObjects";
    const ENDPOINT_DETAILS: &str = "/api/v4/catalog/getDetails";
    const ENDPOINT_PLAYLIST_TRACKS: &str = "/api/v7/playlists/getTracks";
    const ENDPOINT_ARTIST_ALL_TRACKS: &str = "/api/v4/catalog/getAllArtistTracksWithCollaborations";
    fn url_regex() -> &'static Regex {
        static RE: OnceLock<Regex> = OnceLock::new();
        RE.get_or_init(|| {
        Regex::new(r"https?://(?:www\.)?pandora\.com/(?:playlist/(?P<id>PL:[\d:]+)|artist/(?:[\w\-]+/)*(?P<id2>(?:TR|AL|AR)[A-Za-z0-9]+))").unwrap()
    })
    }
    pub struct PandoraSource {
        client: Arc<reqwest::Client>,
        token_tracker: Arc<PandoraTokenTracker>,
        search_limit: usize,
    }
    impl PandoraSource {
        pub fn new(
            config: Option<crate::config::PandoraConfig>,
            client: Arc<reqwest::Client>,
        ) -> Result<Self, String> {
            let (s_limit, csrf_override) = config
                .map(|c| (c.search_limit, c.csrf_token))
                .unwrap_or((10, None));
            let token_tracker = Arc::new(PandoraTokenTracker::new(client.clone(), csrf_override));
            token_tracker.clone().init();
            Ok(Self {
                client,
                token_tracker,
                search_limit: s_limit,
            })
        }
        async fn api_request(&self, path: &str, body: Value) -> Option<Value> {
            for is_retry in [false, true] {
                let tokens = self.token_tracker.get_tokens().await?;
                let url = format!("{BASE_URL}{path}");
                let resp = match self
                    .base_request(self.client.post(&url))
                    .header(ACCEPT, "application/json, text/plain, */*")
                    .header(CONTENT_TYPE, "application/json")
                    .header("origin", BASE_URL)
                    .header("X-Csrftoken", &tokens.csrf_token_parsed)
                    .header("X-Authtoken", &tokens.auth_token)
                    .header("Cookie", &tokens.csrf_token_raw)
                    .json(&body)
                    .send()
                    .await
                {
                    Ok(r) => r,
                    Err(e) => {
                        warn!("Pandora API request error for {path}: {e}");
                        return None;
                    }
                };
                let status = resp.status();
                let body_res: Value = resp.json::<Value>().await.ok()?;
                let error_code = body_res
                    .get("errorCode")
                    .and_then(|v| v.as_i64())
                    .unwrap_or(-1);
                let error_string = body_res
                    .get("errorString")
                    .and_then(|v| v.as_str())
                    .unwrap_or("");
                let is_error = !status.is_success()
                    || (body_res.get("errorCode").is_some()
                        && !body_res.get("errorCode").unwrap().is_null());
                if is_error {
                    if !is_retry
                        && (error_code == 1001 || error_string.contains("could not be validated"))
                    {
                        debug!(
                            "Auth token error (code: {error_code}, message: {error_string}), refreshing..."
                        );
                        self.token_tracker.force_refresh().await;
                        continue;
                    }
                    warn!(
                        "Pandora API error for {path}: status {status}, code {error_code}, message {error_string}"
                    );
                    return None;
                }
                return Some(body_res);
            }
            None
        }
        fn get_artwork_url(&self, node: &Value) -> Option<String> {
            if let Some(icon) = node.get("icon").filter(|v| !v.is_null())
                && let Some(art_id) = icon
                    .get("artId")
                    .and_then(|v| v.as_str())
                    .filter(|s| !s.is_empty())
            {
                return Some(format!(
                    "https://content-images.p-cdn.com/{art_id}_1080W_1080H.jpg"
                ));
            }
            if let Some(thor_layers) = node
                .get("thorLayers")
                .and_then(|v| v.as_str())
                .filter(|s| !s.is_empty())
            {
                if let Some(grid) = thor_layers.strip_prefix("_;grid") {
                    let encoded = urlencoding::encode(grid);
                    return Some(format!(
                        "https://dyn-images.p-cdn.com/?l=_;grid{encoded}&w=1080&h=1080"
                    ));
                }
                return Some(format!(
                    "https://content-images.p-cdn.com/{thor_layers}_1080W_1080H.jpg"
                ));
            }
            None
        }
        fn map_track(&self, track: &Value, annotations: &Value) -> Option<Track> {
            let title = track.get("name").and_then(|v| v.as_str())?;
            let author = track
                .get("artistName")
                .and_then(|v| v.as_str())
                .unwrap_or("Unknown Artist");
            let duration = track.get("duration").and_then(|v| v.as_i64()).unwrap_or(0) * 1000;
            if duration == 0 {
                return None;
            }
            let id = track.get("pandoraId").and_then(|v| v.as_str())?;
            let isrc = track
                .get("isrc")
                .and_then(|v| v.as_str())
                .map(|s| s.to_owned());
            let album_id = track.get("albumId").and_then(|v| v.as_str()).unwrap_or("");
            let album = annotations.get(album_id).filter(|v| !v.is_null());
            let album_name = album.and_then(|v| v.get("name")).and_then(|v| v.as_str());
            let album_url = album
                .and_then(|v| v.get("shareableUrlPath"))
                .and_then(|v| v.as_str());
            let artist_id = track.get("artistId").and_then(|v| v.as_str()).unwrap_or("");
            let artist = annotations.get(artist_id).filter(|v| !v.is_null());
            let artist_url = artist
                .and_then(|v| v.get("shareableUrlPath"))
                .and_then(|v| v.as_str());
            let artist_artwork_url = artist.and_then(|v| self.get_artwork_url(v));
            let original_url = track
                .get("shareableUrlPath")
                .and_then(|v| v.as_str())
                .map(|p| format!("{BASE_URL}{p}"));
            let artwork_url = self.get_artwork_url(track);
            let info = TrackInfo {
                title: title.to_owned(),
                author: author.to_owned(),
                length: duration as u64,
                identifier: id.to_owned(),
                is_stream: false,
                uri: original_url,
                artwork_url,
                isrc,
                source_name: "pandora".to_owned(),
                is_seekable: true,
                position: 0,
            };
            let mut t = Track::new(info);
            t.plugin_info = json!({
                "albumName": album_name,
                "albumUrl": album_url.map(|p| format!("{BASE_URL}{p}")),
                "artistUrl": artist_url.map(|p| format!("{BASE_URL}{p}")),
                "artistArtworkUrl": artist_artwork_url,
                "previewUrl": null,
                "isPreview": false
            });
            Some(t)
        }
        fn build_annotate_request(&self, ids: &[String]) -> Value {
            json!({ "pandoraIds": ids })
        }
        fn find_by_url_suffix(&self, tail: &str, annotations: &Value) -> Value {
            if let Some(obj) = annotations.as_object() {
                for value in obj.values() {
                    if let Some(path) = value.get("shareableUrlPath").and_then(|v| v.as_str())
                        && path.ends_with(&format!("/{}", tail))
                    {
                        return value.clone();
                    }
                    if let Some(slug) = value.get("slugPlusPandoraId").and_then(|v| v.as_str())
                        && (slug.ends_with(tail) || slug.contains(tail))
                    {
                        return value.clone();
                    }
                }
            }
            Value::Null
        }
        async fn fetch_track(&self, id: &str) -> LoadResult {
            let data = match self
                .api_request(ENDPOINT_DETAILS, json!({ "pandoraId": id }))
                .await
            {
                Some(d) => d,
                None => return LoadResult::Empty {},
            };
            let annotations = data.get("annotations").unwrap_or(&Value::Null);
            let track = self.find_by_url_suffix(id, annotations);
            if track.is_null() {
                return LoadResult::Empty {};
            }
            self.map_track(&track, annotations)
                .map(LoadResult::Track)
                .unwrap_or(LoadResult::Empty {})
        }
        async fn get_album(&self, id: &str) -> LoadResult {
            let data = match self
                .api_request(ENDPOINT_DETAILS, json!({ "pandoraId": id }))
                .await
            {
                Some(d) => d,
                None => return LoadResult::Empty {},
            };
            let annotations = data.get("annotations").unwrap_or(&Value::Null);
            let album_node = self.find_by_url_suffix(id, annotations);
            if album_node.is_null() {
                return LoadResult::Empty {};
            }
            let name = album_node
                .get("name")
                .and_then(|v| v.as_str())
                .unwrap_or("Unknown Album");
            let mut tracks = Vec::new();
            if let Some(track_ids) = album_node.get("tracks").and_then(|v| v.as_array()) {
                for tid in track_ids.iter().filter_map(|v| v.as_str()) {
                    let t_node = annotations.get(tid).unwrap_or(&Value::Null);
                    if !t_node.is_null()
                        && let Some(t) = self.map_track(t_node, annotations)
                    {
                        tracks.push(t);
                    }
                }
            }
            let url = album_node
                .get("shareableUrlPath")
                .and_then(|v| v.as_str())
                .map(|p| format!("{BASE_URL}{p}"));
            let artwork = self.get_artwork_url(&album_node);
            LoadResult::Playlist(PlaylistData {
                info: PlaylistInfo {
                    name: name.to_owned(),
                    selected_track: -1,
                },
                plugin_info: json!({
                  "url": url,
                  "type": "album",
                  "artworkUrl": artwork,
                  "totalTracks": tracks.len(),
                  "author": album_node.get("artistName").and_then(|v| v.as_str())
                }),
                tracks,
            })
        }
        async fn get_artist(&self, id: &str) -> LoadResult {
            let data = match self
                .api_request(ENDPOINT_DETAILS, json!({ "pandoraId": id }))
                .await
            {
                Some(d) => d,
                None => return LoadResult::Empty {},
            };
            let annotations = data.get("annotations").unwrap_or(&Value::Null);
            let artist_node = self.find_by_url_suffix(id, annotations);
            if artist_node.is_null() {
                return LoadResult::Empty {};
            }
            let name = artist_node
                .get("name")
                .and_then(|v| v.as_str())
                .unwrap_or("Artist");
            let top_tracks = data
                .get("artistDetails")
                .and_then(|v| v.get("topTracks"))
                .and_then(|v| v.as_array());
            let mut tracks = Vec::new();
            if let Some(ids) = top_tracks {
                for tid in ids.iter().filter_map(|v| v.as_str()) {
                    let t_node = annotations.get(tid).unwrap_or(&Value::Null);
                    if !t_node.is_null()
                        && let Some(t) = self.map_track(t_node, annotations)
                    {
                        tracks.push(t);
                    }
                }
            }
            let url = artist_node
                .get("shareableUrlPath")
                .and_then(|v| v.as_str())
                .map(|p| format!("{BASE_URL}{p}"));
            let artwork = self.get_artwork_url(&artist_node);
            LoadResult::Playlist(PlaylistData {
                info: PlaylistInfo {
                    name: format!("{name}'s Top Tracks"),
                    selected_track: -1,
                },
                plugin_info: json!({
                  "url": url,
                  "type": "artist",
                  "artworkUrl": artwork,
                  "totalTracks": tracks.len(),
                  "author": name
                }),
                tracks,
            })
        }
        async fn get_playlist(&self, id: &str) -> LoadResult {
            let body = json!({
              "request": {
                "pandoraId": id,
                "playlistVersion": 0,
                "offset": 0,
                "limit": 5000,
                "annotationLimit": 100,
                "allowedTypes": ["TR"],
                "bypassPrivacyRules": true
              }
            });
            let json = match self.api_request(ENDPOINT_PLAYLIST_TRACKS, body).await {
                Some(j) => j,
                None => return LoadResult::Empty {},
            };
            let annotations = json.get("annotations").unwrap_or(&Value::Null);
            let tracks_node = json.get("tracks").and_then(|v| v.as_array());
            let mut merged = serde_json::Map::new();
            if let Some(obj) = annotations.as_object() {
                for (k, v) in obj {
                    merged.insert(k.clone(), v.clone());
                }
            }
            let mut missing = Vec::new();
            if let Some(ts) = tracks_node {
                for t in ts {
                    if let Some(pid) = t.get("pandoraId").and_then(|v| v.as_str())
                        && !merged.contains_key(pid)
                    {
                        missing.push(pid.to_owned());
                    }
                }
            }
            if !missing.is_empty()
                && let Some(extra) = self
                    .api_request(ENDPOINT_ANNOTATE, self.build_annotate_request(&missing))
                    .await
                && let Some(obj) = extra.as_object()
            {
                for (k, v) in obj {
                    merged.insert(k.clone(), v.clone());
                }
            }
            let mut tracks = Vec::new();
            let merged_val = Value::Object(merged);
            if let Some(ts) = tracks_node {
                for t in ts {
                    if let Some(pid) = t.get("pandoraId").and_then(|v| v.as_str())
                        && let Some(ann) = merged_val.get(pid)
                        && let Some(tr) = self.map_track(ann, &merged_val)
                    {
                        tracks.push(tr);
                    }
                }
            }
            let name = json
                .get("name")
                .and_then(|v| v.as_str())
                .unwrap_or("Playlist");
            let url = json
                .get("shareableUrlPath")
                .and_then(|v| v.as_str())
                .map(|p| format!("{BASE_URL}{p}"));
            let artwork = self.get_artwork_url(&json);
            let mut author_name = None;
            if let Some(l_id) = json.get("listenerPandoraId").and_then(|v| v.as_str())
                && let Some(author) = annotations.get(l_id)
            {
                author_name = author.get("fullname").and_then(|v| v.as_str());
            }
            LoadResult::Playlist(PlaylistData {
                info: PlaylistInfo {
                    name: name.to_owned(),
                    selected_track: -1,
                },
                plugin_info: json!({
                  "url": url,
                  "type": "playlist",
                  "artworkUrl": artwork,
                  "totalTracks": tracks.len(),
                  "author": author_name
                }),
                tracks,
            })
        }
        async fn get_recommendations(&self, id: &str) -> LoadResult {
            let details = match self
                .api_request(ENDPOINT_DETAILS, json!({ "pandoraId": id }))
                .await
            {
                Some(d) => d,
                None => return LoadResult::Empty {},
            };
            let similar = details
                .get("trackDetails")
                .and_then(|v| v.get("similarTracks"))
                .and_then(|v| v.as_array());
            let id_list: Vec<String> = similar
                .map(|s| {
                    s.iter()
                        .filter_map(|v| v.as_str().map(|s| s.to_owned()))
                        .collect()
                })
                .unwrap_or_default();
            if id_list.is_empty() {
                return LoadResult::Empty {};
            }
            let annotations = match self
                .api_request(ENDPOINT_ANNOTATE, self.build_annotate_request(&id_list))
                .await
            {
                Some(a) => a,
                None => return LoadResult::Empty {},
            };
            let mut tracks = Vec::new();
            for tid in id_list {
                if let Some(item) = annotations.get(&tid).filter(|v| !v.is_null())
                    && let Some(t) = self.map_track(item, &annotations)
                {
                    tracks.push(t);
                }
            }
            LoadResult::Playlist(PlaylistData {
                info: PlaylistInfo {
                    name: "Pandora recommendations".to_owned(),
                    selected_track: -1,
                },
                plugin_info: json!({
                  "type": "recommendations",
                  "totalTracks": tracks.len()
                }),
                tracks,
            })
        }
        async fn get_artist_all_songs(&self, id: &str) -> LoadResult {
            let body = json!({ "artistPandoraId": id, "annotationLimit": 100 });
            let json = match self.api_request(ENDPOINT_ARTIST_ALL_TRACKS, body).await {
                Some(j) => j,
                None => return LoadResult::Empty {},
            };
            let annotations = json.get("annotations").unwrap_or(&Value::Null);
            let tracks_node = json.get("tracks").and_then(|v| v.as_array());
            if tracks_node.as_ref().map(|n| n.is_empty()).unwrap_or(true) {
                return LoadResult::Empty {};
            }
            let mut merged = serde_json::Map::new();
            if let Some(obj) = annotations.as_object() {
                for (k, v) in obj {
                    merged.insert(k.clone(), v.clone());
                }
            }
            let all_ids: Vec<String> = tracks_node
                .unwrap()
                .iter()
                .filter_map(|v| v.as_str().map(|s| s.to_owned()))
                .collect();
            let mut missing = Vec::new();
            for tid in &all_ids {
                if !merged.contains_key(tid) {
                    missing.push(tid.clone());
                }
            }
            if !missing.is_empty()
                && let Some(extra) = self
                    .api_request(ENDPOINT_ANNOTATE, self.build_annotate_request(&missing))
                    .await
                && let Some(obj) = extra.as_object()
            {
                for (k, v) in obj {
                    merged.insert(k.clone(), v.clone());
                }
            }
            let merged_val = Value::Object(merged);
            let mut tracks = Vec::new();
            for tid in all_ids {
                if let Some(ann) = merged_val.get(&tid)
                    && let Some(tr) = self.map_track(ann, &merged_val)
                {
                    tracks.push(tr);
                }
            }
            let mut artist_node = self.find_by_url_suffix(id, annotations);
            if artist_node.is_null()
                && let Some(details) = self
                    .api_request(ENDPOINT_DETAILS, json!({ "pandoraId": id }))
                    .await
            {
                let details_ann = details.get("annotations").unwrap_or(&Value::Null);
                let match_node = self.find_by_url_suffix(id, details_ann);
                if !match_node.is_null() {
                    artist_node = match_node;
                }
            }
            let name = artist_node
                .get("name")
                .and_then(|v| v.as_str())
                .map(|n| format!("{n} - All Songs"))
                .unwrap_or_else(|| "All Songs".to_owned());
            let url = artist_node
                .get("shareableUrlPath")
                .and_then(|v| v.as_str())
                .map(|p| format!("{BASE_URL}{p}"));
            let artwork = self.get_artwork_url(&artist_node);
            LoadResult::Playlist(PlaylistData {
                info: PlaylistInfo {
                    name,
                    selected_track: -1,
                },
                plugin_info: json!({
                  "url": url,
                  "type": "artist",
                  "artworkUrl": artwork,
                  "totalTracks": tracks.len(),
                  "author": artist_node.get("name").and_then(|v| v.as_str())
                }),
                tracks,
            })
        }
        async fn get_search(&self, query: &str) -> LoadResult {
            let body = json!({
              "query": query,
              "types": ["TR"],
              "listener": null,
              "start": 0,
              "count": 100,
              "annotate": true,
              "annotationRecipe": "CLASS_OF_2019"
            });
            let json = match self.api_request(ENDPOINT_SEARCH, body).await {
                Some(j) => j,
                None => return LoadResult::Empty {},
            };
            let annotations = json.get("annotations").unwrap_or(&Value::Null);
            let results = json.get("results").and_then(|v| v.as_array());
            if results.as_ref().map(|r| r.is_empty()).unwrap_or(true) {
                return LoadResult::Empty {};
            }
            let mut tracks = Vec::new();
            for v in results.unwrap() {
                if let Some(id) = v.as_str()
                    && let Some(item) = annotations.get(id)
                    && item.get("type").and_then(|v| v.as_str()) == Some("TR")
                    && let Some(tr) = self.map_track(item, annotations)
                {
                    tracks.push(tr);
                    if tracks.len() >= self.search_limit {
                        break;
                    }
                }
            }
            if tracks.is_empty() {
                LoadResult::Empty {}
            } else {
                LoadResult::Search(tracks)
            }
        }
        pub fn base_request(&self, builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
            builder.header(USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36")
        }
        async fn get_autocomplete(&self, query: &str, types: &[String]) -> Option<SearchResult> {
            let mut type_keys = Vec::new();
            if types.is_empty() {
                type_keys.extend_from_slice(&["TR", "AL", "AR", "PL"]);
            } else {
                for t in types {
                    match t.as_str() {
                        "track" => type_keys.push("TR"),
                        "album" => type_keys.push("AL"),
                        "artist" => type_keys.push("AR"),
                        "playlist" => type_keys.push("PL"),
                        _ => {}
                    }
                }
            }
            let body = json!({
              "query": query,
              "types": type_keys,
              "listener": null,
              "start": 0,
              "count": 100,
              "annotate": true,
              "annotationRecipe": "CLASS_OF_2019"
            });
            let json = self.api_request(ENDPOINT_SEARCH, body).await?;
            let annotations = json.get("annotations").unwrap_or(&Value::Null);
            let results = json.get("results").and_then(|v| v.as_array())?;
            let mut albums = Vec::new();
            let mut artists = Vec::new();
            let mut playlists = Vec::new();
            let mut tracks = Vec::new();
            for id_node in results {
                let id = id_node.as_str()?;
                let item = annotations.get(id).filter(|v| !v.is_null())?;
                let type_str = item.get("type").and_then(|v| v.as_str())?;
                match type_str {
                    "TR" => {
                        if let Some(tr) = self.map_track(item, annotations) {
                            tracks.push(tr);
                        }
                    }
                    "AL" => {
                        let name = item
                            .get("name")
                            .and_then(|v| v.as_str())
                            .unwrap_or("Unknown Album");
                        let path = item.get("shareableUrlPath").and_then(|v| v.as_str());
                        let artwork = self.get_artwork_url(item);
                        let artist_name = item.get("artistName").and_then(|v| v.as_str());
                        albums.push(PlaylistData {
                        info: PlaylistInfo {
                            name: name.to_owned(),
                            selected_track: -1,
                        },
                        plugin_info: json!({
                          "url": path.map(|p| format!("{BASE_URL}{p}")),
                          "type": "album",
                          "artworkUrl": artwork,
                          "totalTracks": item.get("trackCount").and_then(|v| v.as_u64()).unwrap_or(0),
                          "author": artist_name
                        }),
                        tracks: Vec::new(),
                    });
                    }
                    "AR" => {
                        let name = item
                            .get("name")
                            .and_then(|v| v.as_str())
                            .unwrap_or("Artist");
                        let path = item.get("shareableUrlPath").and_then(|v| v.as_str());
                        let artwork = self.get_artwork_url(item);
                        artists.push(PlaylistData {
                            info: PlaylistInfo {
                                name: format!("{name}'s Top Tracks"),
                                selected_track: -1,
                            },
                            plugin_info: json!({
                              "url": path.map(|p| format!("{BASE_URL}{p}")),
                              "type": "artist",
                              "artworkUrl": artwork,
                              "totalTracks": 0,
                              "author": name
                            }),
                            tracks: Vec::new(),
                        });
                    }
                    "PL" => {
                        let name = item
                            .get("name")
                            .and_then(|v| v.as_str())
                            .unwrap_or("Playlist");
                        let path = item.get("shareableUrlPath").and_then(|v| v.as_str());
                        let artwork = self.get_artwork_url(item);
                        let mut author_name = None;
                        if let Some(l_id) = item.get("listenerPandoraId").and_then(|v| v.as_str())
                            && let Some(author) = annotations.get(l_id)
                        {
                            author_name = author.get("fullname").and_then(|v| v.as_str());
                        }
                        playlists.push(PlaylistData {
                        info: PlaylistInfo {
                            name: name.to_owned(),
                            selected_track: -1,
                        },
                        plugin_info: json!({
                          "url": path.map(|p| format!("{BASE_URL}{p}")),
                          "type": "playlist",
                          "artworkUrl": artwork,
                          "totalTracks": item.get("totalTracks").and_then(|v| v.as_u64()).unwrap_or(0),
                          "author": author_name
                        }),
                        tracks: Vec::new(),
                    });
                    }
                    _ => {}
                }
            }
            if tracks.len() > self.search_limit {
                tracks.truncate(self.search_limit);
            }
            Some(SearchResult {
                tracks,
                albums,
                artists,
                playlists,
                texts: Vec::new(),
                plugin: json!({}),
            })
        }
    }
    #[async_trait]
    impl SourcePlugin for PandoraSource {
        fn name(&self) -> &str {
            "pandora"
        }
        fn can_handle(&self, identifier: &str) -> bool {
            self.search_prefixes()
                .iter()
                .any(|p| identifier.starts_with(p))
                || self
                    .rec_prefixes()
                    .iter()
                    .any(|p| identifier.starts_with(p))
                || url_regex().is_match(identifier)
        }
        fn search_prefixes(&self) -> Vec<&str> {
            vec!["pdsearch:"]
        }
        fn is_mirror(&self) -> bool {
            true
        }
        fn rec_prefixes(&self) -> Vec<&str> {
            vec!["pdrec:"]
        }
        async fn load(
            &self,
            identifier: &str,
            _routeplanner: Option<Arc<dyn crate::routeplanner::RoutePlanner>>,
        ) -> LoadResult {
            if let Some(prefix) = self
                .search_prefixes()
                .into_iter()
                .find(|p| identifier.starts_with(p))
            {
                let query = identifier.strip_prefix(prefix).unwrap();
                if query.is_empty() {
                    return LoadResult::Empty {};
                }
                return self.get_search(query).await;
            }
            if let Some(prefix) = self
                .rec_prefixes()
                .into_iter()
                .find(|p| identifier.starts_with(p))
            {
                let id = identifier.strip_prefix(prefix).unwrap();
                if id.is_empty() {
                    return LoadResult::Empty {};
                }
                return self.get_recommendations(id).await;
            }
            let input = identifier.trim();
            if let Some(caps) = url_regex().captures(input)
                && let Some(id_match) = caps.name("id").or_else(|| caps.name("id2"))
            {
                let id = id_match.as_str();
                if id.is_empty() {
                    return LoadResult::Empty {};
                }
                if let Some(tr_id) = id.strip_prefix("TR")
                    && !tr_id.is_empty()
                {
                    return self.fetch_track(id).await;
                }
                if let Some(al_id) = id.strip_prefix("AL")
                    && !al_id.is_empty()
                {
                    return self.get_album(id).await;
                }
                if let Some(ar_id) = id.strip_prefix("AR")
                    && !ar_id.is_empty()
                {
                    if input.contains("/artist/all-songs/") {
                        return self.get_artist_all_songs(id).await;
                    }
                    return self.get_artist(id).await;
                }
                if let Some(pl_id) = id.strip_prefix("PL:")
                    && !pl_id.is_empty()
                {
                    return self.get_playlist(id).await;
                }
            }
            LoadResult::Empty {}
        }
        async fn load_search(
            &self,
            query: &str,
            types: &[String],
            _routeplanner: Option<Arc<dyn crate::routeplanner::RoutePlanner>>,
        ) -> Option<SearchResult> {
            self.get_autocomplete(query, types).await
        }
        async fn get_track(
            &self,
            _identifier: &str,
            _routeplanner: Option<Arc<dyn crate::routeplanner::RoutePlanner>>,
        ) -> Option<BoxedTrack> {
            None
        }
    }
}
pub mod token {
    use serde_json::Value;
    use std::{
        sync::Arc,
        time::{Duration, Instant},
    };
    use tokio::sync::RwLock;
    use tracing::{debug, error, info};
    #[derive(Debug, Clone)]
    pub struct PandoraTokens {
        pub auth_token: String,
        pub csrf_token_raw: String,
        pub csrf_token_parsed: String,
        pub expires_at: Instant,
    }
    pub struct PandoraTokenTracker {
        client: Arc<reqwest::Client>,
        tokens: Arc<RwLock<Option<PandoraTokens>>>,
        csrf_override: Option<String>,
    }
    impl PandoraTokenTracker {
        pub fn new(client: Arc<reqwest::Client>, csrf_override: Option<String>) -> Self {
            Self {
                client,
                tokens: Arc::new(RwLock::new(None)),
                csrf_override,
            }
        }
        pub async fn get_tokens(&self) -> Option<PandoraTokens> {
            {
                let tokens = self.tokens.read().await;
                if let Some(t) = &*tokens
                    && t.expires_at > Instant::now()
                {
                    return Some(t.clone());
                }
            }
            self.refresh_tokens().await
        }
        pub async fn force_refresh(&self) -> Option<PandoraTokens> {
            self.perform_refresh(true).await
        }
        pub async fn refresh_tokens(&self) -> Option<PandoraTokens> {
            self.perform_refresh(false).await
        }
        async fn perform_refresh(&self, force: bool) -> Option<PandoraTokens> {
            let mut tokens_lock = self.tokens.write().await;
            if !force
                && let Some(t) = &*tokens_lock
                && t.expires_at > Instant::now()
            {
                return Some(t.clone());
            }
            debug!("Refreshing Pandora tokens...");
            let (csrf_raw, csrf_parsed) = if let Some(csrf) = &self.csrf_override {
                (
                    format!("csrftoken={csrf};Path=/;Domain=.pandora.com;Secure"),
                    csrf.clone(),
                )
            } else {
                match self.fetch_csrf_token().await {
                    Ok(res) => res,
                    Err(e) => {
                        error!("Failed to fetch Pandora CSRF token: {e}");
                        return None;
                    }
                }
            };
            let auth_token = match self.perform_anonymous_login(&csrf_raw, &csrf_parsed).await {
                Ok(token) => token,
                Err(e) => {
                    error!("Failed to perform Pandora anonymous login: {e}");
                    return None;
                }
            };
            let new_tokens = PandoraTokens {
                auth_token,
                csrf_token_raw: csrf_raw,
                csrf_token_parsed: csrf_parsed,
                expires_at: Instant::now() + Duration::from_secs(12 * 3600),
            };
            *tokens_lock = Some(new_tokens.clone());
            info!("Successfully refreshed Pandora tokens");
            Some(new_tokens)
        }
        async fn fetch_csrf_token(&self) -> Result<(String, String), String> {
            let resp = self
                .client
                .head("https://www.pandora.com")
                .send()
                .await
                .map_err(|e| e.to_string())?;
            let cookies = resp.headers().get_all(reqwest::header::SET_COOKIE);
            let regex = regex::Regex::new(r"csrftoken=([a-f0-9]{16})").unwrap();
            for cookie in cookies {
                let cookie_str = cookie.to_str().unwrap_or("");
                if let Some(raw) = cookie_str.split(';').next()
                    && raw.starts_with("csrftoken=")
                    && let Some(captures) = regex.captures(raw)
                    && let Some(parsed_match) = captures.get(1)
                {
                    return Ok((raw.to_owned(), parsed_match.as_str().to_owned()));
                }
            }
            Err("CSRF token not found in cookies".to_owned())
        }
        async fn perform_anonymous_login(
            &self,
            csrf_raw: &str,
            csrf_parsed: &str,
        ) -> Result<String, String> {
            let resp = self
                .client
                .post("https://www.pandora.com/api/v1/auth/anonymousLogin")
                .header("Cookie", csrf_raw)
                .header("X-CsrfToken", csrf_parsed)
                .header("Content-Type", "application/json")
                .header("Accept", "*/*")
                .body("")
                .send()
                .await
                .map_err(|e| e.to_string())?;
            if !resp.status().is_success() {
                return Err(format!(
                    "Anonymous login failed with status: {}",
                    resp.status()
                ));
            }
            let body: Value = resp.json().await.map_err(|e| e.to_string())?;
            if let Some(error_code) = body.get("errorCode")
                && error_code.as_i64() == Some(0)
            {
                return Err("Anonymous login returned error code 0".to_owned());
            }
            body.get("authToken")
                .and_then(|t| t.as_str())
                .map(|s| s.to_owned())
                .ok_or_else(|| "Auth token not found in response".to_owned())
        }
        pub fn init(self: Arc<Self>) {
            let this = self.clone();
            tokio::spawn(async move {
                this.get_tokens().await;
            });
        }
    }
}
pub use manager::PandoraSource;
