import System from "../System.js";
import { getUserEncryptionKey } from "../keyManager.js";
import Book, { CompleteBookData } from "./Book.js";
import ChapterRepo, {
    ActChapterQuery,
    ChapterExportInfoResult,
    ChapterQueryResult,
    ChapterSelectionParam,
    ChapterStoryQueryResult,
    LastChapterResult,
    SelectedChapterContentResult
} from "../repositories/chapter.repository.js";
import { ActChapter, ActStory } from "./Act.js";
import ChapterContentRepository, {
    ChapterContentQueryResult,
    CompanionContentQueryResult,
    ContentQueryResult
} from "../repositories/chaptercontent.repository.js";
import RemovedItem from "./RemovedItem.js";

export interface ChapterContent {
    version: number;
    content: string;
    wordsCount: number;
}

export interface ChapterContentData extends ChapterContent {
    title: string;
    chapterOrder: number;
}

export interface ChapterProps {
    chapterId: string;
    title: string;
    chapterOrder: number;
    chapterContent?: ChapterContent
}

export interface CompanionContent {
    version: number;
    content: string;
    wordsCount: number;
}

export interface SyncedChapter {
    id: string;
    name: string;
    lastUpdate: number;
    contents: SyncedChapterContent[];
    info: SyncedChapterInfo | null;
}

export interface SyncedChapterContent {
    id: string;
    lastUpdate: number;
}

export interface SyncedChapterInfo {
    id: string;
    lastUpdate: number;
}

export interface CompleteChapterContent {
    id: string;
    title: string;
    content: string;
    order: number;
    version?: number;
}

export interface ChapterExportInfo {
    chapterId: string;
    title: string;
    chapterOrder: number;
    availableVersions: number[];
}

interface TipTapNode {
    type?: string;
    text?: string;
    content?: TipTapNode[];
    attrs?: Record<string, unknown>;
    marks?: TipTapMark[];
}

interface TipTapMark {
    type: string;
    attrs?: Record<string, unknown>;
}

export default class Chapter {
    /**
     * Retrieves all chapters from a specific book.
     * @param userId - The unique identifier of the user
     * @param bookId - The unique identifier of the book
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns An array of ChapterProps containing chapter details
     */
    public static getAllChaptersFromABook(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): ChapterProps[] {
        const chapterQueryResults: ChapterQueryResult[] = ChapterRepo.fetchAllChapterFromABook(userId, bookId, lang);
        const decryptedChapters: ChapterProps[] = [];
        const userEncryptionKey: string = getUserEncryptionKey(userId);

        for (const chapterResult of chapterQueryResults) {
            const decryptedTitle: string = System.decryptDataWithUserKey(chapterResult.title, userEncryptionKey);
            decryptedChapters.push({
                chapterId: chapterResult.chapter_id,
                title: decryptedTitle,
                chapterOrder: chapterResult.chapter_order
            });
        }

        return decryptedChapters;
    }

