import { Media, Staff } from './types'; const BASE_URL = 'http://192.168.1.102:6400'; function normalizeUrl(url: string | null): string { if (!url) return ''; if (url.startsWith('http://') || url.startsWith('https://')) { return url; } // Remove leading slash if present and add base URL const cleanPath = url.startsWith('/') ? url.slice(1) : url; return `${BASE_URL}/${cleanPath}`; } // API Response Types export interface ApiResponse { success: boolean; data: T; } export interface PaginatedResponse { items: T[]; total: number; page: number; limit: number; totalPages?: number; } // Media Types export interface ApiMediaItem { id: number; title: string; year: number; poster: string | null; banner: string | null; description: string | null; rating: number | null; category: string | null; type: string; status: string; aspectRatio: string | null; runtime: number | null; director: string | null; writer: string | null; releaseDate: string | null; createdAt: string; updatedAt: string; genres?: string[]; tags?: string[]; studios?: string[]; staff?: ApiStaff[]; } export interface ApiStaff { id: number; name: string; photo: string | null; bio: string | null; birthDate: string | null; birthPlace: string | null; role: string; characterName: string | null; characterImage: string | null; occupations?: string[]; } export interface CreateMediaInput { title: string; year: number; poster?: string | null; banner?: string | null; description?: string | null; rating?: number | null; category?: string | null; type?: string; status?: string; aspectRatio?: string | null; runtime?: number | null; director?: string | null; writer?: string | null; releaseDate?: string | null; genres?: string[]; tags?: string[]; studios?: string[]; staff?: CreateStaffInput[]; } export interface UpdateMediaInput extends Partial {} export interface CreateStaffInput { name: string; photo?: string | null; bio?: string | null; birthDate?: string | null; birthPlace?: string | null; role: string; characterName?: string | null; characterImage?: string | null; occupations?: string[]; } // Cast Types export interface ApiCastItem { id: number; name: string; photo: string | null; bio: string | null; birthDate: string | null; birthPlace: string | null; createdAt: string; updatedAt: string; occupations?: string[]; filmography?: ApiCastMediaItem[]; } export interface ApiCastMediaItem { id: number; title: string; year: number; poster: string | null; category: string | null; type: string; role: string; characterName: string | null; } export interface CreateCastInput { name: string; photo?: string | null; bio?: string | null; birthDate?: string | null; birthPlace?: string | null; occupations?: string[]; } export interface UpdateCastInput extends Partial {} export function convertApiToMedia(apiItem: ApiMediaItem): Media { // Convert staff from API to Media staff format const staff: Staff[] = (apiItem.staff || []).map((staffMember) => ({ id: staffMember.id.toString(), name: staffMember.name, role: staffMember.role, photo: normalizeUrl(staffMember.photo) || `https://picsum.photos/seed/staff-${staffMember.id}/200/200`, characterName: staffMember.characterName || staffMember.name, characterImage: normalizeUrl(staffMember.characterImage) || normalizeUrl(staffMember.photo) || `https://picsum.photos/seed/staff-${staffMember.id}/200/200`, })); // Determine aspect ratio from API format let aspectRatio: '2/3' | '16/9' | '1/1' = '2/3'; if (apiItem.aspectRatio) { const ratio = apiItem.aspectRatio.toLowerCase(); if (ratio.includes('16:9') || ratio.includes('1.78') || ratio.includes('2.39')) { aspectRatio = '16/9'; } else if (ratio.includes('1:1') || ratio.includes('1.00')) { aspectRatio = '1/1'; } } // Map API type to Media type allowed values let mediaType: 'TV' | 'Movie' | 'OVA' | 'ONA' | 'Album' | 'Single' | 'Hardcover' | 'E-book' | 'Console' | 'Game' = 'Movie'; const apiType = apiItem.type?.toLowerCase(); if (apiType === 'tv' || apiType === 'episode') { mediaType = 'TV'; } else if (apiType === 'album' || apiType === 'single') { mediaType = apiType === 'album' ? 'Album' : 'Single'; } else if (apiType === 'game' || apiType === 'console') { mediaType = apiType === 'game' ? 'Game' : 'Console'; } else if (apiType === 'ova') { mediaType = 'OVA'; } else if (apiType === 'ona') { mediaType = 'ONA'; } else if (apiType === 'hardcover' || apiType === 'e-book') { mediaType = apiType === 'hardcover' ? 'Hardcover' : 'E-book'; } // Map API category to MediaCategory let mediaCategory: 'Anime' | 'Movies' | 'Music' | 'Books' | 'Adult' | 'Consoles' | 'Games' = 'Movies'; const apiCategory = apiItem.category?.toLowerCase(); console.log('API Category:', apiItem.category, 'Lowercased:', apiCategory, 'Type:', apiType); if (apiCategory === 'anime') { mediaCategory = 'Anime'; } else if (apiCategory === 'movie' || apiCategory === 'movies') { mediaCategory = 'Movies'; } else if (apiCategory === 'music' || apiType === 'album' || apiType === 'single') { mediaCategory = 'Music'; } else if (apiCategory === 'book' || apiCategory === 'books' || apiType === 'hardcover' || apiType === 'e-book') { mediaCategory = 'Books'; } else if (apiCategory === 'adult') { mediaCategory = 'Adult'; } else if (apiCategory === 'console' || apiCategory === 'consoles' || apiType === 'console') { mediaCategory = 'Consoles'; } else if (apiCategory === 'game' || apiCategory === 'games' || apiType === 'game') { mediaCategory = 'Games'; } else { // If category doesn't match any known category, use the original value capitalized // This handles cases where the API returns unexpected category values console.warn('Unknown category:', apiItem.category, 'defaulting to Movies'); mediaCategory = 'Movies'; } console.log('Mapped to:', mediaCategory); // Map API status to Media status allowed values let mediaStatus: 'watching' | 'completed' | 'planned' | 'dropped' | 'reading' | 'listening' | 'playing' | 'on-hold' = 'completed'; const apiStatus = apiItem.status?.toLowerCase(); if (apiStatus === 'ongoing' || apiStatus === 'watching') { mediaStatus = 'watching'; } else if (apiStatus === 'upcoming' || apiStatus === 'planned') { mediaStatus = 'planned'; } else if (apiStatus === 'dropped') { mediaStatus = 'dropped'; } else if (apiStatus === 'reading') { mediaStatus = 'reading'; } else if (apiStatus === 'listening') { mediaStatus = 'listening'; } else if (apiStatus === 'playing') { mediaStatus = 'playing'; } else if (apiStatus === 'on-hold') { mediaStatus = 'on-hold'; } return { id: apiItem.id.toString(), title: apiItem.title, year: apiItem.year?.toString() || 'Unknown', poster: normalizeUrl(apiItem.poster) || `https://picsum.photos/seed/${apiItem.id}/400/600`, category: mediaCategory, banner: normalizeUrl(apiItem.banner) || undefined, description: apiItem.description || undefined, rating: apiItem.rating || undefined, genres: apiItem.genres || [], tags: apiItem.tags || [], studios: apiItem.studios, type: mediaType, status: mediaStatus, staff: staff.length > 0 ? staff : undefined, aspectRatio: aspectRatio }; } // Media API Functions export async function fetchAllMedia(page: number = 1, limit: number = 50): Promise { try { const response = await fetch(`${BASE_URL}/api/media?page=${page}&limit=${limit}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiResponse> = await response.json(); if (data.success && data.data.items) { return data.data.items.map(convertApiToMedia); } return []; } catch (error) { console.error('Error fetching media from API:', error); return []; } } export async function fetchMediaById(id: number | string): Promise { try { const response = await fetch(`${BASE_URL}/api/media/${id}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiResponse = await response.json(); if (data.success && data.data) { return convertApiToMedia(data.data); } return null; } catch (error) { console.error('Error fetching media by ID:', error); return null; } } export async function createMedia(media: CreateMediaInput): Promise { try { const response = await fetch(`${BASE_URL}/api/media`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(media), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiResponse = await response.json(); if (data.success && data.data) { return convertApiToMedia(data.data); } return null; } catch (error) { console.error('Error creating media:', error); return null; } } export async function updateMedia(id: number | string, media: UpdateMediaInput): Promise { try { const response = await fetch(`${BASE_URL}/api/media/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(media), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiResponse = await response.json(); if (data.success && data.data) { return convertApiToMedia(data.data); } return null; } catch (error) { console.error('Error updating media:', error); return null; } } export async function deleteMedia(id: number | string): Promise { try { const response = await fetch(`${BASE_URL}/api/media/${id}`, { method: 'DELETE', }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiResponse<{ message: string }> = await response.json(); return data.success; } catch (error) { console.error('Error deleting media:', error); return false; } } // Cast API Functions export async function fetchAllCast(page: number = 1, limit: number = 50): Promise { try { const response = await fetch(`${BASE_URL}/api/cast?page=${page}&limit=${limit}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiResponse> = await response.json(); if (data.success && data.data.items) { return data.data.items; } return []; } catch (error) { console.error('Error fetching cast from API:', error); return []; } } export async function fetchCastById(id: number | string): Promise { try { const response = await fetch(`${BASE_URL}/api/cast/${id}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiResponse = await response.json(); if (data.success && data.data) { return data.data; } return null; } catch (error) { console.error('Error fetching cast by ID:', error); return null; } } export async function fetchCastMedia(castId: number | string): Promise { try { const response = await fetch(`${BASE_URL}/api/cast/${castId}/media`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiResponse> = await response.json(); if (data.success && data.data.items) { return data.data.items.map(convertApiToMedia); } return []; } catch (error) { console.error('Error fetching cast media:', error); return []; } } export async function createCast(cast: CreateCastInput): Promise { try { const response = await fetch(`${BASE_URL}/api/cast`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(cast), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiResponse = await response.json(); if (data.success && data.data) { return data.data; } return null; } catch (error) { console.error('Error creating cast:', error); return null; } } export async function updateCast(id: number | string, cast: UpdateCastInput): Promise { try { const response = await fetch(`${BASE_URL}/api/cast/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(cast), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiResponse = await response.json(); if (data.success && data.data) { return data.data; } return null; } catch (error) { console.error('Error updating cast:', error); return null; } } export async function deleteCast(id: number | string): Promise { try { const response = await fetch(`${BASE_URL}/api/cast/${id}`, { method: 'DELETE', }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiResponse<{ message: string }> = await response.json(); return data.success; } catch (error) { console.error('Error deleting cast:', error); return false; } } // Legacy function for compatibility - fetches all unique staff members from media export async function fetchAllActors(): Promise> { try { const media = await fetchAllMedia(1, 1000); const actorMap = new Map(); media.forEach(item => { item.staff?.forEach(staffMember => { const id = parseInt(staffMember.id); if (!actorMap.has(id)) { actorMap.set(id, { id: id, name: staffMember.name, photo: staffMember.photo }); } }); }); return Array.from(actorMap.values()); } catch (error) { console.error('Error fetching all actors:', error); return []; } } // Legacy function for compatibility - fetches all unique tags from media export async function fetchAllTags(): Promise { try { const media = await fetchAllMedia(1, 1000); const tagSet = new Set(); media.forEach(item => { item.tags?.forEach(tag => tagSet.add(tag)); item.genres?.forEach(genre => tagSet.add(genre)); }); return Array.from(tagSet).sort(); } catch (error) { console.error('Error fetching all tags:', error); return []; } } // Legacy function for compatibility - fetches media by actor name export async function fetchMediaByActor(actorName: string): Promise { try { const media = await fetchAllMedia(1, 1000); return media.filter(item => item.staff?.some(staffMember => staffMember.name.toLowerCase().includes(actorName.toLowerCase()) ) ); } catch (error) { console.error('Error fetching media by actor:', error); return []; } } // Legacy function for compatibility - fetches media by tag export async function fetchMediaByTag(tag: string): Promise { try { const media = await fetchAllMedia(1, 1000); return media.filter(item => item.tags?.some(t => t.toLowerCase().includes(tag.toLowerCase())) || item.genres?.some(g => g.toLowerCase().includes(tag.toLowerCase())) ); } catch (error) { console.error('Error fetching media by tag:', error); return []; } } // Convenience function - fetch media from API (legacy compatibility) export async function fetchMediaFromApi(apiUrl?: string): Promise { return fetchAllMedia(); } // Convenience function - fetch media from local JSON (legacy compatibility) export async function fetchMediaFromLocalJson(): Promise { return fetchAllMedia(); }