package net.sergeych.toread

import kotlin.test.Test
import kotlin.test.assertEquals
import net.sergeych.toread.fb2.Fb2Block
import net.sergeych.toread.fb2.Fb2Book
import net.sergeych.toread.fb2.Fb2Epigraph
import net.sergeych.toread.fb2.Fb2EpigraphBlock
import net.sergeych.toread.fb2.Fb2ImageRef
import net.sergeych.toread.fb2.Fb2Poem
import net.sergeych.toread.fb2.Fb2PoemBlock
import net.sergeych.toread.fb2.Fb2Section
import net.sergeych.toread.fb2.Fb2Stanza
import net.sergeych.toread.fb2.Fb2Text
import net.sergeych.toread.fb2.Fb2TextSpan

class ReadAloudContentPlanTest {
    @Test
    fun starBreakAddsPauseBeforeNextSpokenSentence() {
        val plan = buildReaderContentPlan(
            Fb2Book(
                title = "Book",
                sections = listOf(
                    Fb2Section(
                        blocks = listOf(
                            paragraph("Before."),
                            paragraph("* * *"),
                            paragraph("After."),
                        ),
                    ),
                ),
            ),
        )

        assertEquals(listOf("Before.", "After."), plan.sentences.map { it.text })
        assertEquals(0L, plan.sentences[0].pauseBeforeMillis)
        assertEquals(1_200L, plan.sentences[1].pauseBeforeMillis)
    }

    @Test
    fun ellipsisIsCollapsedForSpeechAndAddsShortPause() {
        val plan = buildReaderContentPlan(
            Fb2Book(
                title = "Book",
                sections = listOf(
                    Fb2Section(blocks = listOf(paragraph("Wait... Go.. Stop… Done."))),
                ),
            ),
        )

        assertEquals(
            listOf("Wait...", "Go..", "Stop…", "Done."),
            plan.sentences.map { it.text },
        )
        assertEquals(
            listOf("Wait.", "Go.", "Stop.", "Done."),
            plan.sentences.map { it.spokenText },
        )
        assertEquals(listOf(350L, 350L, 350L, 0L), plan.sentences.map { it.pauseAfterMillis })
    }

    @Test
    fun dotsAfterQuestionOrExclamationAreRemovedForSpeech() {
        val plan = buildReaderContentPlan(
            Fb2Book(
                title = "Book",
                sections = listOf(
                    Fb2Section(blocks = listOf(paragraph("What?.. No!.. Done."))),
                ),
            ),
        )

        assertEquals(
            listOf("What?..", "No!..", "Done."),
            plan.sentences.map { it.text },
        )
        assertEquals(
            listOf("What?", "No!", "Done."),
            plan.sentences.map { it.spokenText },
        )
    }

    @Test
    fun hardcodedReadAloudReplacementCanMarkStress() {
        val plan = buildReaderContentPlan(
            Fb2Book(
                title = "Book",
                sections = listOf(
                    Fb2Section(blocks = listOf(paragraph("Господа, идите."))),
                ),
            ),
        )

        assertEquals("Господа, идите.", plan.sentences.single().text)
        assertEquals("Господа́, идите.", plan.sentences.single().spokenText)
    }

    @Test
    fun hardcodedReadAloudReplacementsAreCaseSensitive() {
        val plan = buildReaderContentPlan(
            Fb2Book(
                title = "Book",
                sections = listOf(
                    Fb2Section(blocks = listOf(paragraph("ГОСПОДА, идите."))),
                ),
            ),
        )

        assertEquals("ГОСПОДА, идите.", plan.sentences.single().spokenText)
    }

    @Test
    fun stressMarkersSupportRussianAndEnglishVowels() {
        val plan = buildReaderContentPlan(
            Fb2Book(
                title = "Book",
                sections = listOf(
                    Fb2Section(
                        blocks = listOf(
                            paragraph(
                                "/а/е/ё/и/о/у/ы/э/ю/я " +
                                    "/А/Е/Ё/И/О/У/Ы/Э/Ю/Я " +
                                    "'a'e'i'o'u'y " +
                                    "'A'E'I'O'U'Y.",
                            ),
                        ),
                    ),
                ),
            ),
        )

        assertEquals(
            "а́е́ё́и́о́у́ы́э́ю́я́ " +
                "А́Е́Ё́И́О́У́Ы́Э́Ю́Я́ " +
                "áéíóúý " +
                "ÁÉÍÓÚÝ.",
            plan.sentences.single().spokenText,
        )
    }

    @Test
    fun stressMarkersIgnoreNonStressableLetters() {
        val plan = buildReaderContentPlan(
            Fb2Book(
                title = "Book",
                sections = listOf(
                    Fb2Section(blocks = listOf(paragraph("/б 'z /1."))),
                ),
            ),
        )

        assertEquals("/б 'z /1.", plan.sentences.single().spokenText)
    }

    @Test
    fun poemVersesAreIncludedInReadAloudPlan() {
        val plan = buildReaderContentPlan(
            Fb2Book(
                title = "Book",
                sections = listOf(
                    Fb2Section(
                        blocks = listOf(
                            Fb2Block.Poem(
                                Fb2Poem(
                                    blocks = listOf(
                                        Fb2PoemBlock.Stanza(
                                            Fb2Stanza(
                                                verses = listOf(
                                                    text("Line one."),
                                                    text("Line two."),
                                                ),
                                            ),
                                        ),
                                    ),
                                ),
                            ),
                        ),
                    ),
                ),
            ),
        )

        assertEquals(listOf("Line one.", "Line two."), plan.sentences.map { it.text })
        assertEquals(1, plan.elements.filterIsInstance<ReaderElement.Poem>().size)
    }