    /**
     * Retrieves all chapters organized by acts for a specific book.
     * Caches decrypted titles to avoid redundant decryption operations.
     * @param userId - The unique identifier of the user
     * @param bookId - The unique identifier of the book
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns An array of ActChapter containing chapter details with act information
     */
    public static getAllChapterFromActs(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): ActChapter[] {
        const actChapterQueryResults: ActChapterQuery[] = ChapterRepo.fetchAllChapterForActs(userId, bookId, lang);
        const actChapters: ActChapter[] = [];
        const decryptedTitleCache: { id: string; title: string }[] = [];
        const userEncryptionKey: string = getUserEncryptionKey(userId);

        if (actChapterQueryResults.length === 0) {
            return [];
        }

        for (const chapterQueryResult of actChapterQueryResults) {
            let decryptedTitle: string = '';
            const cachedTitleIndex: number = decryptedTitleCache.findIndex(
                (cachedItem: { id: string; title: string }) => cachedItem.id === chapterQueryResult.chapter_id
            );

            if (cachedTitleIndex > -1) {
                decryptedTitle = decryptedTitleCache[cachedTitleIndex]?.title ?? '';
            } else {
                decryptedTitle = System.decryptDataWithUserKey(chapterQueryResult.title, userEncryptionKey);
                decryptedTitleCache.push({ id: chapterQueryResult.chapter_id, title: decryptedTitle });
            }

            actChapters.push({
                chapterId: chapterQueryResult.chapter_id,
                title: decryptedTitle,
                actId: chapterQueryResult.act_id,
                chapterInfoId: chapterQueryResult.chapter_info_id,
                chapterOrder: chapterQueryResult.chapter_order,
                goal: chapterQueryResult.goal ? System.decryptDataWithUserKey(chapterQueryResult.goal, userEncryptionKey) : '',
                summary: chapterQueryResult.summary ? System.decryptDataWithUserKey(chapterQueryResult.summary, userEncryptionKey) : '',
                incidentId: chapterQueryResult.incident_id,
                plotPointId: chapterQueryResult.plot_point_id
            });
        }

        return actChapters;
    }

    /**
     * Retrieves a complete chapter with its content for a specific version.
     * Optionally updates the last chapter record for the book.
     * @param userId - The unique identifier of the user
     * @param chapterId - The unique identifier of the chapter
     * @param version - The version number of the chapter content
     * @param bookId - Optional book identifier to update last chapter record
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns ChapterProps containing chapter details and content
     */
    public static getWholeChapter(userId: string, chapterId: string, version: number, bookId?: string, lang: 'fr' | 'en' = 'fr'): ChapterProps {
        const chapterContentResult: ChapterContentQueryResult = ChapterContentRepository.fetchWholeChapter(userId, chapterId, version, lang);
        const userEncryptionKey: string = getUserEncryptionKey(userId);

        if (bookId) {
            ChapterRepo.updateLastChapterRecord(userId, bookId, chapterId, version, lang);
        }

        return {
            chapterId: chapterContentResult.chapter_id,
            title: System.decryptDataWithUserKey(chapterContentResult.title, userEncryptionKey),
            chapterOrder: chapterContentResult.chapter_order,
            chapterContent: {
                content: chapterContentResult.content ? System.decryptDataWithUserKey(chapterContentResult.content, userEncryptionKey) : '',
                version: version,
                wordsCount: chapterContentResult.words_count
            }
        };
    }

    /**
     * Saves the content of a chapter for a specific version.
     * Encrypts the content before storing it in the database.
     * @param userId - The unique identifier of the user
     * @param chapterId - The unique identifier of the chapter
     * @param version - The version number of the chapter content
     * @param content - The JSON content to save
     * @param wordsCount - The word count of the content
     * @param currentTime - The current timestamp (unused, actual timestamp is generated)
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the content was saved successfully, false otherwise
     */
    public static saveChapterContent(userId: string, chapterId: string, version: number, content: JSON, wordsCount: number, currentTime: number, lang: 'fr' | 'en' = 'fr'): boolean {
        const userEncryptionKey: string = getUserEncryptionKey(userId);
        const encryptedContent: string = System.encryptDataWithUserKey(JSON.stringify(content), userEncryptionKey);
        return ChapterContentRepository.updateChapterContent(userId, chapterId, version, encryptedContent, wordsCount, System.timeStampInSeconds(), lang);
    }

