import { Playlist, ServerType, ApiResponse, PlexServerConnection, PlexConnectionSettings, PlexLibrary, ReplacementRule, PathMappingConfig, PathMappingMode, PathMappingRules, SyncStrategy, ScheduleSettings, BackupSettings } 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 || item.libraryType || item.library_type || item.section?.type || '', }); // Helper function to map raw rules array to ReplacementRule[] const mapReplacementRules = (rules: any[]): ReplacementRule[] => (rules || []).map((rule, index) => ({ id: rule.id || `${rule.search || 'rule'}-${index}-${Date.now()}`, search: rule.search || rule.pattern || '', replace: rule.replace || rule.replacement || '', })); // Helper function to map API path_mapping response to PathMappingConfig const mapPathMappingConfig = (data: any): PathMappingConfig => { const defaultConfig: PathMappingConfig = { mode: PathMappingMode.SIMPLE, simple: [], regex: { localPre: [], localPost: [], remotePre: [], remotePost: [] } }; if (!data || !data.path_mapping) { return defaultConfig; } const pm = data.path_mapping; return { mode: pm.mode === 'REGEX' ? PathMappingMode.REGEX : PathMappingMode.SIMPLE, simple: mapReplacementRules(pm.simple || []), regex: { localPre: mapReplacementRules(pm.regex?.localPre || pm.regex?.local_pre || []), localPost: mapReplacementRules(pm.regex?.localPost || pm.regex?.local_post || []), remotePre: mapReplacementRules(pm.regex?.remotePre || pm.regex?.remote_pre || []), remotePost: mapReplacementRules(pm.regex?.remotePost || pm.regex?.remote_post || []) } }; }; // Helper function to convert PathMappingConfig to API format const pathMappingToApi = (config: PathMappingConfig) => { const rulesToApi = (rules: ReplacementRule[]) => rules.map(({ id, search, replace }) => ({ id, search, replace })); return { mode: config.mode, simple: rulesToApi(config.simple), regex: { local_pre: rulesToApi(config.regex.localPre), local_post: rulesToApi(config.regex.localPost), remote_pre: rulesToApi(config.regex.remotePre), remote_post: rulesToApi(config.regex.remotePost) } }; }; 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 pathMapping = mapPathMappingConfig(result.data); 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, pathMapping, connection, localPath: result.data.local_path || '' } }; } return result as ApiResponse as ApiResponse<{ strategy: SyncStrategy; pathMapping: PathMappingConfig; 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 savePathMapping(config: PathMappingConfig): Promise> { const payload = pathMappingToApi(config); const response = await fetch(`${API_BASE}/api/settings/path-mapping`, { 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 getScheduleSettings(): Promise> { const response = await fetch(`${API_BASE}/api/schedule`); return handleResponse(response); }, async saveScheduleSettings(settings: ScheduleSettings): Promise> { const response = await fetch(`${API_BASE}/api/schedule`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(settings), }); 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, _pathMapping: PathMappingConfig, 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); }, async getSyncStatus(): Promise> { const response = await fetch(`${API_BASE}/api/sync/status`); return handleResponse(response); }, async getBackupSettings(): Promise> { const response = await fetch(`${API_BASE}/api/backup/settings`); const result = await handleResponse(response); if (result.status === 'success') { return { status: 'success', data: { enabled: result.data.enabled ?? false, retentionCount: result.data.retention_count ?? 5, }, }; } return result as ApiResponse; }, async saveBackupSettings(settings: BackupSettings): Promise> { const response = await fetch(`${API_BASE}/api/backup/settings`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: settings.enabled, retention_count: settings.retentionCount, }), }); return handleResponse(response); }, };