1use anyhow::{anyhow, Result};
8
9use crate::client::RommClient;
10use crate::endpoints::collections::{ListCollections, ListSmartCollections};
11use crate::endpoints::{
12 platforms::{GetPlatform, ListPlatforms},
13 roms::{GetRom, GetRoms},
14};
15use crate::types::{Collection, Platform, Rom, RomList};
16
17pub struct PlatformService<'a> {
22 client: &'a RommClient,
23}
24
25impl<'a> PlatformService<'a> {
26 pub fn new(client: &'a RommClient) -> Self {
28 Self { client }
29 }
30
31 pub async fn list_platforms(&self) -> Result<Vec<Platform>> {
33 let platforms = self.client.call(&ListPlatforms).await?;
34 Ok(platforms)
35 }
36
37 pub async fn get_platform(&self, id: u64) -> Result<Platform> {
39 let platform = self.client.call(&GetPlatform { id }).await?;
40 Ok(platform)
41 }
42}
43
44pub struct RomService<'a> {
49 client: &'a RommClient,
50}
51
52impl<'a> RomService<'a> {
53 pub fn new(client: &'a RommClient) -> Self {
55 Self { client }
56 }
57
58 pub async fn search_roms(&self, ep: &GetRoms) -> Result<RomList> {
60 let results = self.client.call(ep).await?;
61 Ok(results)
62 }
63
64 pub async fn get_rom(&self, id: u64) -> Result<Rom> {
66 let rom = self.client.call(&GetRom { id }).await?;
67 Ok(rom)
68 }
69}
70
71pub fn resolve_platform_id_from_list(query: &str, platforms: &[Platform]) -> Result<u64> {
76 let normalized = query.trim().to_ascii_lowercase();
77
78 if let Some(platform) = platforms.iter().find(|p| {
79 p.slug.eq_ignore_ascii_case(&normalized) || p.fs_slug.eq_ignore_ascii_case(&normalized)
80 }) {
81 return Ok(platform.id);
82 }
83
84 let exact_name_matches: Vec<&Platform> = platforms
85 .iter()
86 .filter(|p| {
87 p.name.eq_ignore_ascii_case(&normalized)
88 || p.display_name
89 .as_deref()
90 .is_some_and(|name| name.eq_ignore_ascii_case(&normalized))
91 || p.custom_name
92 .as_deref()
93 .is_some_and(|name| name.eq_ignore_ascii_case(&normalized))
94 })
95 .collect();
96
97 match exact_name_matches.len() {
98 1 => Ok(exact_name_matches[0].id),
99 0 => Err(anyhow!(
100 "No platform found for '{}'. Use 'romm-cli platforms list' to inspect available values.",
101 query
102 )),
103 _ => {
104 let names = exact_name_matches
105 .iter()
106 .map(|p| format!("{} ({})", p.name, p.id))
107 .collect::<Vec<_>>()
108 .join(", ");
109 Err(anyhow!(
110 "Platform '{}' is ambiguous. Matches: {}. Please use a more specific --platform value.",
111 query,
112 names
113 ))
114 }
115 }
116}
117
118pub async fn resolve_platform_id(
122 client: &RommClient,
123 platform_query: Option<&str>,
124) -> Result<Option<u64>> {
125 let Some(query) = platform_query.map(str::trim).filter(|q| !q.is_empty()) else {
126 return Ok(None);
127 };
128 let service = PlatformService::new(client);
129 let platforms = service.list_platforms().await?;
130 resolve_platform_id_from_list(query, &platforms).map(Some)
131}
132
133pub async fn resolve_platform_ids(client: &RommClient, names: &[String]) -> Result<Vec<u64>> {
135 if names.is_empty() {
136 return Ok(Vec::new());
137 }
138 let service = PlatformService::new(client);
139 let platforms = service.list_platforms().await?;
140 let mut out = Vec::new();
141 for n in names {
142 let id = resolve_platform_id_from_list(n.trim(), &platforms)?;
143 if !out.contains(&id) {
144 out.push(id);
145 }
146 }
147 Ok(out)
148}
149
150fn match_collections_by_name<'a>(q: &str, collections: &'a [Collection]) -> Vec<&'a Collection> {
151 let n = q.trim().to_ascii_lowercase();
152 collections
153 .iter()
154 .filter(|c| c.name.eq_ignore_ascii_case(&n))
155 .collect()
156}
157
158pub async fn resolve_manual_collection_id(
160 client: &RommClient,
161 query: Option<&str>,
162) -> Result<Option<u64>> {
163 let Some(q) = query.map(str::trim).filter(|s| !s.is_empty()) else {
164 return Ok(None);
165 };
166 if let Ok(id) = q.parse::<u64>() {
167 return Ok(Some(id));
168 }
169 let list = client.call(&ListCollections).await?.into_vec();
170 let matches = match_collections_by_name(q, &list);
171 match matches.len() {
172 0 => Err(anyhow!(
173 "No manual collection named '{}'. Use `romm-cli collections list`.",
174 q
175 )),
176 1 => Ok(Some(matches[0].id)),
177 _ => Err(anyhow!(
178 "Manual collection '{}' is ambiguous among {} matches; use a numeric id.",
179 q,
180 matches.len()
181 )),
182 }
183}
184
185pub async fn resolve_smart_collection_id(
187 client: &RommClient,
188 query: Option<&str>,
189) -> Result<Option<u64>> {
190 let Some(q) = query.map(str::trim).filter(|s| !s.is_empty()) else {
191 return Ok(None);
192 };
193 if let Ok(id) = q.parse::<u64>() {
194 return Ok(Some(id));
195 }
196 let list = client.call(&ListSmartCollections).await?.into_vec();
197 let matches = match_collections_by_name(q, &list);
198 match matches.len() {
199 0 => Err(anyhow!(
200 "No smart collection named '{}'. Use `romm-cli collections list`.",
201 q
202 )),
203 1 => Ok(Some(matches[0].id)),
204 _ => Err(anyhow!(
205 "Smart collection '{}' is ambiguous among {} matches; use a numeric id.",
206 q,
207 matches.len()
208 )),
209 }
210}