import { Playlist, ServerType, ApiResponse, PlexServerConnection, PlexConnectionSettings, PlexLibrary, RegexReplacement, SyncStrategy } from '../types'; const API_BASE = import.meta.env.VITE_API_BASE_URL || ''; const MODE_TO_STRATEGY: Record = { local_force: SyncStrategy.LOCAL_OVERWRITE, remote_force: SyncStrategy.CLOUD_OVERWRITE, merge_local_primary: SyncStrategy.MERGE_LOCAL, merge_remote_primary: SyncStrategy.MERGE_CLOUD, }; const STRATEGY_TO_MODE: Record = { [SyncStrategy.LOCAL_OVERWRITE]: 'local_force', [SyncStrategy.CLOUD_OVERWRITE]: 'remote_force', [SyncStrategy.MERGE_LOCAL]: 'merge_local_primary', [SyncStrategy.MERGE_CLOUD]: 'merge_remote_primary', }; const handleResponse = async (response: Response): Promise> => { try { const data = await response.json(); if (!response.ok) { return { data: data as T, status: 'error', message: (data as any)?.detail || response.statusText }; } return { data, status: 'success' }; } catch (error: any) { return { data: {} as T, status: 'error', message: error?.message || 'Unexpected error' }; } }; const mapPlaylist = (item: any): Playlist => ({ id: item.id || `${item.title}-${item.trackCount}`, title: item.title ?? item.name ?? 'Unknown', trackCount: item.trackCount ?? item.track_count ?? 0, lastUpdated: item.lastUpdated || item.last_updated || new Date().toISOString(), }); const mapLibrary = (item: any): PlexLibrary => ({ id: item.id ?? item.title, title: item.title ?? item.id, type: item.type ?? 'artist', }); const mapRegexRules = (rules: any[]): RegexReplacement[] => (rules || []).map((rule, index) => ({ id: rule.id || `${rule.pattern || 'rule'}-${index}`, pattern: rule.pattern || '', replacement: rule.replacement || '', })); export const apiService = { async getSettings(): Promise> { const response = await fetch(`${API_BASE}/api/settings`); const result = await handleResponse(response); if (result.status === 'success') { const mode = result.data.sync_mode as string; const strategy = MODE_TO_STRATEGY[mode] || SyncStrategy.LOCAL_OVERWRITE; const regex = mapRegexRules(result.data.path_rules || []); const connection: PlexConnectionSettings = { protocol: (result.data.scheme as 'http' | 'https') || 'https', address: result.data.server_url || '', port: result.data.port || '32400', token: result.data.token || '', libraryName: result.data.library_name || '', }; return { status: 'success', data: { strategy, regex, connection, localPath: result.data.local_path || '' } }; } return result as ApiResponse as ApiResponse<{ strategy: SyncStrategy; regex: RegexReplacement[]; connection: PlexConnectionSettings; localPath: string }>; }, async updateSyncStrategy(strategy: SyncStrategy): Promise> { const payload = { mode: STRATEGY_TO_MODE[strategy] }; const response = await fetch(`${API_BASE}/api/settings/sync-mode`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); return handleResponse(response); }, async saveRegexRules(replacements: RegexReplacement[]): Promise> { const payload = { rules: replacements.map(({ pattern, replacement }) => ({ pattern, replacement })) }; const response = await fetch(`${API_BASE}/api/settings/regex-rules`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); return handleResponse(response); }, async updateLibrary(libraryName: string): Promise> { const response = await fetch(`${API_BASE}/api/settings/library`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ library_name: libraryName }), }); return handleResponse(response); }, async getPlaylists(serverType: ServerType, signal?: AbortSignal, localPath?: string): Promise> { const params = new URLSearchParams({ server: serverType.toLowerCase() }); if (serverType === ServerType.LOCAL && localPath) { params.append('local_path', localPath); } const response = await fetch(`${API_BASE}/api/playlists?${params.toString()}`, { signal }); const result = await handleResponse(response); if (result.status === 'success' && (result.data as any)?.playlists) { return { data: (result.data.playlists as any[]).map(mapPlaylist), status: 'success' }; } return result as ApiResponse; }, async getServerStatus(signal?: AbortSignal): Promise> { const response = await fetch(`${API_BASE}/api/server`, { signal }); const result = await handleResponse(response); if (result.status === 'success') { const info = result.data.serverInfo || {}; const libraries: PlexLibrary[] = (result.data.libraries || []).map(mapLibrary); return { status: 'success', data: { isConnected: !!info.isConnected, name: info.name, ip: info.ip, port: info.port ? Number(info.port) : undefined, libraryName: info.libraryName, libraries, }, }; } return result as ApiResponse; }, async connectToPlex(settings: PlexConnectionSettings, signal?: AbortSignal): Promise> { const response = await fetch(`${API_BASE}/api/connect`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ protocol: settings.protocol, address: settings.address, port: settings.port, token: settings.token, username: settings.username, password: settings.password, library_name: settings.libraryName, timeout: settings.timeout, }), signal, }); const result = await handleResponse(response); if (result.status === 'success') { const info = result.data.serverInfo; info.libraries = (info.libraries || []).map(mapLibrary); return { status: 'success', data: { token: result.data.token, serverInfo: info } }; } return result as ApiResponse<{ token: string; serverInfo: PlexServerConnection }>; }, async syncPlaylists(strategy: SyncStrategy, _regexRules: RegexReplacement[], localPath?: string): Promise> { const response = await fetch(`${API_BASE}/api/sync`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: STRATEGY_TO_MODE[strategy], local_path: localPath, }), }); return handleResponse(response); }, };