use std::collections::HashMap;

use rusqlite::Connection;
use serde::Serialize;
use serde_json::Value;

use crate::crypto::encryption::{decrypt_data_with_user_key, encrypt_data_with_user_key, hash_element};
use crate::crypto::key_manager::get_user_encryption_key;
use crate::domains::book::service as book_service;
use crate::domains::chapter::repo;
use crate::domains::chapter_content::repo as chapter_content_repo;
use crate::domains::tombstone::repo as tombstone_repo;
use crate::error::{AppError, AppResult};
use crate::helpers::{create_unique_id, html_to_text, timestamp_in_seconds};
use crate::shared::types::Lang;

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ChapterContent {
    pub version: i64,
    pub content: String,
    pub words_count: i64,
}

pub struct ChapterContentData {
    pub title: String,
    pub content: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ChapterProps {
    pub chapter_id: String,
    pub title: String,
    pub chapter_order: i64,
    pub chapter_content: Option<ChapterContent>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CompanionContent {
    pub version: i64,
    pub content: String,
    pub words_count: i64,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CompleteChapterContent {
    pub id: String,
    pub title: String,
    pub content: String,
    pub order: i64,
    pub version: Option<i64>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ChapterExportInfo {
    pub chapter_id: String,
    pub title: String,
    pub chapter_order: i64,
    pub available_versions: Vec<i64>,
}

pub use crate::domains::act::service::ActChapter;

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct IncidentStory {
    pub incident_title: String,
    pub incident_summary: String,
    pub chapter_summary: String,
    pub chapter_goal: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PlotPointStory {
    pub plot_title: String,
    pub plot_summary: String,
    pub chapter_summary: String,
    pub chapter_goal: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ActStory {
    pub act_id: i64,
    pub summary: String,
    pub chapter_summary: String,
    pub chapter_goal: String,
    pub incidents: Vec<IncidentStory>,
    pub plot_points: Vec<PlotPointStory>,
}

struct TipTapMark {
    mark_type: String,
    attrs: Option<serde_json::Map<String, Value>>,
}

struct TipTapNode {
    node_type: Option<String>,
    text: Option<String>,
    content: Option<Vec<TipTapNode>>,
    attrs: Option<serde_json::Map<String, Value>>,
    marks: Option<Vec<TipTapMark>>,
}

/// Retrieves all chapters from a specific book.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language for error messages
/// Returns an array of ChapterProps containing chapter details.
pub fn get_all_chapters_from_a_book(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Vec<ChapterProps>> {
    let chapter_query_results: Vec<repo::ChapterQueryResult> = repo::fetch_all_chapter_from_a_book(conn, user_id, book_id, lang)?;
    let mut decrypted_chapters: Vec<ChapterProps> = Vec::new();
    let user_encryption_key: String = get_user_encryption_key(user_id)?;

    for chapter_result in chapter_query_results {
        let decrypted_title: String = decrypt_data_with_user_key(&chapter_result.title, &user_encryption_key)?;
        decrypted_chapters.push(ChapterProps {
            chapter_id: chapter_result.chapter_id,
            title: decrypted_title,
            chapter_order: chapter_result.chapter_order,
            chapter_content: None,
        });
    }

    Ok(decrypted_chapters)
}

/// Retrieves all chapters organized by acts for a specific book.
/// Caches decrypted titles to avoid redundant decryption operations.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book

/// Retrieves a complete chapter with its content for a specific version.
/// Optionally updates the last chapter record for the book.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `chapter_id` - The unique identifier of the chapter
/// * `version` - The version number of the chapter content
/// * `book_id` - Optional book identifier to update last chapter record
/// * `lang` - The language for error messages
/// Returns ChapterProps containing chapter details and content.
pub fn get_whole_chapter(conn: &Connection, user_id: &str, chapter_id: &str, version: i64, book_id: Option<&str>, lang: Lang) -> AppResult<ChapterProps> {
    let chapter_content_result: chapter_content_repo::ChapterContentQueryResult = chapter_content_repo::fetch_whole_chapter(conn, user_id, chapter_id, version, lang)?;
    let user_encryption_key: String = get_user_encryption_key(user_id)?;

    if let Some(book_id_value) = book_id {
        repo::update_last_chapter_record(conn, user_id, book_id_value, chapter_id, version, lang)?;
    }

    Ok(ChapterProps {
        chapter_id: chapter_content_result.chapter_id,
        title: decrypt_data_with_user_key(&chapter_content_result.title, &user_encryption_key)?,
        chapter_order: chapter_content_result.chapter_order,
        chapter_content: Some(ChapterContent {
            content: if chapter_content_result.content.is_empty() { String::new() } else { decrypt_data_with_user_key(&chapter_content_result.content, &user_encryption_key)? },
            version,
            words_count: chapter_content_result.words_count,
        }),
    })
}

/// Saves the content of a chapter for a specific version.
/// Encrypts the content before storing it in the database.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `chapter_id` - The unique identifier of the chapter
/// * `version` - The version number of the chapter content
/// * `content` - The JSON content to save
/// * `words_count` - The word count of the content
/// * `current_time` - The current timestamp (unused, actual timestamp is generated)
/// * `lang` - The language for error messages
/// Returns true if the content was saved successfully, false otherwise.
pub fn save_chapter_content(conn: &Connection, user_id: &str, chapter_id: &str, version: i64, content: &str, words_count: i64, _current_time: i64, lang: Lang) -> AppResult<bool> {
    let user_encryption_key: String = get_user_encryption_key(user_id)?;
    let encrypted_content: String = encrypt_data_with_user_key(content, &user_encryption_key)?;
    chapter_content_repo::update_chapter_content(conn, user_id, chapter_id, version, &encrypted_content, words_count, timestamp_in_seconds(), lang)
}

/// Retrieves the last accessed chapter for a specific book.
/// Falls back to the first chapter content if no last chapter record exists.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language for error messages
/// Returns ChapterProps containing chapter details and content, or None if no chapters exist.
pub fn get_last_chapter(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Option<ChapterProps>> {
    let last_chapter_record: Option<repo::LastChapterResult> = repo::fetch_last_chapter(conn, user_id, book_id, lang)?;

    if let Some(last_chapter) = last_chapter_record {
        let chapter_props: ChapterProps = get_whole_chapter(conn, user_id, &last_chapter.chapter_id, last_chapter.version, Some(book_id), lang)?;
        return Ok(Some(chapter_props));
    }

    let chapter_content_results: Vec<chapter_content_repo::ChapterContentQueryResult> = chapter_content_repo::fetch_last_chapter_content(conn, user_id, book_id, lang)?;

    if chapter_content_results.is_empty() {
        return Ok(None);
    }

    let first_chapter_content: &chapter_content_repo::ChapterContentQueryResult = &chapter_content_results[0];
    let user_encryption_key: String = get_user_encryption_key(user_id)?;

    Ok(Some(ChapterProps {
        chapter_id: first_chapter_content.chapter_id.clone(),
        title: if first_chapter_content.title.is_empty() { String::new() } else { decrypt_data_with_user_key(&first_chapter_content.title, &user_encryption_key)? },
        chapter_order: first_chapter_content.chapter_order,
        chapter_content: Some(ChapterContent {
            content: if first_chapter_content.content.is_empty() { String::new() } else { decrypt_data_with_user_key(&first_chapter_content.content, &user_encryption_key)? },
            version: first_chapter_content.version,
            words_count: first_chapter_content.words_count,
        }),
    }))
}

/// Adds a new chapter to a book.
/// Validates that the chapter name is unique within the book.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `title` - The title of the new chapter
/// * `words_count` - The initial word count of the chapter
/// * `chapter_order` - The order position of the chapter
/// * `lang` - The language for error messages
/// * `existing_chapter_id` - Optional existing chapter ID for updates
/// Returns the unique identifier of the created chapter.
/// Errors if a chapter with the same name already exists.
pub fn add_chapter(conn: &Connection, user_id: &str, book_id: &str, title: &str, words_count: i64, chapter_order: i64, lang: Lang, existing_chapter_id: Option<&str>) -> AppResult<String> {
    let hashed_title: String = hash_element(title);
    let user_encryption_key: String = get_user_encryption_key(user_id)?;
    let encrypted_title: String = encrypt_data_with_user_key(title, &user_encryption_key)?;

    if existing_chapter_id.is_none() && repo::check_name_duplication(conn, user_id, book_id, &hashed_title, lang)? {
        return Err(AppError::Internal(if lang == Lang::Fr { "Ce nom de chapitre existe déjà.".to_string() } else { "This chapter name already exists.".to_string() }));
    }

    let chapter_id: String = create_unique_id(existing_chapter_id);
    repo::insert_chapter(conn, &chapter_id, user_id, book_id, &encrypted_title, &hashed_title, words_count, chapter_order, lang)
}

/// Removes a chapter from the database.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `chapter_id` - The unique identifier of the chapter to remove
/// * `deleted_at` - The timestamp of deletion
/// * `lang` - The language for error messages
/// Returns true if the chapter was removed successfully, false otherwise.
pub fn remove_chapter(conn: &Connection, user_id: &str, book_id: &str, chapter_id: &str, deleted_at: i64, lang: Lang) -> AppResult<bool> {
    let deleted: bool = repo::delete_chapter(conn, user_id, chapter_id, lang)?;
    if deleted {
        tombstone_repo::insert(conn, book_id, "book_chapters", chapter_id, Some(book_id), user_id, deleted_at, lang)?;
    }
    Ok(deleted)
}

/// Adds chapter information linking a chapter to an act, plot point, and/or incident.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `chapter_id` - The unique identifier of the chapter
/// * `act_id` - The act number the chapter belongs to
/// * `book_id` - The unique identifier of the book
/// * `plot_id` - Optional plot point identifier
/// * `incident_id` - Optional incident identifier
/// * `lang` - The language for error messages
/// * `existing_chapter_info_id` - Optional existing chapter info ID for updates
/// Returns the unique identifier of the created chapter information.
pub fn add_chapter_information(conn: &Connection, user_id: &str, chapter_id: &str, act_id: i64, book_id: &str, plot_id: Option<&str>, incident_id: Option<&str>, lang: Lang, existing_chapter_info_id: Option<&str>) -> AppResult<String> {
    let chapter_info_id: String = create_unique_id(existing_chapter_info_id);
    repo::insert_chapter_information(conn, &chapter_info_id, user_id, chapter_id, act_id, book_id, plot_id, incident_id, lang)
}

/// Updates a chapter's title and order position.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `chapter_id` - The unique identifier of the chapter
/// * `title` - The new title for the chapter
/// * `chapter_order` - The new order position for the chapter
/// * `lang` - The language for error messages
/// Returns true if the chapter was updated successfully, false otherwise.
pub fn update_chapter(conn: &Connection, user_id: &str, chapter_id: &str, title: &str, chapter_order: i64, lang: Lang) -> AppResult<bool> {
    let hashed_title: String = hash_element(title);
    let user_encryption_key: String = get_user_encryption_key(user_id)?;
    let encrypted_title: String = encrypt_data_with_user_key(title, &user_encryption_key)?;
    repo::update_chapter(conn, user_id, chapter_id, &encrypted_title, &hashed_title, chapter_order, timestamp_in_seconds(), lang)
}

/// Updates chapter information for multiple chapters including summary and goal.
/// * `conn` - Database connection
/// * `chapters` - Array of ActChapter objects containing updated information
/// * `user_id` - The unique identifier of the user
/// * `act_id` - The act number the chapters belong to
/// * `book_id` - The unique identifier of the book
/// * `incident_id` - Optional incident identifier
/// * `plot_id` - Optional plot point identifier
/// * `lang` - The language for error messages
pub fn update_chapter_infos(conn: &Connection, chapters: &[ActChapter], user_id: &str, act_id: i64, book_id: &str, incident_id: Option<&str>, plot_id: Option<&str>, lang: Lang) -> AppResult<()> {
    let user_encryption_key: String = get_user_encryption_key(user_id)?;

    for chapter_data in chapters {
        let encrypted_summary: String = if chapter_data.summary.is_empty() { String::new() } else { encrypt_data_with_user_key(&chapter_data.summary, &user_encryption_key)? };
        let encrypted_goal: String = if chapter_data.goal.is_empty() { String::new() } else { encrypt_data_with_user_key(&chapter_data.goal, &user_encryption_key)? };
        let chapter_id: &str = &chapter_data.chapter_id;
        repo::update_chapter_infos(conn, user_id, chapter_id, act_id, book_id, incident_id, plot_id, &encrypted_summary, Some(&encrypted_goal), timestamp_in_seconds(), lang)?;
    }

    Ok(())
}

/// Retrieves the companion content for a chapter (previous version content).
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `chapter_id` - The unique identifier of the chapter
/// * `version` - The current version number (companion is version - 1)
/// * `lang` - The language for error messages
/// Returns CompanionContent containing the previous version's content.
pub fn get_companion_content(conn: &Connection, user_id: &str, chapter_id: &str, version: i64, lang: Lang) -> AppResult<CompanionContent> {
    let companion_version: i64 = version - 1;
    let companion_content_results: Vec<chapter_content_repo::CompanionContentQueryResult> = chapter_content_repo::fetch_companion_content(conn, user_id, chapter_id, companion_version, lang)?;

    if companion_content_results.is_empty() {
        return Ok(CompanionContent {
            version,
            content: String::new(),
            words_count: 0,
        });
    }

    let companion_content_data: &chapter_content_repo::CompanionContentQueryResult = &companion_content_results[0];
    let user_encryption_key: String = get_user_encryption_key(user_id)?;

    Ok(CompanionContent {
        version: companion_content_data.version,
        content: if companion_content_data.content.is_empty() { String::new() } else { decrypt_data_with_user_key(&companion_content_data.content, &user_encryption_key)? },
        words_count: companion_content_data.words_count,
    })
}

/// Retrieves the story context for a chapter including act summaries, incidents, and plot points.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `chapter_id` - The unique identifier of the chapter
/// * `lang` - The language for error messages
/// Returns an array of ActStory containing story context organized by act.
pub fn get_chapter_story(conn: &Connection, user_id: &str, chapter_id: &str, lang: Lang) -> AppResult<Vec<ActStory>> {
    let chapter_story_results: Vec<repo::ChapterStoryQueryResult> = repo::fetch_chapter_story(conn, user_id, chapter_id, lang)?;
    let mut act_stories_map: HashMap<i64, ActStory> = HashMap::new();
    let user_encryption_key: String = get_user_encryption_key(user_id)?;

    for story_result in &chapter_story_results {
        let act_id: i64 = story_result.act_id;

        if !act_stories_map.contains_key(&act_id) {
            act_stories_map.insert(act_id, ActStory {
                act_id,
                summary: if story_result.summary.is_empty() { String::new() } else { decrypt_data_with_user_key(&story_result.summary, &user_encryption_key)? },
                chapter_summary: if story_result.chapter_summary.is_empty() { String::new() } else { decrypt_data_with_user_key(&story_result.chapter_summary, &user_encryption_key)? },
                chapter_goal: if story_result.chapter_goal.is_empty() { String::new() } else { decrypt_data_with_user_key(&story_result.chapter_goal, &user_encryption_key)? },
                incidents: Vec::new(),
                plot_points: Vec::new(),
            });
        }

        if let Some(ref _incident_id) = story_result.incident_id {
            let decrypted_incident_title: String = if let Some(ref incident_title) = story_result.incident_title { if incident_title.is_empty() { String::new() } else { decrypt_data_with_user_key(incident_title, &user_encryption_key)? } } else { String::new() };
            let decrypted_incident_summary: String = if let Some(ref incident_summary) = story_result.incident_summary { if incident_summary.is_empty() { String::new() } else { decrypt_data_with_user_key(incident_summary, &user_encryption_key)? } } else { String::new() };

            let act_story = act_stories_map.get(&act_id).unwrap();
            let incident_already_exists: bool = act_story.incidents.iter().any(
                |existing_incident| existing_incident.incident_title == decrypted_incident_title && existing_incident.incident_summary == decrypted_incident_summary
            );

            if !incident_already_exists {
                let chapter_summary: String = if story_result.chapter_summary.is_empty() { String::new() } else { decrypt_data_with_user_key(&story_result.chapter_summary, &user_encryption_key)? };
                let chapter_goal: String = if story_result.chapter_goal.is_empty() { String::new() } else { decrypt_data_with_user_key(&story_result.chapter_goal, &user_encryption_key)? };
                let act_story_mut = act_stories_map.get_mut(&act_id).unwrap();
                act_story_mut.incidents.push(IncidentStory {
                    incident_title: decrypted_incident_title,
                    incident_summary: decrypted_incident_summary,
                    chapter_summary,
                    chapter_goal,
                });
            }
        }

        if let Some(ref _plot_point_id) = story_result.plot_point_id {
            let decrypted_plot_title: String = if let Some(ref plot_title) = story_result.plot_title { if plot_title.is_empty() { String::new() } else { decrypt_data_with_user_key(plot_title, &user_encryption_key)? } } else { String::new() };
            let decrypted_plot_summary: String = if let Some(ref plot_summary) = story_result.plot_summary { if plot_summary.is_empty() { String::new() } else { decrypt_data_with_user_key(plot_summary, &user_encryption_key)? } } else { String::new() };

            let act_story = act_stories_map.get(&act_id).unwrap();
            let plot_point_already_exists: bool = act_story.plot_points.iter().any(
                |existing_plot_point| existing_plot_point.plot_title == decrypted_plot_title && existing_plot_point.plot_summary == decrypted_plot_summary
            );

            if !plot_point_already_exists {
                let chapter_summary: String = if story_result.chapter_summary.is_empty() { String::new() } else { decrypt_data_with_user_key(&story_result.chapter_summary, &user_encryption_key)? };
                let chapter_goal: String = if story_result.chapter_goal.is_empty() { String::new() } else { decrypt_data_with_user_key(&story_result.chapter_goal, &user_encryption_key)? };
                let act_story_mut = act_stories_map.get_mut(&act_id).unwrap();
                act_story_mut.plot_points.push(PlotPointStory {
                    plot_title: decrypted_plot_title,
                    plot_summary: decrypted_plot_summary,
                    chapter_summary,
                    chapter_goal,
                });
            }
        }
    }

    Ok(act_stories_map.into_values().collect())
}

/// Retrieves the content of a specific chapter version.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `chapter_id` - The unique identifier of the chapter
/// * `version` - The version number of the content to retrieve
/// * `lang` - The language for error messages
/// Returns the decrypted content string, or empty string if not found.
pub fn get_chapter_content_by_version(conn: &Connection, user_id: &str, chapter_id: &str, version: i64, lang: Lang) -> AppResult<String> {
    let content_result: chapter_content_repo::ContentQueryResult = chapter_content_repo::fetch_chapter_content_by_version(conn, user_id, chapter_id, version, lang)?;
    let user_encryption_key: String = get_user_encryption_key(user_id)?;
    if content_result.content.is_empty() { Ok(String::new()) } else { Ok(decrypt_data_with_user_key(&content_result.content, &user_encryption_key)?) }
}

/// Removes chapter information by its identifier.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `chapter_info_id` - The unique identifier of the chapter information to remove
/// * `deleted_at` - The timestamp of deletion
/// * `lang` - The language for error messages
/// Returns true if the chapter information was removed successfully, false otherwise.
pub fn remove_chapter_information(conn: &Connection, user_id: &str, book_id: &str, chapter_info_id: &str, deleted_at: i64, lang: Lang) -> AppResult<bool> {
    let deleted: bool = repo::delete_chapter_information(conn, user_id, chapter_info_id, lang)?;
    if deleted {
        tombstone_repo::insert(conn, book_id, "book_chapter_infos", chapter_info_id, Some(book_id), user_id, deleted_at, lang)?;
    }
    Ok(deleted)
}

/// Converts TipTap JSON content to HTML string.
/// Handles various node types including paragraphs, headings, lists, and text marks.
/// * `tip_tap_content` - The TipTap JSON content to convert
/// Returns the converted HTML string.
pub fn tip_tap_to_html(tip_tap_content: &Value) -> String {
    fn escape_html_characters(text: &str) -> String {
        text.replace('&', "&amp;")
            .replace('<', "&lt;")
            .replace('>', "&gt;")
            .replace('"', "&quot;")
            .replace('\'', "&#039;")
    }

    fn parse_marks(marks_value: &Value) -> Option<Vec<TipTapMark>> {
        marks_value.as_array().map(|marks_array| {
            marks_array.iter().map(|mark_value| {
                TipTapMark {
                    mark_type: mark_value.get("type").and_then(|t| t.as_str()).unwrap_or("").to_string(),
                    attrs: mark_value.get("attrs").and_then(|a| a.as_object()).cloned(),
                }
            }).collect()
        })
    }

    fn parse_node(node_value: &Value) -> TipTapNode {
        let content: Option<Vec<TipTapNode>> = node_value.get("content").and_then(|c| c.as_array()).map(|children| {
            children.iter().map(|child| parse_node(child)).collect()
        });
        TipTapNode {
            node_type: node_value.get("type").and_then(|t| t.as_str()).map(|s| s.to_string()),
            text: node_value.get("text").and_then(|t| t.as_str()).map(|s| s.to_string()),
            content,
            attrs: node_value.get("attrs").and_then(|a| a.as_object()).cloned(),
            marks: node_value.get("marks").map(|m| parse_marks(m)).flatten(),
        }
    }

    fn render_text_with_marks(text: &str, marks: &Option<Vec<TipTapMark>>) -> String {
        let escaped_text: String = escape_html_characters(text);
        match marks {
            None => escaped_text,
            Some(marks_list) if marks_list.is_empty() => escaped_text,
            Some(marks_list) => {
                let mut rendered_text: String = escaped_text;
                for mark in marks_list {
                    match mark.mark_type.as_str() {
                        "bold" => rendered_text = format!("<strong>{}</strong>", rendered_text),
                        "italic" => rendered_text = format!("<em>{}</em>", rendered_text),
                        "underline" => rendered_text = format!("<u>{}</u>", rendered_text),
                        "strike" => rendered_text = format!("<s>{}</s>", rendered_text),
                        "code" => rendered_text = format!("<code>{}</code>", rendered_text),
                        "link" => {
                            let link_href: String = mark.attrs.as_ref()
                                .and_then(|attrs| attrs.get("href"))
                                .and_then(|href| href.as_str())
                                .map(|href| escape_html_characters(href))
                                .unwrap_or_else(|| "#".to_string());
                            rendered_text = format!("<a href=\"{}\">{}</a>", link_href, rendered_text);
                        }
                        _ => {}
                    }
                }
                rendered_text
            }
        }
    }

    fn render_tip_tap_node(node: &TipTapNode) -> String {
        if let Some(ref node_type) = node.node_type {
            if node_type == "text" {
                let text_content: &str = node.text.as_deref().unwrap_or("\u{00A0}");
                return render_text_with_marks(text_content, &node.marks);
            }
        } else {
            return String::new();
        }

        let children_html: String = node.content.as_ref()
            .map(|children| children.iter().map(|child| render_tip_tap_node(child)).collect::<Vec<String>>().join(""))
            .unwrap_or_default();

        let text_align_style: String = node.attrs.as_ref()
            .and_then(|attrs| attrs.get("textAlign"))
            .and_then(|align| align.as_str())
            .map(|align| format!(" style=\"text-align: {}\"", align))
            .unwrap_or_default();

        match node.node_type.as_deref().unwrap_or("") {
            "doc" => children_html,
            "paragraph" => {
                let paragraph_content: &str = if children_html.is_empty() { "\u{00A0}" } else { &children_html };
                format!("<p{}>{}</p>", text_align_style, paragraph_content)
            }
            "heading" => {
                let heading_level: i64 = node.attrs.as_ref()
                    .and_then(|attrs| attrs.get("level"))
                    .and_then(|level| level.as_i64())
                    .unwrap_or(1);
                format!("<h{}{}>{}</h{}>", heading_level, text_align_style, children_html, heading_level)
            }
            "bulletList" => format!("<ul>{}</ul>", children_html),
            "orderedList" => format!("<ol>{}</ol>", children_html),
            "listItem" => format!("<li>{}</li>", children_html),
            "blockquote" => format!("<blockquote>{}</blockquote>", children_html),
            "codeBlock" => format!("<pre><code>{}</code></pre>", children_html),
            "hardBreak" => "<br />".to_string(),
            "horizontalRule" => "<hr />".to_string(),
            _ => children_html,
        }
    }

    let content_node: TipTapNode = parse_node(tip_tap_content);
    render_tip_tap_node(&content_node)
}

/// Retrieves all chapters with their content data for a specific book.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language for error messages
/// Returns an array of ChapterContentData containing chapter details with content.

/// 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.
/// * `book_chapters` - Array of CompleteChapterContent from the book
/// Returns an array of ChapterContentData with processed content.
pub fn get_chapters_or_sheet(book_chapters: &[CompleteChapterContent]) -> Vec<ChapterContentData> {
    let mut processed_chapters: Vec<ChapterContentData> = Vec::new();
    let sheet_content: Option<&CompleteChapterContent> = book_chapters.iter().find(|chapter| chapter.order == -1);
    let regular_chapter: Option<&CompleteChapterContent> = book_chapters.iter().find(|chapter| chapter.order > 0);

    if sheet_content.is_some() && regular_chapter.is_none() {
        let sheet: &CompleteChapterContent = sheet_content.unwrap();
        let parsed_content: Value = serde_json::from_str(&sheet.content).unwrap_or(Value::Null);
        processed_chapters.push(ChapterContentData {
            title: sheet.title.clone(),
            content: html_to_text(&tip_tap_to_html(&parsed_content)),
        });
    } else if regular_chapter.is_some() {
        for chapter_data in book_chapters {
            if chapter_data.order < 0 { continue; }
            let parsed_content: Value = serde_json::from_str(&chapter_data.content).unwrap_or(Value::Null);
            processed_chapters.push(ChapterContentData {
                title: chapter_data.title.clone(),
                content: html_to_text(&tip_tap_to_html(&parsed_content)),
            });
        }
    }

    processed_chapters
}

/// Retrieves export information for all chapters of a book.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language for error messages
/// Returns an array of ChapterExportInfo with available versions.
pub fn get_chapters_export_info(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Vec<ChapterExportInfo>> {
    let results: Vec<repo::ChapterExportInfoResult> = repo::fetch_chapters_export_info(conn, user_id, book_id, lang)?;
    let user_encryption_key: String = get_user_encryption_key(user_id)?;
    let mut export_infos: Vec<ChapterExportInfo> = Vec::new();

    for result in results {
        if result.available_versions.is_empty() { continue; }
        let mut versions: Vec<i64> = result.available_versions
            .split(',')
            .filter_map(|version_string| version_string.trim().parse::<i64>().ok())
            .collect();
        if versions.is_empty() { continue; }
        versions.sort();
        export_infos.push(ChapterExportInfo {
            chapter_id: result.chapter_id,
            title: if result.title.is_empty() { String::new() } else { decrypt_data_with_user_key(&result.title, &user_encryption_key)? },
            chapter_order: result.chapter_order,
            available_versions: versions,
        });
    }

    Ok(export_infos)
}

/// Retrieves complete book data with selected chapter versions.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `selections` - Optional array of chapter selections with specific versions
/// * `lang` - The language for error messages
/// Returns complete book data with the selected chapter contents.
pub fn get_complete_book_data_with_selections(conn: &Connection, user_id: &str, book_id: &str, selections: Option<&[repo::ChapterSelectionParam]>, lang: Lang) -> AppResult<book_service::CompleteBookData> {
    if selections.is_none() || selections.map_or(true, |s| s.is_empty()) {
        return book_service::complete_book_data(conn, user_id, book_id, lang);
    }

    let book_data: book_service::CompleteBookData = book_service::complete_book_data(conn, user_id, book_id, lang)?;
    let selected_results: Vec<repo::SelectedChapterContentResult> = repo::fetch_selected_chapters_content(conn, book_id, selections.unwrap(), lang)?;
    let user_encryption_key: String = get_user_encryption_key(user_id)?;
    let mut selected_chapters: Vec<book_service::CompleteChapterContent> = Vec::new();

    for result in selected_results {
        selected_chapters.push(book_service::CompleteChapterContent {
            id: result.chapter_id,
            title: if result.title.is_empty() { String::new() } else { decrypt_data_with_user_key(&result.title, &user_encryption_key)? },
            content: if result.content.is_empty() { String::new() } else { decrypt_data_with_user_key(&result.content, &user_encryption_key)? },
            order: result.chapter_order,
            version: None,
        });
    }

    Ok(book_service::CompleteBookData {
        book_id: book_data.book_id,
        title: book_data.title,
        sub_title: book_data.sub_title,
        summary: book_data.summary,
        cover_image: book_data.cover_image,
        user_infos: book_data.user_infos,
        chapters: selected_chapters,
    })
}
