import { invalidate } from '$app/navigation';
import { addBooksToShelf, createBookshelf, listBookshelves, removeBooksFromShelf } from '$lib/api';
import type { Book, Bookshelf } from '$lib/schema';
import { getContext, setContext } from 'svelte';
import { toast } from 'svelte-sonner';
import { SvelteMap } from 'svelte/reactivity';

export class BookshelfState {
	readonly libraryBookshelves = new SvelteMap<string, Bookshelf[]>();

	getBookshelves(libraryId: string | number): Bookshelf[] | undefined {
		const id = libraryId.toString();

		if (!this.libraryBookshelves.has(id)) {
			this.fetchBookshelves(id).then((shelves) => {
				this.libraryBookshelves.set(id, shelves);
			});
		}

		return this.libraryBookshelves.get(id);
	}

	async fetchBookshelves(libraryId: string) {
		try {
			const paginatedBookshelves = await listBookshelves({
				libraries: [libraryId]
			});

			return paginatedBookshelves.items;
		} catch (error) {
			console.error('Failed to retrieve bookshelves: ', error);
		}
	}

	async addBookshelf(name: string, libraryId?: string | number, booksToAdd?: string[] | number[]) {
		try {
			const bookshelf = await createBookshelf({
				title: name,
				library_id: libraryId,
				book_ids: booksToAdd
			});

			if (bookshelf.library_id) {
				const currentShelves = this.libraryBookshelves.get(bookshelf.library_id.toString()) || [];
				this.libraryBookshelves.set(bookshelf.library_id.toString(), [
					...currentShelves,
					bookshelf
				]);
			}

			invalidate('app:books');

			if (booksToAdd?.length) toast.success(`Added ${booksToAdd.length} books to '${name}'`);
			else toast.success(`Created shelf '${name}'`);

			return bookshelf;
		} catch (error) {
			toast.error(`Failed to create bookshelf '${name}'`);
			console.error(`Failed to create bookshelf: `, error);
		}
	}

	async addBooksToShelf(shelfId: number | string, bookIds: number[]) {
		try {
			const shelf = await addBooksToShelf({
				shelf_id: shelfId,
				book_ids: bookIds
			});

			this.updateShelf(shelf);

			if (bookIds.length === 1) toast.success(`Added book to shelf!`);
			else toast.success(`Added ${bookIds.length} books to shelf!`);
		} catch (error) {
			console.error('Failed to add book(s) to shelf: ', error);
			toast.error('Failed to add book(s) to shelf.');
		}
	}

	async removeBooksFromShelf(shelfId: number | string, bookIds: number[]) {
		try {
			const shelf = await removeBooksFromShelf({
				shelf_id: shelfId,
				book_ids: bookIds
			});

			this.updateShelf(shelf);

			if (bookIds.length === 1) toast.success(`Removed book from shelf.`);
			else toast.success(`Removed ${bookIds.length} books from shelf!`);
		} catch (error) {
			console.error('Failed to remove book(s) from shelf: ', error);
			toast.error('Failed to remove book(s) from shelf.');
		}
	}

	async updateShelf(shelf: Bookshelf) {
		if (!shelf.library_id) return;
		const bookshelves = this.libraryBookshelves.get(shelf.library_id.toString());
		if (!bookshelves) return;

		const index = bookshelves.findIndex((bookshelf) => bookshelf.id === shelf.id);
		if (index !== -1) {
			this.libraryBookshelves.set(shelf.library_id.toString(), [
				...bookshelves.slice(0, index),
				shelf,
				...bookshelves.slice(index + 1)
			]);
		}
	}

	deletedBooks(books: Book[]) {
		// Assume all books are in the same library
		const libraryId = books[0].library_id.toString();
		const bookshelves = this.libraryBookshelves.get(libraryId);
		if (!bookshelves) return;

		// Create a Set of shelf IDs that need updating for efficient lookup
		// eslint-disable-next-line svelte/prefer-svelte-reactivity -- local temporary, not reactive state
		const shelfIdsToUpdate = new Set<number>();
		books.forEach((book) => {
			book.lists.forEach((shelf) => {
				shelfIdsToUpdate.add(shelf.id);
			});
		});

		const updatedBookshelves = bookshelves.map((shelf) => {
			if (shelfIdsToUpdate.has(shelf.id)) {
				// Count how many times this shelf appears across all deleted books
				let decrementBy = 0;
				books.forEach((book) => {
					if (book.lists.some((s) => s.id === shelf.id)) {
						decrementBy++;
					}
				});
				return { ...shelf, total: (shelf.total ?? 0) - decrementBy };
			}
			return shelf;
		});

		this.libraryBookshelves.set(libraryId, updatedBookshelves);
	}
}

const BOOKSHELF_KEY = Symbol('BOOKSHELF');

export function setBookshelfState() {
	return setContext(BOOKSHELF_KEY, new BookshelfState());
}

export function getBookshelfState() {
	return getContext<ReturnType<typeof setBookshelfState>>(BOOKSHELF_KEY);
}