    @Test
    fun epigraphsAreIncludedInReaderPlan() {
        val plan = buildReaderContentPlan(
            Fb2Book(
                title = "Book",
                bodyEpigraphs = listOf(
                    Fb2Epigraph(
                        blocks = listOf(Fb2EpigraphBlock.Paragraph(text("Body quote."))),
                        textAuthors = listOf(text("Body author.")),
                    ),
                ),
                sections = listOf(
                    Fb2Section(
                        blocks = listOf(
                            Fb2Block.Epigraph(
                                Fb2Epigraph(
                                    blocks = listOf(Fb2EpigraphBlock.Paragraph(text("Section quote."))),
                                    textAuthors = listOf(text("Section author.")),
                                ),
                            ),
                        ),
                    ),
                ),
            ),
        )

        assertEquals(2, plan.elements.filterIsInstance<ReaderElement.Epigraph>().size)
        assertEquals(
            listOf("Body quote.", "Body author.", "Section quote.", "Section author."),
            plan.sentences.map { it.text },
        )
    }

    @Test
    fun wrapperOnlyTopSectionDoesNotIndentChildParagraphs() {
        val plan = buildReaderContentPlan(
            Fb2Book(
                title = "Book",
                sections = listOf(
                    Fb2Section(
                        title = "Wrapper title",
                        sections = listOf(
                            Fb2Section(
                                title = "Chapter",
                                blocks = listOf(paragraph("Chapter text.")),
                            ),
                        ),
                    ),
                ),
            ),
        )

        assertEquals(0, plan.elements.filterIsInstance<ReaderElement.Paragraph>().single().depth)
        assertEquals(
            listOf(0, 0),
            plan.elements.filterIsInstance<ReaderElement.SectionTitle>().map { it.depth },
        )
    }

    @Test
    fun titleOnlyPartSectionDoesNotIndentChaptersAfterFrontMatter() {
        val plan = buildReaderContentPlan(
            Fb2Book(
                title = "Book",
                sections = listOf(
                    Fb2Section(
                        title = "Copyright",
                        blocks = listOf(paragraph("Copyright text.")),
                    ),
                    Fb2Section(
                        title = "Part one",
                        sections = listOf(
                            Fb2Section(
                                title = "Chapter one",
                                blocks = listOf(paragraph("Chapter text.")),
                            ),
                        ),
                    ),
                ),
            ),
        )

        assertEquals(
            listOf(0, 0),
            plan.elements.filterIsInstance<ReaderElement.Paragraph>().map { it.depth },
        )
        assertEquals(
            listOf(0, 0, 0),
            plan.elements.filterIsInstance<ReaderElement.SectionTitle>().map { it.depth },
        )
    }

    @Test
    fun imageOnlyPartStructureDoesNotIndentWholeBook() {
        val plan = buildReaderContentPlan(
            Fb2Book(
                title = "Book",
                sections = listOf(
                    Fb2Section(
                        title = "Wrapper title",
                        sections = listOf(
                            Fb2Section(
                                images = listOf(Fb2ImageRef("#front.jpg")),
                            ),
                            Fb2Section(
                                title = "Part one",
                                blocks = listOf(Fb2Block.Image(Fb2ImageRef("#part.png"))),
                                sections = listOf(
                                    Fb2Section(
                                        title = "Chapter one",
                                        blocks = listOf(paragraph("Chapter text.")),
                                    ),
                                ),
                            ),
                        ),
                    ),
                ),
            ),
        )

        assertEquals(0, plan.elements.filterIsInstance<ReaderElement.Paragraph>().single().depth)
        assertEquals(
            listOf(0, 0, 0),
            plan.elements.filterIsInstance<ReaderElement.SectionTitle>().map { it.depth },
        )
        assertEquals(listOf(0, 0, 0, 0), plan.tocEntries.map { it.depth })
    }

    @Test
    fun realNestedSectionsKeepRelativeIndent() {
        val plan = buildReaderContentPlan(
            Fb2Book(
                title = "Book",
                sections = listOf(
                    Fb2Section(
                        title = "Part",
                        blocks = listOf(paragraph("Part text.")),
                        sections = listOf(
                            Fb2Section(
                                title = "Nested chapter",
                                blocks = listOf(paragraph("Nested text.")),
                            ),
                        ),
                    ),
                ),
            ),
        )

        assertEquals(
            listOf(0, 1),
            plan.elements.filterIsInstance<ReaderElement.Paragraph>().map { it.depth },
        )
        assertEquals(
            listOf(0, 1),
            plan.elements.filterIsInstance<ReaderElement.SectionTitle>().map { it.depth },
        )
    }

    @Test
    fun contentPlanIncludesClickableTableOfContentsEntries() {
        val plan = buildReaderContentPlan(
            Fb2Book(
                title = "Book",
                sections = listOf(
                    Fb2Section(
                        title = "Part",
                        sections = listOf(
                            Fb2Section(
                                title = "Chapter",
                                blocks = listOf(paragraph("Chapter text.")),
                            ),
                        ),
                    ),
                    Fb2Section(blocks = listOf(paragraph("Untitled text."))),
                ),
            ),
        )

        assertEquals(listOf("Part", "Chapter", "Section 2"), plan.tocEntries.map { it.title })
        assertEquals(listOf(0, 0, 0), plan.tocEntries.map { it.depth })
        assertEquals(
            plan.elements.filterIsInstance<ReaderElement.SectionTitle>().first(),
            plan.elements[plan.tocEntries.first().itemIndex],
        )
    }

    private fun paragraph(text: String): Fb2Block.Paragraph =
        Fb2Block.Paragraph(text(text))

    private fun text(text: String): Fb2Text =
        Fb2Text(listOf(Fb2TextSpan(text)))
}
