use crate::Versification;
use crate::book::get_book;
use std::fmt::{Display, Formatter as FmtFormatter};

/// Controls which book-name form is used when formatting references.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AbbreviationStyle {
    /// Use the canonical full book title, such as `Genesis`.
    Full,
    /// Use the first configured book abbreviation, such as `Gen`.
    Standard,
    /// Use a compact OSIS-like book identifier derived from the canonical title.
    Osis,
}

/// Controls which character is used between reference range endpoints.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SeparatorStyle {
    /// Use an en dash (`–`) between range endpoints.
    EnDash,
    /// Use an ASCII hyphen (`-`) between range endpoints.
    Hyphen,
}

/// Controls when full chapter or full book ranges are collapsed in formatted output.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RangeStyle {
    /// Preserve explicit start and end chapter/verse details.
    Preserve,
    /// Collapse ranges that cover full chapters, such as `Genesis 1:1–31` to `Genesis 1`.
    CollapseFullChapters,
    /// Collapse full chapters and full books, such as `Genesis 1:1–50:26` to `Genesis`.
    CollapseFullBooks,
}

/// Formatting options used by [`Reference::format_with`] and [`Formatter`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FormatOptions {
    /// Book-name style used in formatted output.
    pub abbreviation_style: AbbreviationStyle,
    /// Range separator style used in formatted output.
    pub separator_style: SeparatorStyle,
    /// Rules for collapsing full chapter and full book ranges.
    pub range_style: RangeStyle,
    /// Versification system used when determining whether a range covers a full chapter or book.
    pub versification: Versification,
}

impl Default for FormatOptions {
    fn default() -> Self {
        Self {
            abbreviation_style: AbbreviationStyle::Full,
            separator_style: SeparatorStyle::EnDash,
            range_style: RangeStyle::CollapseFullBooks,
            versification: Versification::default(),
        }
    }
}

/// Options that control collection-level formatting behavior.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListFormatOptions {
    /// Sort references before formatting.
    pub sort: bool,
    /// Remove duplicate references before formatting.
    pub dedupe: bool,
    /// Canonical order preferences passed to [`Reference::sort_with`] when sorting.
    pub orderings: Vec<&'static str>,
    /// Separator used when joining multiple formatted references.
    pub list_separator: String,
}

impl Default for ListFormatOptions {
    fn default() -> Self {
        Self {
            sort: false,
            dedupe: false,
            orderings: vec!["talmud", "english"],
            list_separator: ", ".to_string(),
        }
    }
}

/// Formats individual references and reference collections.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Formatter {
    format: FormatOptions,
    list: ListFormatOptions,
}

impl Formatter {
    /// Create a formatter with reference-level options and default list options.
    pub fn new(format: FormatOptions) -> Self {
        Self {
            format,
            list: ListFormatOptions::default(),
        }
    }

    /// Replace collection-level formatting options.
    pub fn with_list_options(mut self, options: ListFormatOptions) -> Self {
        self.list = options;
        self
    }

    /// Format a single reference.
    pub fn format(&self, reference: &Reference) -> String {
        reference.format_with_options(&self.format)
    }