    /**
     * Retrieves the last accessed chapter for a specific book.
     * Falls back to the first chapter content if no last chapter record exists.
     * @param userId - The unique identifier of the user
     * @param bookId - The unique identifier of the book
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns ChapterProps containing chapter details and content, or null if no chapters exist
     */
    public static getLastChapter(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): ChapterProps | null {
        const lastChapterRecord: LastChapterResult | null = ChapterRepo.fetchLastChapter(userId, bookId, lang);

        if (lastChapterRecord) {
            return Chapter.getWholeChapter(userId, lastChapterRecord.chapter_id, lastChapterRecord.version, bookId, lang);
        }

        const chapterContentResults: ChapterContentQueryResult[] = ChapterContentRepository.fetchLastChapterContent(userId, bookId, lang);

        if (chapterContentResults.length === 0) {
            return null;
        }

        const firstChapterContent: ChapterContentQueryResult = chapterContentResults[0];
        const userEncryptionKey: string = getUserEncryptionKey(userId);

        return {
            chapterId: firstChapterContent.chapter_id,
            title: firstChapterContent.title ? System.decryptDataWithUserKey(firstChapterContent.title, userEncryptionKey) : '',
            chapterOrder: firstChapterContent.chapter_order,
            chapterContent: {
                content: firstChapterContent.content ? System.decryptDataWithUserKey(firstChapterContent.content, userEncryptionKey) : '',
                version: firstChapterContent.version,
                wordsCount: firstChapterContent.words_count
            }
        };
    }

    /**
     * Adds a new chapter to a book.
     * Validates that the chapter name is unique within the book.
     * @param userId - The unique identifier of the user
     * @param bookId - The unique identifier of the book
     * @param title - The title of the new chapter
     * @param wordsCount - The initial word count of the chapter
     * @param chapterOrder - The order position of the chapter
     * @param lang - The language for error messages ('fr' or 'en')
     * @param existingChapterId - Optional existing chapter ID for updates
     * @returns The unique identifier of the created chapter
     * @throws Error if a chapter with the same name already exists
     */
    public static addChapter(userId: string, bookId: string, title: string, wordsCount: number, chapterOrder: number, lang: 'fr' | 'en' = 'fr', existingChapterId?: string): string {
        const hashedTitle: string = System.hashElement(title);
        const userEncryptionKey: string = getUserEncryptionKey(userId);
        const encryptedTitle: string = System.encryptDataWithUserKey(title, userEncryptionKey);

        if (!existingChapterId && ChapterRepo.checkNameDuplication(userId, bookId, hashedTitle, lang)) {
            throw new Error(lang === 'fr' ? `Ce nom de chapitre existe déjà.` : `This chapter name already exists.`);
        }

        const chapterId: string = existingChapterId || System.createUniqueId();
        return ChapterRepo.insertChapter(chapterId, userId, bookId, encryptedTitle, hashedTitle, wordsCount, chapterOrder, lang);
    }

    /**
     * Removes a chapter from the database.
     * @param userId - The unique identifier of the user
     * @param bookId - The unique identifier of the book
     * @param chapterId - The unique identifier of the chapter to remove
     * @param deletedAt - The timestamp of deletion (from UI via System.timeStampInSeconds())
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the chapter was removed successfully, false otherwise
     */
    public static removeChapter(userId: string, bookId: string, chapterId: string, deletedAt: number, lang: 'fr' | 'en' = 'fr'): boolean {
        const deleted: boolean = ChapterRepo.deleteChapter(userId, chapterId, lang);
        if (deleted) {
            RemovedItem.deleteTracker(userId, bookId, 'book_chapters', chapterId, deletedAt, lang);
        }
        return deleted;
    }

    /**
     * Adds chapter information linking a chapter to an act, plot point, and/or incident.
     * @param userId - The unique identifier of the user
     * @param chapterId - The unique identifier of the chapter
     * @param actId - The act number the chapter belongs to
     * @param bookId - The unique identifier of the book
     * @param plotId - Optional plot point identifier
     * @param incidentId - Optional incident identifier
     * @param lang - The language for error messages ('fr' or 'en')
     * @param existingChapterInfoId - Optional existing chapter info ID for updates
     * @returns The unique identifier of the created chapter information
     */
    public static addChapterInformation(userId: string, chapterId: string, actId: number, bookId: string, plotId: string | null, incidentId: string | null, lang: 'fr' | 'en' = 'fr', existingChapterInfoId?: string): string {
        const chapterInfoId: string = existingChapterInfoId || System.createUniqueId();
        return ChapterRepo.insertChapterInformation(chapterInfoId, userId, chapterId, actId, bookId, plotId, incidentId, lang);
    }

