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