"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.exportBooks = exports.bulkImportBooks = exports.bulkUpdateBooks = exports.bulkDeleteBooks = exports.deleteBook = exports.updateBook = exports.createBook = exports.getBookBySlug = exports.getBooks = void 0;
const client_1 = require("@prisma/client");
const crypto_1 = __importDefault(require("crypto"));
const database_1 = __importDefault(require("../config/database"));
const response_utils_1 = require("../utils/response.utils");
const catalog_utils_1 = require("../utils/catalog.utils");
const includeBookRelations = {
    images: { orderBy: { sortOrder: 'asc' } },
    publisher: true,
    subCategory: { include: { category: true } },
    bookAuthors: { include: { author: true } },
    bookCategories: { include: { category: true } },
    inventory: true,
    variants: { where: { isActive: true }, orderBy: [{ isDefault: 'desc' }, { format: 'asc' }] },
    catalogues: { include: { catalogue: true } },
};
const formatBook = (book, includeAttributes = false) => ({
    id: book.id,
    sourceProductId: book.sourceProductId,
    title: book.title,
    slug: book.slug,
    sourceSlug: book.sourceSlug,
    isbn: book.isbn,
    sku: book.sku,
    sourceSku: book.sourceSku,
    barcode: book.barcode,
    description: book.description,
    descriptionHtml: book.descriptionHtml,
    additionalAttributesText: book.additionalAttributesText,
    additionalAttributesHtml: book.additionalAttributesHtml,
    price: Number(book.price),
    discountPrice: book.discountPrice === null ? null : Number(book.discountPrice),
    gstRate: Number(book.gstRate),
    stock: book.inventory?.stock ?? book.stock,
    format: book.format,
    binding: book.binding,
    language: book.language,
    sourceLanguage: book.sourceLanguage,
    edition: book.edition,
    classGrade: book.classGrade,
    publicationYear: book.publicationYear,
    publicationDate: book.publicationDate,
    pageCount: book.pageCount,
    pages: book.pagesText ?? (book.pageCount === null ? null : String(book.pageCount)),
    weight: book.weight === null ? null : Number(book.weight),
    dimensions: book.dimensions,
    coverImage: book.coverImage,
    metaTitle: book.metaTitle,
    metaDescription: book.metaDescription,
    status: book.status,
    isActive: book.isActive,
    displayOrder: book.displayOrder,
    isNewRelease: book.isNewRelease,
    isFeatured: book.isFeatured,
    isTrending: book.isTrending,
    isBestseller: book.isBestseller,
    ratingAverage: Number(book.ratingAverage),
    ratingCount: book.ratingCount,
    publisherId: book.publisherId,
    publisherName: book.publisher?.name ?? null,
    authorName: book.bookAuthors[0]?.author.name ?? null,
    authors: book.bookAuthors.map(({ author }) => ({ id: author.id, name: author.name, slug: author.slug })),
    categoryName: book.bookCategories[0]?.category.name ?? null,
    categories: book.bookCategories.map(({ category }) => ({
        id: category.id,
        name: category.name,
        slug: category.slug,
    })),
    images: book.images.map((image) => ({
        id: image.id,
        url: image.url,
        isPrimary: image.isPrimary,
        sortOrder: image.sortOrder,
        assetStatus: image.assetStatus,
    })),
    variants: (book.variants || []).map((v) => ({
        id: v.id,
        bookId: v.bookId,
        title: v.title,
        format: v.format,
        isbn: v.isbn,
        sku: v.sku,
        barcode: v.barcode,
        price: Number(v.price),
        discountPrice: v.discountPrice === null ? null : Number(v.discountPrice),
        stock: v.stock,
        weight: v.weight === null ? null : Number(v.weight),
        dimensions: v.dimensions,
        coverImage: v.coverImage,
        digitalFileUrl: v.digitalFileUrl,
        digitalFormat: v.digitalFormat,
        attributes: v.attributes,
        isDefault: v.isDefault,
        isActive: v.isActive,
    })),
    catalogues: book.catalogues.map(({ catalogue }) => ({
        id: catalogue.id,
        title: catalogue.title,
        pdfUrl: catalogue.pdfUrl,
        assetStatus: catalogue.assetStatus,
    })),
    createdAt: book.createdAt,
    updatedAt: book.updatedAt,
    ...(includeAttributes && 'attributes' in book ? { attributes: book.attributes } : {}),
});
const getUniqueSlug = async (requested, currentBookId) => {
    const base = (0, catalog_utils_1.slugify)(requested) || 'book';
    let candidate = base.slice(0, 191);
    let sequence = 2;
    while (await database_1.default.book.findFirst({
        where: { slug: candidate, ...(currentBookId ? { id: { not: currentBookId } } : {}) },
        select: { id: true },
    })) {
        const suffix = `-${sequence}`;
        candidate = `${base.slice(0, 191 - suffix.length)}${suffix}`;
        sequence += 1;
    }
    return candidate;
};
const getBooks = async (req, res) => {
    try {
        const { search, category, author, publisher, language, status, isFeatured, isTrending, isBestseller, minPrice, maxPrice, sortBy = 'displayOrder', sortOrder = 'asc', limit = '20', page = '1', ids, } = req.query;
        const take = Math.max(1, Math.min(100, Number(limit) || 20));
        const currentPage = Math.max(1, Number(page) || 1);
        const skip = (currentPage - 1) * take;
        const where = {};
        if (!status)
            where.isActive = true;
        else if (String(status).toUpperCase() !== 'ALL') {
            where.status = String(status).toUpperCase() === 'DISABLED' ? client_1.BookStatus.DISABLED : client_1.BookStatus.ENABLED;
        }
        if (ids) {
            const idList = String(ids).split(',').filter(Boolean);
            if (idList.length > 0) {
                where.id = { in: idList };
            }
        }
        if (search) {
            const query = String(search).trim();
            where.OR = [
                { title: { contains: query } },
                { description: { contains: query } },
                { isbn: { contains: query } },
                { sku: { contains: query } },
                { sourceSku: { contains: query } },
                { publisher: { name: { contains: query } } },
                { bookAuthors: { some: { author: { name: { contains: query } } } } },
                { bookCategories: { some: { category: { name: { contains: query } } } } },
                { attributes: { some: { OR: [{ name: { contains: query } }, { value: { contains: query } }] } } },
            ];
        }
        if (category) {
            const value = String(category);
            where.bookCategories = {
                some: { category: { OR: [{ id: value }, { slug: value }, { name: value }] } },
            };
        }
        if (author) {
            const value = String(author);
            where.bookAuthors = { some: { author: { OR: [{ id: value }, { slug: value }, { name: value }] } } };
        }
        if (publisher) {
            const value = String(publisher);
            where.publisher = { OR: [{ id: value }, { slug: value }, { name: value }] };
        }
        if (language)
            where.language = String(language);
        if (isFeatured === 'true')
            where.isFeatured = true;
        if (isTrending === 'true')
            where.isTrending = true;
        if (isBestseller === 'true')
            where.isBestseller = true;
        if (minPrice || maxPrice) {
            where.price = {};
            if (minPrice)
                where.price.gte = Number(minPrice);
            if (maxPrice)
                where.price.lte = Number(maxPrice);
        }
        const sortableFields = new Set([
            'displayOrder',
            'createdAt',
            'updatedAt',
            'title',
            'price',
            'stock',
            'ratingAverage',
        ]);
        const orderField = sortableFields.has(String(sortBy)) ? String(sortBy) : 'displayOrder';
        const direction = sortOrder === 'desc' ? 'desc' : 'asc';
        const [books, total] = await Promise.all([
            database_1.default.book.findMany({
                where,
                take,
                skip,
                include: includeBookRelations,
                orderBy: { [orderField]: direction },
            }),
            database_1.default.book.count({ where }),
        ]);
        return (0, response_utils_1.sendSuccess)(res, 'Books retrieved successfully', {
            books: books.map((book) => formatBook(book)),
            pagination: {
                total,
                page: currentPage,
                limit: take,
                totalPages: Math.max(1, Math.ceil(total / take)),
            },
        });
    }
    catch (error) {
        return (0, response_utils_1.sendError)(res, 'Error fetching books', error instanceof Error ? error.message : String(error), 500);
    }
};
exports.getBooks = getBooks;
const getBookBySlug = async (req, res) => {
    try {
        const value = req.params.slug;
        const book = await database_1.default.book.findFirst({
            where: { OR: [{ slug: value }, { id: value }, { sourceProductId: value }] },
            include: {
                ...includeBookRelations,
                attributes: { orderBy: [{ sortOrder: 'asc' }, { sourceRow: 'asc' }] },
                texts: { orderBy: [{ field: 'asc' }, { partNumber: 'asc' }] },
                reviews: { include: { user: true }, take: 10, orderBy: { createdAt: 'desc' } },
            },
        });
        if (!book)
            return (0, response_utils_1.sendError)(res, 'Book not found', null, 404);
        return (0, response_utils_1.sendSuccess)(res, 'Book detail retrieved', { book: formatBook(book, true) });
    }
    catch (error) {
        return (0, response_utils_1.sendError)(res, 'Error fetching book detail', error instanceof Error ? error.message : String(error), 500);
    }
};
exports.getBookBySlug = getBookBySlug;
const createBook = async (req, res) => {
    try {
        const input = req.body;
        const slug = await getUniqueSlug(input.slug || input.title);
        const requestedSku = input.sku || input.isbn || null;
        const existingSku = requestedSku
            ? await database_1.default.book.findUnique({ where: { sku: requestedSku } })
            : null;
        const sku = existingSku && requestedSku
            ? (0, catalog_utils_1.operationalSku)(requestedSku, crypto_1.default.randomUUID().slice(0, 8), 2)
            : requestedSku;
        const book = await database_1.default.$transaction(async (tx) => tx.book.create({
            data: {
                title: input.title,
                slug,
                isbn: input.isbn,
                sku,
                barcode: input.barcode ?? null,
                description: input.description,
                price: input.price,
                discountPrice: input.discountPrice ?? null,
                gstRate: input.gstRate ?? 0,
                stock: input.stock ?? 0,
                format: input.format ?? 'PAPERBACK',
                binding: input.binding ?? input.format ?? null,
                language: input.language ?? 'UNKNOWN',
                edition: input.edition ?? null,
                weight: input.weight ?? null,
                dimensions: input.dimensions ?? null,
                publisherId: input.publisherId ?? null,
                subCategoryId: input.subCategoryId ?? null,
                publicationDate: input.publicationDate ? new Date(input.publicationDate) : null,
                pageCount: input.pageCount ?? null,
                pagesText: input.pagesText ?? (input.pageCount ? String(input.pageCount) : null),
                coverImage: input.coverImage ?? input.imageUrls?.[0] ?? null,
                metaTitle: input.metaTitle ?? null,
                metaDescription: input.metaDescription ?? null,
                status: input.status === 'DISABLED' ? client_1.BookStatus.DISABLED : client_1.BookStatus.ENABLED,
                isActive: input.status !== 'DISABLED',
                displayOrder: input.displayOrder ?? 0,
                isNewRelease: input.isNewRelease ?? false,
                isFeatured: input.isFeatured ?? false,
                isTrending: input.isTrending ?? false,
                isBestseller: input.isBestseller ?? false,
                inventory: { create: { stock: input.stock ?? 0, reserved: 0 } },
                images: {
                    create: (input.imageUrls ?? []).map((url, index) => ({
                        url,
                        storageKey: decodeURIComponent(url.replace(/^\/assets\//, '')),
                        assetStatus: 'DOWNLOADED',
                        sortOrder: index,
                        isPrimary: index === 0,
                    })),
                },
                bookAuthors: { create: (input.authorIds ?? []).map((authorId) => ({ authorId })) },
                bookCategories: {
                    create: (input.categoryIds ?? []).map((categoryId) => ({ categoryId })),
                },
                ...(input.variants && input.variants.length > 0 && {
                    variants: {
                        create: input.variants.map((v, index) => ({
                            title: v.title || `${input.title} (${v.format})`,
                            format: v.format || 'PAPERBACK',
                            isbn: v.isbn || null,
                            sku: v.sku || `${sku}-${v.format.toLowerCase()}`,
                            barcode: v.barcode || null,
                            price: v.price || input.price,
                            discountPrice: v.discountPrice ?? null,
                            stock: v.stock !== undefined ? v.stock : input.stock ?? 0,
                            weight: v.weight ?? null,
                            dimensions: v.dimensions ?? null,
                            coverImage: v.coverImage ?? null,
                            digitalFileUrl: v.digitalFileUrl ?? null,
                            digitalFormat: v.digitalFormat ?? null,
                            attributes: v.attributes ?? undefined,
                            isDefault: v.isDefault ?? index === 0,
                            isActive: v.isActive ?? true,
                        })),
                    },
                }),
            },
            include: includeBookRelations,
        }));
        return (0, response_utils_1.sendSuccess)(res, 'Book added to store catalog successfully', { book: formatBook(book) }, 201);
    }
    catch (error) {
        return (0, response_utils_1.sendError)(res, 'Error creating book', error instanceof Error ? error.message : String(error), 500);
    }
};
exports.createBook = createBook;
const updateBook = async (req, res) => {
    try {
        const id = req.params.id;
        const input = req.body;
        const existing = await database_1.default.book.findUnique({ where: { id } });
        if (!existing)
            return (0, response_utils_1.sendError)(res, 'Book not found', null, 404);
        const slug = input.slug || input.title ? await getUniqueSlug(input.slug || input.title, id) : undefined;
        await database_1.default.$transaction(async (tx) => {
            await tx.book.update({
                where: { id },
                data: {
                    ...(input.title !== undefined && { title: input.title }),
                    ...(slug && { slug }),
                    ...(input.isbn !== undefined && { isbn: input.isbn }),
                    ...(input.sku !== undefined && { sku: input.sku }),
                    ...(input.barcode !== undefined && { barcode: input.barcode }),
                    ...(input.description !== undefined && { description: input.description }),
                    ...(input.price !== undefined && { price: input.price }),
                    ...(input.discountPrice !== undefined && { discountPrice: input.discountPrice }),
                    ...(input.stock !== undefined && { stock: input.stock }),
                    ...(input.format !== undefined && { format: input.format }),
                    ...(input.binding !== undefined && { binding: input.binding }),
                    ...(input.language !== undefined && { language: input.language }),
                    ...(input.edition !== undefined && { edition: input.edition }),
                    ...(input.weight !== undefined && { weight: input.weight }),
                    ...(input.dimensions !== undefined && { dimensions: input.dimensions }),
                    ...(input.publisherId !== undefined && { publisherId: input.publisherId }),
                    ...(input.pageCount !== undefined && { pageCount: input.pageCount }),
                    ...(input.coverImage !== undefined && { coverImage: input.coverImage }),
                    ...(input.metaTitle !== undefined && { metaTitle: input.metaTitle }),
                    ...(input.metaDescription !== undefined && { metaDescription: input.metaDescription }),
                    ...(input.status !== undefined && {
                        status: input.status === 'DISABLED' ? client_1.BookStatus.DISABLED : client_1.BookStatus.ENABLED,
                        isActive: input.status !== 'DISABLED',
                    }),
                    ...(input.displayOrder !== undefined && { displayOrder: input.displayOrder }),
                    ...(input.isNewRelease !== undefined && { isNewRelease: input.isNewRelease }),
                    ...(input.isFeatured !== undefined && { isFeatured: input.isFeatured }),
                    ...(input.isTrending !== undefined && { isTrending: input.isTrending }),
                    ...(input.isBestseller !== undefined && { isBestseller: input.isBestseller }),
                },
            });
            if (input.stock !== undefined) {
                await tx.bookInventory.upsert({
                    where: { bookId: id },
                    create: { bookId: id, stock: input.stock },
                    update: { stock: input.stock },
                });
            }
            if (input.authorIds) {
                await tx.bookAuthor.deleteMany({ where: { bookId: id } });
                if (input.authorIds.length) {
                    await tx.bookAuthor.createMany({
                        data: input.authorIds.map((authorId) => ({ bookId: id, authorId })),
                    });
                }
            }
            if (input.categoryIds) {
                await tx.bookCategory.deleteMany({ where: { bookId: id } });
                if (input.categoryIds.length) {
                    await tx.bookCategory.createMany({
                        data: input.categoryIds.map((categoryId) => ({ bookId: id, categoryId })),
                    });
                }
            }
        });
        const book = await database_1.default.book.findUnique({ where: { id }, include: includeBookRelations });
        return (0, response_utils_1.sendSuccess)(res, 'Book updated successfully', { book: book ? formatBook(book) : null });
    }
    catch (error) {
        return (0, response_utils_1.sendError)(res, 'Error updating book', error instanceof Error ? error.message : String(error), 500);
    }
};
exports.updateBook = updateBook;
const deleteBook = async (req, res) => {
    try {
        const result = await database_1.default.book.updateMany({
            where: { id: req.params.id },
            data: { status: client_1.BookStatus.DISABLED, isActive: false },
        });
        if (!result.count)
            return (0, response_utils_1.sendError)(res, 'Book not found', null, 404);
        return (0, response_utils_1.sendSuccess)(res, 'Book disabled successfully');
    }
    catch (error) {
        return (0, response_utils_1.sendError)(res, 'Error disabling book', error instanceof Error ? error.message : String(error), 500);
    }
};
exports.deleteBook = deleteBook;
const bulkDeleteBooks = async (req, res) => {
    try {
        const result = await database_1.default.book.updateMany({
            where: { id: { in: req.body.bookIds } },
            data: { status: client_1.BookStatus.DISABLED, isActive: false },
        });
        return (0, response_utils_1.sendSuccess)(res, `Bulk disabled ${result.count} books successfully`, { count: result.count });
    }
    catch (error) {
        return (0, response_utils_1.sendError)(res, 'Bulk disable failed', error instanceof Error ? error.message : String(error), 500);
    }
};
exports.bulkDeleteBooks = bulkDeleteBooks;
const bulkUpdateBooks = async (req, res) => {
    try {
        const { bookIds, data } = req.body;
        const { stock, ...bookData } = data;
        const result = await database_1.default.$transaction(async (tx) => {
            const updated = await tx.book.updateMany({ where: { id: { in: bookIds } }, data: bookData });
            if (stock !== undefined) {
                await tx.book.updateMany({ where: { id: { in: bookIds } }, data: { stock } });
                await Promise.all(bookIds.map((bookId) => tx.bookInventory.upsert({
                    where: { bookId },
                    create: { bookId, stock },
                    update: { stock },
                })));
            }
            return updated;
        });
        return (0, response_utils_1.sendSuccess)(res, `Bulk updated ${result.count} books successfully`, { count: result.count });
    }
    catch (error) {
        return (0, response_utils_1.sendError)(res, 'Bulk update failed', error instanceof Error ? error.message : String(error), 500);
    }
};
exports.bulkUpdateBooks = bulkUpdateBooks;
const bulkImportBooks = async (_req, res) => (0, response_utils_1.sendError)(res, 'Use the resumable catalog:import command for workbook imports; the legacy JSON bulk endpoint is disabled.', null, 410);
exports.bulkImportBooks = bulkImportBooks;
const exportBooks = async (_req, res) => {
    try {
        const books = await database_1.default.book.findMany({ include: includeBookRelations, orderBy: { displayOrder: 'asc' } });
        return (0, response_utils_1.sendSuccess)(res, 'Book catalog exported successfully', books.map((book) => formatBook(book)));
    }
    catch (error) {
        return (0, response_utils_1.sendError)(res, 'Export failed', error instanceof Error ? error.message : String(error), 500);
    }
};
exports.exportBooks = exportBooks;
//# sourceMappingURL=book.controller.js.map