/** * @copyright 2026 NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import { Injectable, inject, signal, computed } from '@angular/core'; import { Observable, map, tap, catchError, of } from 'rxjs'; import { ApiService } from './api.service'; import { CreateLikeDto, LikeResponse, LikedItemDto, Like } from '@library/shared-types'; interface LikeState { entityType: Like['entityType']; entityId: string; liked: boolean; count: number; } @Injectable({ providedIn: 'root' }) export class LikesService { private api = inject(ApiService); // Store like states for caching private likeStates = signal>(new Map()); /** * Toggle like on an item. */ toggleLike(entityType: Like['entityType'], entityId: string): Observable { const dto: CreateLikeDto = { entityType, entityId }; return this.api.post('/likes/toggle', dto).pipe( tap(response => { const key = this.getCacheKey(entityType, entityId); this.likeStates.update(states => { const newStates = new Map(states); newStates.set(key, { entityType, entityId, liked: response.liked, count: response.count }); return newStates; }); }), catchError(() => { // On error, return current state or default const key = this.getCacheKey(entityType, entityId); const currentState = this.likeStates().get(key); return of({ liked: currentState?.liked || false, count: currentState?.count || 0 }); }) ); } /** * Get like count for an item. */ getLikeCount(entityType: Like['entityType'], entityId: string): Observable { return this.api.get<{ count: number }>(`/likes/count?entityType=${entityType}&entityId=${entityId}`).pipe( map(response => response.count), tap(count => { const key = this.getCacheKey(entityType, entityId); this.likeStates.update(states => { const newStates = new Map(states); const currentState = newStates.get(key); newStates.set(key, { entityType, entityId, liked: currentState?.liked || false, count }); return newStates; }); }) ); } /** * Check if current user has liked an item. */ getUserLikeStatus(entityType: Like['entityType'], entityId: string): Observable { return this.api.get<{ liked: boolean }>(`/likes/status?entityType=${entityType}&entityId=${entityId}`).pipe( map(response => response.liked), tap(liked => { const key = this.getCacheKey(entityType, entityId); this.likeStates.update(states => { const newStates = new Map(states); const currentState = newStates.get(key); newStates.set(key, { entityType, entityId, liked, count: currentState?.count || 0 }); return newStates; }); }) ); } /** * Get all items liked by the current user. */ getUserLikedItems(entityType?: Like['entityType']): Observable { const query = entityType ? `?entityType=${entityType}` : ''; return this.api.get(`/likes/user${query}`); } /** * Get bulk like statuses for multiple items. */ getBulkLikeStatuses(items: Array<{ entityType: string; entityId: string }>): Observable> { return this.api.post>('/likes/bulk-status', { items }).pipe( tap(results => { this.likeStates.update(states => { const newStates = new Map(states); results.forEach(result => { const key = this.getCacheKey(result.entityType as Like['entityType'], result.entityId); newStates.set(key, { entityType: result.entityType as Like['entityType'], entityId: result.entityId, liked: result.liked, count: result.count }); }); return newStates; }); }) ); } /** * Get like state from cache. */ getCachedLikeState(entityType: Like['entityType'], entityId: string): LikeState | undefined { const key = this.getCacheKey(entityType, entityId); return this.likeStates().get(key); } /** * Create computed signal for specific item's like state. */ createLikeStateSignal(entityType: Like['entityType'], entityId: string) { const key = this.getCacheKey(entityType, entityId); return computed(() => { const state = this.likeStates().get(key); return { liked: state?.liked || false, count: state?.count || 0 }; }); } private getCacheKey(entityType: Like['entityType'], entityId: string): string { return `${entityType}:${entityId}`; } }