    /**
     * Updates a chapter's title and order position.
     * @param userId - The unique identifier of the user
     * @param chapterId - The unique identifier of the chapter
     * @param title - The new title for the chapter
     * @param chapterOrder - The new order position for the chapter
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the chapter was updated successfully, false otherwise
     */
    public static updateChapter(userId: string, chapterId: string, title: string, chapterOrder: number, lang: 'fr' | 'en' = 'fr'): boolean {
        const hashedTitle: string = System.hashElement(title);
        const userEncryptionKey: string = getUserEncryptionKey(userId);
        const encryptedTitle: string = System.encryptDataWithUserKey(title, userEncryptionKey);
        return ChapterRepo.updateChapter(userId, chapterId, encryptedTitle, hashedTitle, chapterOrder, System.timeStampInSeconds(), lang);
    }

    /**
     * Updates chapter information for multiple chapters including summary and goal.
     * @param chapters - Array of ActChapter objects containing updated information
     * @param userId - The unique identifier of the user
     * @param actId - The act number the chapters belong to
     * @param bookId - The unique identifier of the book
     * @param incidentId - Optional incident identifier
     * @param plotId - Optional plot point identifier
     * @param lang - The language for error messages ('fr' or 'en')
     */
    static updateChapterInfos(chapters: ActChapter[], userId: string, actId: number, bookId: string, incidentId: string | null, plotId: string | null, lang: 'fr' | 'en' = 'fr'): void {
        const userEncryptionKey: string = getUserEncryptionKey(userId);

        for (const chapterData of chapters) {
            const encryptedSummary: string = chapterData.summary ? System.encryptDataWithUserKey(chapterData.summary, userEncryptionKey) : '';
            const encryptedGoal: string = chapterData.goal ? System.encryptDataWithUserKey(chapterData.goal, userEncryptionKey) : '';
            const chapterId: string = chapterData.chapterId;
            ChapterRepo.updateChapterInfos(userId, chapterId, actId, bookId, incidentId, plotId, encryptedSummary, encryptedGoal, System.timeStampInSeconds(), lang);
        }
    }

    /**
     * Retrieves the companion content for a chapter (previous version content).
     * @param userId - The unique identifier of the user
     * @param chapterId - The unique identifier of the chapter
     * @param version - The current version number (companion is version - 1)
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns CompanionContent containing the previous version's content
     */
    static getCompanionContent(userId: string, chapterId: string, version: number, lang: 'fr' | 'en' = 'fr'): CompanionContent {
        const companionVersion: number = version - 1;
        const companionContentResults: CompanionContentQueryResult[] = ChapterContentRepository.fetchCompanionContent(userId, chapterId, companionVersion, lang);

        if (companionContentResults.length === 0) {
            return {
                version: version,
                content: '',
                wordsCount: 0
            };
        }

        const companionContentData: CompanionContentQueryResult = companionContentResults[0];
        const userEncryptionKey: string = getUserEncryptionKey(userId);

        return {
            version: companionContentData.version,
            content: companionContentData.content ? System.decryptDataWithUserKey(companionContentData.content, userEncryptionKey) : '',
            wordsCount: companionContentData.words_count
        };
    }