    /// Prepare and format multiple references, then join them with the configured list separator.
    pub fn format_many<'a>(&self, references: impl IntoIterator<Item = &'a Reference>) -> String {
        let refs: Vec<Reference> = references.into_iter().cloned().collect();
        self.prepare_many(&refs)
            .iter()
            .map(|reference| self.format(reference))
            .collect::<Vec<_>>()
            .join(&self.list.list_separator)
    }

    /// Apply collection-level transformations without rendering to strings.
    pub fn prepare_many(&self, references: &[Reference]) -> Vec<Reference> {
        let mut prepared = references.to_vec();

        if self.list.sort {
            let orderings = self.list.orderings.as_slice();
            prepared.sort_by_key(|reference| reference.sort_with(orderings));
        }

        if self.list.dedupe {
            let mut unique = Vec::with_capacity(prepared.len());
            for reference in prepared {
                if !unique.contains(&reference) {
                    unique.push(reference);
                }
            }
            prepared = unique;
        }

        prepared
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Reference {
    Single {
        book: &'static str,
        chapter: usize,
        verse: usize,
    },
    Range {
        book: &'static str,
        start_chapter: usize,
        start_verse: usize,

        end_book: &'static str,
        end_chapter: usize,
        end_verse: usize,
    },
}

impl Reference {
    /// Format this reference with the default display rules.
    pub fn format(&self) -> String {
        self.to_string()
    }

    /// Format this reference with custom formatting options.
    pub fn format_with(&self, options: &FormatOptions) -> String {
        Formatter::new(options.clone()).format(self)
    }

    /// Returns true if this is a Range that covers a full chapter (starts at verse 1 and ends at the last verse)
    fn is_full_chapter(&self) -> bool {
        self.is_full_chapter_versified(Versification::default())
    }

    fn is_full_chapter_versified(&self, versification: Versification) -> bool {
        if let Reference::Range {
            book,
            start_chapter,
            start_verse,
            end_book,
            end_chapter,
            end_verse,
        } = self
        {
            if book == end_book && start_chapter == end_chapter && *start_verse == 1 {
                // Check if end_verse is the last verse of the chapter
                if let Some(b) = get_book(book) {
                    if let Some(last_verse) =
                        b.verses_in_chapter_versified(*start_chapter as u8, versification)
                    {
                        return *end_verse == last_verse as usize;
                    }
                }
            }
        }
        false
    }

    /// Returns true if this is a Range that spans multiple whole chapters
    /// (e.g. "Genesis 1–2"): the start verse is the first verse of the start chapter
    /// and the end verse is the last verse of the end chapter, with at least
    /// two chapters covered.
    pub fn is_multi_chapter(&self) -> bool {
        self.is_multi_chapter_versified(Versification::default())
    }

    fn is_multi_chapter_versified(&self, versification: Versification) -> bool {
        if let Reference::Range {
            book,
            start_chapter,
            start_verse,
            end_book,
            end_chapter,
            end_verse,
        } = self
        {
            if book != end_book || *start_verse != 1 || start_chapter == end_chapter {
                return false;
            }
            if let Some(b) = get_book(book) {
                if let Some(last_verse) =
                    b.verses_in_chapter_versified(*end_chapter as u8, versification)
                {
                    return *end_verse == last_verse as usize;
                }
            }
        }
        false
    }

    /// Returns true if this is a Range that spans multiple whole books
    pub fn is_multi_book(&self) -> bool {
        self.is_multi_book_versified(Versification::default())
    }

    fn is_multi_book_versified(&self, versification: Versification) -> bool {
        if let Reference::Range {
            book,
            start_chapter,
            start_verse,
            end_book,
            end_chapter,
            end_verse,
        } = self
        {
            if book == end_book || *start_chapter != 1 || *start_verse != 1 {
                return false;
            }
            if let Some(b) = get_book(end_book) {
                let last_chapter = b.chapters_versified(versification) as usize;
                if *end_chapter != last_chapter {
                    return false;
                }
                if let Some(last_verse) =
                    b.verses_in_chapter_versified(last_chapter as u8, versification)
                {
                    return *end_verse == last_verse as usize;
                }
            }
        }
        false
    }

    /// Returns true if this is a Range that covers an entire book
    fn is_full_book(&self) -> bool {
        self.is_full_book_versified(Versification::default())
    }

    fn is_full_book_versified(&self, versification: Versification) -> bool {
        if let Reference::Range {
            book,
            start_chapter,
            start_verse,
            end_book,
            end_chapter,
            end_verse,
        } = self
        {
            if book == end_book && *start_chapter == 1 && *start_verse == 1 {
                if let Some(b) = get_book(book) {
                    let last_chapter = b.chapters_versified(versification) as usize;
                    if *end_chapter == last_chapter {
                        if let Some(last_verse) =
                            b.verses_in_chapter_versified(last_chapter as u8, versification)
                        {
                            return *end_verse == last_verse as usize;
                        }
                    }
                }
            }
        }
        false
    }

    fn format_with_options(&self, options: &FormatOptions) -> String {
        let separator = match options.separator_style {
            SeparatorStyle::EnDash => "–",
            SeparatorStyle::Hyphen => "-",
        };

        match self {
            Reference::Single {
                book,
                chapter,
                verse,
            } => {
                format!(
                    "{} {}:{}",
                    format_book_name(book, options.abbreviation_style),
                    chapter,
                    verse
                )
            }
            Reference::Range {
                book,
                start_chapter,
                start_verse,
                end_book,
                end_chapter,
                end_verse,
            } => {
                let start_book = format_book_name(book, options.abbreviation_style);
                let end_book_name = format_book_name(end_book, options.abbreviation_style);

                if book == end_book {
                    let collapse_books =
                        matches!(options.range_style, RangeStyle::CollapseFullBooks);
                    let collapse_chapters = matches!(
                        options.range_style,
                        RangeStyle::CollapseFullBooks | RangeStyle::CollapseFullChapters
                    );

                    if collapse_books && self.is_full_book_versified(options.versification) {
                        start_book
                    } else if collapse_chapters
                        && self.is_multi_chapter_versified(options.versification)
                    {
                        format!("{start_book} {start_chapter}{separator}{end_chapter}")
                    } else if start_chapter == end_chapter {
                        if collapse_chapters
                            && *start_verse == 1
                            && self.is_full_chapter_versified(options.versification)
                        {
                            format!("{start_book} {start_chapter}")
                        } else {
                            format!(
                                "{start_book} {start_chapter}:{start_verse}{separator}{end_verse}"
                            )
                        }
                    } else {
                        format!(
                            "{start_book} {start_chapter}:{start_verse}{separator}{end_chapter}:{end_verse}"
                        )
                    }
                } else if matches!(options.range_style, RangeStyle::CollapseFullBooks)
                    && self.is_multi_book_versified(options.versification)
                {
                    format!("{start_book}{separator}{end_book_name}")
                } else {
                    format!(
                        "{start_book} {start_chapter}:{start_verse} {separator} {end_book_name} {end_chapter}:{end_verse}"
                    )
                }
            }
        }
    }

    pub fn book(&self) -> String {
        match self {
            Reference::Single { book, .. } => book.to_string(),
            Reference::Range { book, .. } => book.to_string(),
        }
    }

    /// Computes a sort number from book order, chapter, and verse.
    /// Format: BBCCCVVV where BB = book order (2 digits), CCC = chapter (3 digits), VVV = verse (3 digits)
    /// Example: Genesis 1:1 = 01001001, Matthew 2:15 = 40002015
    fn compute_sort_value(book_order: isize, chapter: usize, verse: usize) -> u32 {
        let book_part = (book_order.max(0) as u32) * 1_000_000;
        let chapter_part = (chapter as u32) * 1_000;
        let verse_part = verse as u32;
        book_part + chapter_part + verse_part
    }

    /// Returns a sort number for this reference using the default English ordering.
    /// For Single references, returns (start, end) where start == end.
    /// For Range references, returns (start_sort, end_sort).
    /// Format: BBCCCVVV where BB = book order (2 digits), CCC = chapter (3 digits), VVV = verse (3 digits)
    /// Example: Genesis 1:1 = 1001001, Matthew 2:15 = 40002015
    pub fn sort(&self) -> (u32, u32) {
        self.sort_with(&["talmud", "english"])
    }

    /// Returns a sort number for this reference using the specified ordering preferences.
    /// The orderings are tried in order; if a book is not in the first ordering (value is -1),
    /// it falls back to the next ordering in the list.
    ///
    /// For Single references, returns (start, end) where start == end.
    /// For Range references, returns (start_sort, end_sort).
    ///
    /// Supported orderings: "english", "septuagint", "vulgate", "lenningrad", "bhs", "snaith", "talmud", "vaticanus", "sinaiticus", "alexandrinus"
    pub fn sort_with(&self, orderings: &[&str]) -> (u32, u32) {
        let get_book_order = |book_name: &str| -> isize {
            if let Some(b) = get_book(book_name) {
                for ordering in orderings {
                    if let Some(order) = b.order_by_name(ordering) {
                        if order >= 0 {
                            return order;
                        }
                    }
                }
                // Fallback to English if none of the orderings work
                b.order_english()
            } else {
                0
            }
        };

        match self {
            Reference::Single {
                book,
                chapter,
                verse,
            } => {
                let book_order = get_book_order(book);
                let sort_val = Self::compute_sort_value(book_order, *chapter, *verse);
                (sort_val, sort_val)
            }
            Reference::Range {
                book,
                start_chapter,
                start_verse,
                end_book,
                end_chapter,
                end_verse,
            } => {
                let start_book_order = get_book_order(book);
                let end_book_order = get_book_order(end_book);
                let start_sort =
                    Self::compute_sort_value(start_book_order, *start_chapter, *start_verse);
                let end_sort = Self::compute_sort_value(end_book_order, *end_chapter, *end_verse);
                (start_sort, end_sort)
            }
        }
    }
}

impl Display for Reference {
    fn fmt(&self, f: &mut FmtFormatter<'_>) -> std::fmt::Result {
        match self {
            Reference::Single {
                book,
                chapter,
                verse,
            } => {
                write!(f, "{} {}:{}", book, chapter, verse)
            }
            Reference::Range {
                book,
                start_chapter,
                start_verse,
                end_book,
                end_chapter,
                end_verse,
            } => {
                if book == end_book {
                    // Same book
                    if self.is_full_book() {
                        // Full book reference (e.g., "2 Timothy")
                        write!(f, "{}", book)
                    } else if self.is_multi_chapter() {
                        // Multiple full chapters (e.g., "Genesis 1–2")
                        write!(f, "{} {}–{}", book, start_chapter, end_chapter)
                    } else if start_chapter == end_chapter {
                        // Same chapter
                        if *start_verse == 1 && self.is_full_chapter() {
                            // Full chapter reference (e.g., "Genesis 1")
                            write!(f, "{} {}", book, start_chapter)
                        } else {
                            // Partial chapter range (e.g., "Genesis 1:1–5")
                            write!(
                                f,
                                "{} {}:{}–{}",
                                book, start_chapter, start_verse, end_verse
                            )
                        }
                    } else {
                        // Multi-chapter range
                        write!(
                            f,
                            "{} {}:{}–{}:{}",
                            book, start_chapter, start_verse, end_chapter, end_verse
                        )
                    }
                } else if self.is_multi_book() {
                    // Multiple full books (e.g., "Genesis–Exodus")
                    write!(f, "{}–{}", book, end_book)
                } else {
                    write!(
                        f,
                        "{} {}:{} – {} {}:{}",
                        book, start_chapter, start_verse, end_book, end_chapter, end_verse
                    )
                }
            }
        }
    }
}

fn format_book_name(book: &'static str, style: AbbreviationStyle) -> String {
    match style {
        AbbreviationStyle::Full => book.to_string(),
        AbbreviationStyle::Standard => get_book(book)
            .and_then(|book| book.abbreviations.first().copied())
            .unwrap_or(book)
            .to_string(),
        AbbreviationStyle::Osis => book.replace(' ', ""),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn sort_single_reference_genesis() {
        let reference = Reference::Single {
            book: "Genesis",
            chapter: 1,
            verse: 1,
        };
        assert_eq!(reference.sort(), (1_001_001, 1_001_001));
    }

    #[test]
    fn sort_single_reference_matthew() {
        let reference = Reference::Single {
            book: "Matthew",
            chapter: 2,
            verse: 15,
        };
        assert_eq!(reference.sort(), (40_002_015, 40_002_015));
    }

    #[test]
    fn sort_range_reference_same_book() {
        let reference = Reference::Range {
            book: "Genesis",
            start_chapter: 1,
            start_verse: 1,
            end_book: "Genesis",
            end_chapter: 1,
            end_verse: 5,
        };
        assert_eq!(reference.sort(), (1_001_001, 1_001_005));
    }

    #[test]
    fn sort_range_reference_cross_book() {
        let reference = Reference::Range {
            book: "Genesis",
            start_chapter: 50,
            start_verse: 26,
            end_book: "Exodus",
            end_chapter: 1,
            end_verse: 1,
        };
        assert_eq!(reference.sort(), (1_050_026, 2_001_001));
    }

    #[test]
    fn is_multi_chapter_true_for_full_chapter_span() {
        // Genesis 1:1 – 2:25 covers chapters 1 and 2 in full
        let reference = Reference::Range {
            book: "Genesis",
            start_chapter: 1,
            start_verse: 1,
            end_book: "Genesis",
            end_chapter: 2,
            end_verse: 25,
        };
        assert!(reference.is_multi_chapter());
    }

    #[test]
    fn is_multi_chapter_false_when_start_not_verse_one() {
        // Genesis 1:2 – 2:25: start verse is not 1
        let reference = Reference::Range {
            book: "Genesis",
            start_chapter: 1,
            start_verse: 2,
            end_book: "Genesis",
            end_chapter: 2,
            end_verse: 25,
        };
        assert!(!reference.is_multi_chapter());
    }

    #[test]
    fn is_multi_chapter_false_when_end_not_last_verse() {
        // Genesis 1:1 – 2:24: end verse isn't last verse of chapter 2 (25)
        let reference = Reference::Range {
            book: "Genesis",
            start_chapter: 1,
            start_verse: 1,
            end_book: "Genesis",
            end_chapter: 2,
            end_verse: 24,
        };
        assert!(!reference.is_multi_chapter());
    }

    #[test]
    fn is_multi_chapter_false_for_single_chapter_range() {
        // Genesis 1:1 – 1:31 is a single full chapter, not multi-chapter
        let reference = Reference::Range {
            book: "Genesis",
            start_chapter: 1,
            start_verse: 1,
            end_book: "Genesis",
            end_chapter: 1,
            end_verse: 31,
        };
        assert!(!reference.is_multi_chapter());
    }

    #[test]
    fn is_multi_chapter_false_for_single_reference() {
        let reference = Reference::Single {
            book: "Genesis",
            chapter: 1,
            verse: 1,
        };
        assert!(!reference.is_multi_chapter());
    }

    #[test]
    fn is_multi_chapter_false_when_books_differ() {
        let reference = Reference::Range {
            book: "Genesis",
            start_chapter: 1,
            start_verse: 1,
            end_book: "Exodus",
            end_chapter: 1,
            end_verse: 22,
        };
        assert!(!reference.is_multi_chapter());
    }

    #[test]
    fn is_multi_book_true_for_full_book_span() {
        // Genesis 1:1 – Exodus 40:38 covers Genesis and Exodus in full
        let reference = Reference::Range {
            book: "Genesis",
            start_chapter: 1,
            start_verse: 1,
            end_book: "Exodus",
            end_chapter: 40,
            end_verse: 38,
        };
        assert!(reference.is_multi_book());
    }

    #[test]
    fn is_multi_book_false_when_start_not_one_one() {
        // Genesis 1:2 – Exodus 40:38: doesn't start at 1:1
        let reference = Reference::Range {
            book: "Genesis",
            start_chapter: 1,
            start_verse: 2,
            end_book: "Exodus",
            end_chapter: 40,
            end_verse: 38,
        };
        assert!(!reference.is_multi_book());
    }

    #[test]
    fn is_multi_book_false_when_end_not_last_verse_of_book() {
        // Genesis 1:1 – Exodus 40:37: end verse isn't last verse of Exodus 40 (38)
        let reference = Reference::Range {
            book: "Genesis",
            start_chapter: 1,
            start_verse: 1,
            end_book: "Exodus",
            end_chapter: 40,
            end_verse: 37,
        };
        assert!(!reference.is_multi_book());
    }

    #[test]
    fn is_multi_book_false_when_end_not_last_chapter_of_book() {
        // Genesis 1:1 – Exodus 1:22: doesn't reach Exodus's last chapter (40)
        let reference = Reference::Range {
            book: "Genesis",
            start_chapter: 1,
            start_verse: 1,
            end_book: "Exodus",
            end_chapter: 1,
            end_verse: 22,
        };
        assert!(!reference.is_multi_book());
    }

    #[test]
    fn is_multi_book_false_for_same_book_range() {
        // Genesis 1:1 – Genesis 50:26 is the entire book of Genesis, but only one book
        let reference = Reference::Range {
            book: "Genesis",
            start_chapter: 1,
            start_verse: 1,
            end_book: "Genesis",
            end_chapter: 50,
            end_verse: 26,
        };
        assert!(!reference.is_multi_book());
    }

    #[test]
    fn is_multi_book_false_for_single_reference() {
        let reference = Reference::Single {
            book: "Genesis",
            chapter: 1,
            verse: 1,
        };
        assert!(!reference.is_multi_book());
    }

    #[test]
    fn display_multi_chapter_range() {
        let reference = Reference::Range {
            book: "Genesis",
            start_chapter: 1,
            start_verse: 1,
            end_book: "Genesis",
            end_chapter: 2,
            end_verse: 25,
        };
        assert_eq!(reference.to_string(), "Genesis 1–2");
    }

    #[test]
    fn display_multi_book_range() {
        let reference = Reference::Range {
            book: "Genesis",
            start_chapter: 1,
            start_verse: 1,
            end_book: "Exodus",
            end_chapter: 40,
            end_verse: 38,
        };
        assert_eq!(reference.to_string(), "Genesis–Exodus");
    }

    #[test]
    fn format_with_hyphen_separator() {
        let reference = Reference::Range {
            book: "John",
            start_chapter: 3,
            start_verse: 16,
            end_book: "John",
            end_chapter: 3,
            end_verse: 18,
        };
        let options = FormatOptions {
            separator_style: SeparatorStyle::Hyphen,
            ..Default::default()
        };

        assert_eq!(reference.format_with(&options), "John 3:16-18");
    }

    #[test]
    fn formatter_can_sort_and_dedupe_many_references() {
        let references = vec![
            Reference::Single {
                book: "John",
                chapter: 3,
                verse: 16,
            },
            Reference::Single {
                book: "Genesis",
                chapter: 1,
                verse: 1,
            },
            Reference::Single {
                book: "John",
                chapter: 3,
                verse: 16,
            },
        ];
        let formatter = Formatter::default().with_list_options(ListFormatOptions {
            sort: true,
            dedupe: true,
            ..Default::default()
        });

        assert_eq!(formatter.format_many(&references), "Genesis 1:1, John 3:16");
    }

    #[test]
    fn sort_with_fallback_ordering() {
        // Matthew has english=40, but septuagint=-1, so it should fallback
        let reference = Reference::Single {
            book: "Matthew",
            chapter: 1,
            verse: 1,
        };
        // First try septuagint (which is -1), then fallback to english
        let result = reference.sort_with(&["septuagint", "english"]);
        assert_eq!(result, (40_001_001, 40_001_001));
    }
}