    /**
     * Retrieves the story context for a chapter including act summaries, incidents, and plot points.
     * @param userId - The unique identifier of the user
     * @param chapterId - The unique identifier of the chapter
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns An array of ActStory containing story context organized by act
     */
    static getChapterStory(userId: string, chapterId: string, lang: 'fr' | 'en' = 'fr'): ActStory[] {
        const chapterStoryResults: ChapterStoryQueryResult[] = ChapterRepo.fetchChapterStory(userId, chapterId, lang);
        const actStoriesMap: Record<number, ActStory> = {};
        const userEncryptionKey: string = getUserEncryptionKey(userId);

        for (const storyResult of chapterStoryResults) {
            const actId: number = storyResult.act_id;

            if (!actStoriesMap[actId]) {
                actStoriesMap[actId] = {
                    actId: actId,
                    summary: storyResult.summary ? System.decryptDataWithUserKey(storyResult.summary, userEncryptionKey) : '',
                    chapterSummary: storyResult.chapter_summary ? System.decryptDataWithUserKey(storyResult.chapter_summary, userEncryptionKey) : '',
                    chapterGoal: storyResult.chapter_goal ? System.decryptDataWithUserKey(storyResult.chapter_goal, userEncryptionKey) : '',
                    incidents: [],
                    plotPoints: []
                };
            }

            if (storyResult.incident_id) {
                const decryptedIncidentTitle: string = storyResult.incident_title ? System.decryptDataWithUserKey(storyResult.incident_title, userEncryptionKey) : '';
                const decryptedIncidentSummary: string = storyResult.incident_summary ? System.decryptDataWithUserKey(storyResult.incident_summary, userEncryptionKey) : '';

                const incidentAlreadyExists: boolean = actStoriesMap[actId].incidents.some(
                    (existingIncident) => existingIncident.incidentTitle === decryptedIncidentTitle && existingIncident.incidentSummary === decryptedIncidentSummary
                );

                if (!incidentAlreadyExists) {
                    actStoriesMap[actId].incidents.push({
                        incidentTitle: decryptedIncidentTitle,
                        incidentSummary: decryptedIncidentSummary,
                        chapterSummary: storyResult.chapter_summary ? System.decryptDataWithUserKey(storyResult.chapter_summary, userEncryptionKey) : '',
                        chapterGoal: storyResult.chapter_goal ? System.decryptDataWithUserKey(storyResult.chapter_goal, userEncryptionKey) : ''
                    });
                }
            }

            if (storyResult.plot_point_id) {
                const decryptedPlotTitle: string = storyResult.plot_title ? System.decryptDataWithUserKey(storyResult.plot_title, userEncryptionKey) : '';
                const decryptedPlotSummary: string = storyResult.plot_summary ? System.decryptDataWithUserKey(storyResult.plot_summary, userEncryptionKey) : '';

                const plotPointAlreadyExists: boolean = actStoriesMap[actId].plotPoints.some(
                    (existingPlotPoint) => existingPlotPoint.plotTitle === decryptedPlotTitle && existingPlotPoint.plotSummary === decryptedPlotSummary
                );

                if (!plotPointAlreadyExists) {
                    actStoriesMap[actId].plotPoints.push({
                        plotTitle: decryptedPlotTitle,
                        plotSummary: decryptedPlotSummary,
                        chapterSummary: storyResult.chapter_summary ? System.decryptDataWithUserKey(storyResult.chapter_summary, userEncryptionKey) : '',
                        chapterGoal: storyResult.chapter_goal ? System.decryptDataWithUserKey(storyResult.chapter_goal, userEncryptionKey) : ''
                    });
                }
            }
        }

        return Object.values(actStoriesMap);
    }

    /**
     * Retrieves the content of a specific chapter version.
     * @param userId - The unique identifier of the user
     * @param chapterId - The unique identifier of the chapter
     * @param version - The version number of the content to retrieve
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns The decrypted content string, or empty string if not found
     */
    static getChapterContentByVersion(userId: string, chapterId: string, version: number, lang: 'fr' | 'en' = 'fr'): string {
        const contentResult: ContentQueryResult = ChapterContentRepository.fetchChapterContentByVersion(userId, chapterId, version, lang);
        const userEncryptionKey: string = getUserEncryptionKey(userId);
        return contentResult.content ? System.decryptDataWithUserKey(contentResult.content, userEncryptionKey) : '';
    }

    /**
     * Removes chapter information by its identifier.
     * @param userId - The unique identifier of the user
     * @param bookId - The unique identifier of the book
     * @param chapterInfoId - The unique identifier of the chapter information to remove
     * @param deletedAt - The timestamp of deletion (from UI via System.timeStampInSeconds())
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns True if the chapter information was removed successfully, false otherwise
     */
    static removeChapterInformation(userId: string, bookId: string, chapterInfoId: string, deletedAt: number, lang: 'fr' | 'en' = 'fr'): boolean {
        const deleted: boolean = ChapterRepo.deleteChapterInformation(userId, chapterInfoId, lang);
        if (deleted) {
            RemovedItem.deleteTracker(userId, bookId, 'book_chapter_infos', chapterInfoId, deletedAt, lang);
        }
        return deleted;
    }

    /**
     * Converts TipTap JSON content to HTML string.
     * Handles various node types including paragraphs, headings, lists, and text marks.
     * @param tipTapContent - The TipTap JSON content to convert
     * @returns The converted HTML string
     */
    static tipTapToHtml(tipTapContent: JSON): string {
        const escapeHtmlCharacters = (text: string): string => {
            return text
                .replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#039;');
        };

        const renderTextWithMarks = (text: string, marks?: TipTapMark[]): string => {
            if (!marks || marks.length === 0) return escapeHtmlCharacters(text);

            let renderedText: string = escapeHtmlCharacters(text);

            marks.forEach((mark: TipTapMark) => {
                switch (mark.type) {
                    case 'bold':
                        renderedText = `<strong>${renderedText}</strong>`;
                        break;
                    case 'italic':
                        renderedText = `<em>${renderedText}</em>`;
                        break;
                    case 'underline':
                        renderedText = `<u>${renderedText}</u>`;
                        break;
                    case 'strike':
                        renderedText = `<s>${renderedText}</s>`;
                        break;
                    case 'code':
                        renderedText = `<code>${renderedText}</code>`;
                        break;
                    case 'link':
                        const linkHref: string = (mark.attrs?.href as string) || '#';
                        renderedText = `<a href="${escapeHtmlCharacters(linkHref)}">${renderedText}</a>`;
                        break;
                }
            });

            return renderedText;
        };

        const renderTipTapNode = (node: TipTapNode): string => {
            if (!node) return '';

            if (node.type === 'text') {
                const textContent: string = node.text || '\u00A0';
                return renderTextWithMarks(textContent, node.marks);
            }

            const childrenHtml: string = node.content?.map(renderTipTapNode).join('') || '';
            const textAlignStyle: string = node.attrs?.textAlign ? ` style="text-align: ${node.attrs.textAlign}"` : '';

            switch (node.type) {
                case 'doc':
                    return childrenHtml;
                case 'paragraph':
                    return `<p${textAlignStyle}>${childrenHtml || '\u00A0'}</p>`;
                case 'heading':
                    const headingLevel: number = (node.attrs?.level as number) || 1;
                    return `<h${headingLevel}${textAlignStyle}>${childrenHtml}</h${headingLevel}>`;
                case 'bulletList':
                    return `<ul>${childrenHtml}</ul>`;
                case 'orderedList':
                    return `<ol>${childrenHtml}</ol>`;
                case 'listItem':
                    return `<li>${childrenHtml}</li>`;
                case 'blockquote':
                    return `<blockquote>${childrenHtml}</blockquote>`;
                case 'codeBlock':
                    return `<pre><code>${childrenHtml}</code></pre>`;
                case 'hardBreak':
                    return '<br />';
                case 'horizontalRule':
                    return '<hr />';
                default:
                    return childrenHtml;
            }
        };

        const contentNode: TipTapNode = tipTapContent as unknown as TipTapNode;
        return renderTipTapNode(contentNode);
    }

    /**
     * Retrieves all chapters with their content data for a specific book.
     * @param userId - The unique identifier of the user
     * @param bookId - The unique identifier of the book
     * @param lang - The language for error messages ('fr' or 'en')
     * @returns An array of ChapterContentData containing chapter details with content
     */
    static getAllChapters(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): ChapterContentData[] {
        try {
            const completeBookData: CompleteBookData = Book.completeBookData(userId, bookId, lang);
            return Chapter.getChaptersOrSheet(completeBookData.chapters);
        } catch (error: unknown) {
            return [];
        }
    }

    /**
     * Processes book chapters to return either sheet content or chapter content.
     * If only a sheet exists (order -1), returns the sheet. Otherwise, returns all positive-order chapters.
     * @param bookChapters - Array of CompleteChapterContent from the book
     * @returns An array of ChapterContentData with processed content
     */
    static getChaptersOrSheet(bookChapters: CompleteChapterContent[]): ChapterContentData[] {
        const processedChapters: ChapterContentData[] = [];
        const sheetContent: CompleteChapterContent | undefined = bookChapters.find(
            (chapter: CompleteChapterContent): boolean => chapter.order === -1
        );
        const regularChapter: CompleteChapterContent | undefined = bookChapters.find(
            (chapter: CompleteChapterContent): boolean => chapter.order > 0
        );

        if (sheetContent && !regularChapter) {
            processedChapters.push({
                title: sheetContent.title,
                chapterOrder: sheetContent.order,
                content: System.htmlToText(Chapter.tipTapToHtml(JSON.parse(sheetContent.content))),
                wordsCount: 0,
                version: sheetContent.version || 0
            });
        } else if (regularChapter) {
            for (const chapterData of bookChapters) {
                if (chapterData.order < 0) continue;
                processedChapters.push({
                    title: chapterData.title,
                    chapterOrder: chapterData.order,
                    content: System.htmlToText(Chapter.tipTapToHtml(JSON.parse(chapterData.content))),
                    wordsCount: 0,
                    version: chapterData.version || 0
                });
            }
        }

        return processedChapters;
    }

    static getChaptersExportInfo(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): ChapterExportInfo[] {
        const results: ChapterExportInfoResult[] = ChapterRepo.fetchChaptersExportInfo(userId, bookId, lang);
        const userEncryptionKey: string = getUserEncryptionKey(userId);
        const exportInfos: ChapterExportInfo[] = [];

        for (const result of results) {
            if (!result.available_versions) continue;
            const versions: number[] = result.available_versions
                .split(',')
                .map((v: string): number => parseInt(v, 10))
                .filter((v: number): boolean => !isNaN(v));
            if (versions.length === 0) continue;
            exportInfos.push({
                chapterId: result.chapter_id,
                title: result.title ? System.decryptDataWithUserKey(result.title, userEncryptionKey) : '',
                chapterOrder: result.chapter_order,
                availableVersions: versions.sort((a: number, b: number): number => a - b)
            });
        }

        return exportInfos;
    }

    static getCompleteBookDataWithSelections(userId: string, bookId: string, selections: ChapterSelectionParam[] | null, lang: 'fr' | 'en' = 'fr'): CompleteBookData {
        if (!selections || selections.length === 0) {
            return Book.completeBookData(userId, bookId, lang);
        }

        const bookData: CompleteBookData = Book.completeBookData(userId, bookId, lang);
        const selectedResults: SelectedChapterContentResult[] = ChapterRepo.fetchSelectedChaptersContent(bookId, selections, lang);
        const userEncryptionKey: string = getUserEncryptionKey(userId);
        const selectedChapters: CompleteChapterContent[] = [];

        for (const result of selectedResults) {
            selectedChapters.push({
                id: result.chapter_id,
                title: result.title ? System.decryptDataWithUserKey(result.title, userEncryptionKey) : '',
                content: result.content ? System.decryptDataWithUserKey(result.content, userEncryptionKey) : '',
                order: result.chapter_order,
                version: result.version
            });
        }

        return {
            ...bookData,
            chapters: selectedChapters
        };
    }
}
