"""
novelWriter – Manuscript Build Settings Dialog Tester
=====================================================

This file is a part of novelWriter
Copyright (C) 2023 Veronica Berglyd Olsen and novelWriter contributors

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations

import pytest

from PyQt6.QtCore import pyqtSlot
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QFontDialog

from novelwriter import SHARED
from novelwriter.common import describeFont
from novelwriter.constants import nwHeadFmt
from novelwriter.core.buildsettings import BuildSettings, FilterMode
from novelwriter.tools.manussettings import (
    GuiBuildSettings, _FilterTab, _FormattingTab, _HeadingsTab
)
from novelwriter.types import QtDialogApply, QtDialogClose, QtDialogSave

from tests.tools import C, buildTestProject


@pytest.mark.gui
def testToolBuildSettings_Init(qtbot, nwGUI, projPath, mockRnd):
    """Test the initialisation of the GuiBuildSettings dialog."""
    buildTestProject(nwGUI, projPath)
    nwGUI.openProject(projPath)
    build = BuildSettings()

    # Create the dialog and populate it
    bSettings = GuiBuildSettings(nwGUI, build)
    bSettings.show()
    bSettings.loadContent()

    # Flip through pages
    button = bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 1)
    assert button is not None
    button.click()
    assert isinstance(bSettings.toolStack.currentWidget(), _FormattingTab)

    button = bSettings.sidebar._group.button(bSettings.OPT_HEADINGS)
    assert button is not None
    button.click()
    assert isinstance(bSettings.toolStack.currentWidget(), _HeadingsTab)

    button = bSettings.sidebar._group.button(bSettings.OPT_FILTERS)
    assert button is not None
    button.click()
    assert isinstance(bSettings.toolStack.currentWidget(), _FilterTab)

    # Check dialog buttons
    triggered = False

    @pyqtSlot(BuildSettings)
    def _testNewSettingsReady(new: BuildSettings):
        nonlocal triggered
        assert new.buildID == build.buildID
        triggered = True

    # Capture Apply button
    with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000):
        bSettings.newSettingsReady.connect(_testNewSettingsReady)
        button = bSettings.buttonBox.button(QtDialogApply)
        assert button is not None
        bSettings._dialogButtonClicked(button)

    assert triggered

    # Capture Save button
    triggered = False

    with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000):
        bSettings.newSettingsReady.connect(_testNewSettingsReady)
        button = bSettings.buttonBox.button(QtDialogSave)
        assert button is not None
        bSettings._dialogButtonClicked(button)

    assert triggered

    # Close manually
    # This pops the ask to save dialog, which is automatically handled
    triggered = False
    build._changed = True
    bSettings.show()

    with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000):
        bSettings.newSettingsReady.connect(_testNewSettingsReady)
        bSettings._build._changed = True
        bSettings.close()

    assert triggered

    # Finish
    button = bSettings.buttonBox.button(QtDialogClose)
    assert button is not None
    bSettings._dialogButtonClicked(button)
    # qtbot.stop()


@pytest.mark.gui
def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd):
    """Test the Filter Tab of the GuiBuildSettings dialog."""
    buildTestProject(nwGUI, projPath)
    nwGUI.openProject(projPath)
    build = BuildSettings()

    switchMap = {
        "incNovel": 1,
        "incNotes": 2,
        "incInactive": 3,
        "novelRoot": 6,
        "plotRoot": 7,
        "charRoot": 8,
        "worldRoot": 9,
    }

    hPlotDoc = SHARED.project.newFile("Main Plot", C.hPlotRoot)
    hCharDoc = SHARED.project.newFile("Jane Doe", C.hCharRoot)
    SHARED.project.tree[hPlotDoc].setActive(False)  # type: ignore

    # Create the dialog and populate it
    bSettings = GuiBuildSettings(nwGUI, build)
    bSettings.show()
    bSettings.loadContent()

    sBuild = bSettings._build
    assert sBuild.buildID == build.buildID

    filterTab = bSettings.optTabSelect
    button = bSettings.sidebar._group.button(bSettings.OPT_FILTERS)
    assert button is not None
    button.click()
    assert bSettings.toolStack.currentWidget() is filterTab

    # Check content
    assert filterTab.optTree.topLevelItemCount() == 4  # The 4 root folders
    assert filterTab.filterOpt._index == 10  # 2 headers, 1 sep, 3 opt and 4 roots

    # Un-toggle note folders
    filterTab.filterOpt._widgets[switchMap["worldRoot"]].setChecked(False)  # World Root
    assert filterTab.optTree.topLevelItemCount() == 3
    assert C.hWorldRoot in sBuild._skipRoot

    filterTab.filterOpt._widgets[switchMap["charRoot"]].setChecked(False)  # Char Root
    assert filterTab.optTree.topLevelItemCount() == 2
    assert C.hCharRoot in sBuild._skipRoot

    filterTab.filterOpt._widgets[switchMap["plotRoot"]].setChecked(False)  # Plot Root
    assert filterTab.optTree.topLevelItemCount() == 1
    assert C.hPlotRoot in sBuild._skipRoot

    # Reset Plot and Char
    filterTab.filterOpt._widgets[switchMap["plotRoot"]].setChecked(True)
    filterTab.filterOpt._widgets[switchMap["charRoot"]].setChecked(True)
    assert filterTab.optTree.topLevelItemCount() == 3

    # Switch off novel docs
    filterTab.filterOpt._widgets[switchMap["incNovel"]].setChecked(False)
    assert sBuild.buildItemFilter(SHARED.project) == {
        C.hNovelRoot:  (False, FilterMode.SKIPPED),
        C.hTitlePage:  (False, FilterMode.FILTERED),
        C.hChapterDir: (False, FilterMode.SKIPPED),
        C.hChapterDoc: (False, FilterMode.FILTERED),
        C.hSceneDoc:   (False, FilterMode.FILTERED),
        C.hPlotRoot:   (False, FilterMode.SKIPPED),
        hPlotDoc:      (False, FilterMode.FILTERED),
        C.hCharRoot:   (False, FilterMode.SKIPPED),
        hCharDoc:      (False, FilterMode.FILTERED),
        C.hWorldRoot:  (False, FilterMode.SKIPPED),
        C.hTrashRoot:  (False, FilterMode.SKIPPED),
    }

    # Switch on note docs
    filterTab.filterOpt._widgets[switchMap["incNotes"]].setChecked(True)
    assert sBuild.buildItemFilter(SHARED.project) == {
        C.hNovelRoot:  (False, FilterMode.SKIPPED),
        C.hTitlePage:  (False, FilterMode.FILTERED),
        C.hChapterDir: (False, FilterMode.SKIPPED),
        C.hChapterDoc: (False, FilterMode.FILTERED),
        C.hSceneDoc:   (False, FilterMode.FILTERED),
        C.hPlotRoot:   (False, FilterMode.SKIPPED),
        hPlotDoc:      (False, FilterMode.FILTERED),  # Set to inactive
        C.hCharRoot:   (False, FilterMode.SKIPPED),
        hCharDoc:      (True,  FilterMode.FILTERED),  # Now enabled
        C.hWorldRoot:  (False, FilterMode.SKIPPED),
        C.hTrashRoot:  (False, FilterMode.SKIPPED),
    }

    # Switch on inactive docs
    filterTab.filterOpt._widgets[switchMap["incInactive"]].setChecked(True)
    assert sBuild.buildItemFilter(SHARED.project) == {
        C.hNovelRoot:  (False, FilterMode.SKIPPED),
        C.hTitlePage:  (False, FilterMode.FILTERED),
        C.hChapterDir: (False, FilterMode.SKIPPED),
        C.hChapterDoc: (False, FilterMode.FILTERED),
        C.hSceneDoc:   (False, FilterMode.FILTERED),
        C.hPlotRoot:   (False, FilterMode.SKIPPED),
        hPlotDoc:      (True,  FilterMode.FILTERED),  # Now enabled
        C.hCharRoot:   (False, FilterMode.SKIPPED),
        hCharDoc:      (True,  FilterMode.FILTERED),
        C.hWorldRoot:  (False, FilterMode.SKIPPED),
        C.hTrashRoot:  (False, FilterMode.SKIPPED),
    }

    # Set chapter and scene docs to included
    filterTab._treeMap[C.hChapterDoc].setSelected(True)
    filterTab._treeMap[C.hSceneDoc].setSelected(True)
    filterTab.includedButton.click()
    assert sBuild.buildItemFilter(SHARED.project) == {
        C.hNovelRoot:  (False, FilterMode.SKIPPED),
        C.hTitlePage:  (False, FilterMode.FILTERED),
        C.hChapterDir: (False, FilterMode.SKIPPED),
        C.hChapterDoc: (True,  FilterMode.INCLUDED),  # Now included
        C.hSceneDoc:   (True,  FilterMode.INCLUDED),  # Now included
        C.hPlotRoot:   (False, FilterMode.SKIPPED),
        hPlotDoc:      (True,  FilterMode.FILTERED),
        C.hCharRoot:   (False, FilterMode.SKIPPED),
        hCharDoc:      (True,  FilterMode.FILTERED),
        C.hWorldRoot:  (False, FilterMode.SKIPPED),
        C.hTrashRoot:  (False, FilterMode.SKIPPED),
    }

    # Set char and plot docs to excluded
    filterTab.optTree.clearSelection()
    filterTab._treeMap[hPlotDoc].setSelected(True)  # type: ignore
    filterTab._treeMap[hCharDoc].setSelected(True)  # type: ignore
    filterTab.excludedButton.click()
    assert sBuild.buildItemFilter(SHARED.project) == {
        C.hNovelRoot:  (False, FilterMode.SKIPPED),
        C.hTitlePage:  (False, FilterMode.FILTERED),
        C.hChapterDir: (False, FilterMode.SKIPPED),
        C.hChapterDoc: (True,  FilterMode.INCLUDED),
        C.hSceneDoc:   (True,  FilterMode.INCLUDED),
        C.hPlotRoot:   (False, FilterMode.SKIPPED),
        hPlotDoc:      (False, FilterMode.EXCLUDED),  # Now excluded
        C.hCharRoot:   (False, FilterMode.SKIPPED),
        hCharDoc:      (False, FilterMode.EXCLUDED),  # Now excluded
        C.hWorldRoot:  (False, FilterMode.SKIPPED),
        C.hTrashRoot:  (False, FilterMode.SKIPPED),
    }

    # Switch on novel docs
    filterTab.filterOpt._widgets[switchMap["incNovel"]].setChecked(True)
    assert sBuild.buildItemFilter(SHARED.project) == {
        C.hNovelRoot:  (False, FilterMode.SKIPPED),
        C.hTitlePage:  (True,  FilterMode.FILTERED),  # Now enabled
        C.hChapterDir: (False, FilterMode.SKIPPED),
        C.hChapterDoc: (True,  FilterMode.INCLUDED),
        C.hSceneDoc:   (True,  FilterMode.INCLUDED),
        C.hPlotRoot:   (False, FilterMode.SKIPPED),
        hPlotDoc:      (False, FilterMode.EXCLUDED),
        C.hCharRoot:   (False, FilterMode.SKIPPED),
        hCharDoc:      (False, FilterMode.EXCLUDED),
        C.hWorldRoot:  (False, FilterMode.SKIPPED),
        C.hTrashRoot:  (False, FilterMode.SKIPPED),
    }

    # Selecting only novel root should iterate through all children
    filterTab.optTree.clearSelection()
    filterTab._treeMap[C.hNovelRoot].setSelected(True)
    filterTab.resetButton.click()
    assert sBuild.buildItemFilter(SHARED.project) == {
        C.hNovelRoot:  (False, FilterMode.SKIPPED),
        C.hTitlePage:  (True,  FilterMode.FILTERED),
        C.hChapterDir: (False, FilterMode.SKIPPED),
        C.hChapterDoc: (True,  FilterMode.FILTERED),
        C.hSceneDoc:   (True,  FilterMode.FILTERED),
        C.hPlotRoot:   (False, FilterMode.SKIPPED),
        hPlotDoc:      (False, FilterMode.EXCLUDED),
        C.hCharRoot:   (False, FilterMode.SKIPPED),
        hCharDoc:      (False, FilterMode.EXCLUDED),
        C.hWorldRoot:  (False, FilterMode.SKIPPED),
        C.hTrashRoot:  (False, FilterMode.SKIPPED),
    }

    # Set everything back to filtered
    filterTab.optTree.clearSelection()
    filterTab._treeMap[C.hNovelRoot].setSelected(True)
    filterTab._treeMap[C.hSceneDoc].setSelected(True)
    filterTab._treeMap[hPlotDoc].setSelected(True)  # type: ignore
    filterTab._treeMap[hCharDoc].setSelected(True)  # type: ignore
    filterTab.resetButton.click()
    assert sBuild.buildItemFilter(SHARED.project) == {
        C.hNovelRoot:  (False, FilterMode.SKIPPED),
        C.hTitlePage:  (True,  FilterMode.FILTERED),
        C.hChapterDir: (False, FilterMode.SKIPPED),
        C.hChapterDoc: (True,  FilterMode.FILTERED),
        C.hSceneDoc:   (True,  FilterMode.FILTERED),
        C.hPlotRoot:   (False, FilterMode.SKIPPED),
        hPlotDoc:      (True,  FilterMode.FILTERED),
        C.hCharRoot:   (False, FilterMode.SKIPPED),
        hCharDoc:      (True,  FilterMode.FILTERED),
        C.hWorldRoot:  (False, FilterMode.SKIPPED),
        C.hTrashRoot:  (False, FilterMode.SKIPPED),
    }

    # Check handling of invalid project items
    assert list(filterTab._treeMap.keys()) == [
        C.hNovelRoot, C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
        C.hPlotRoot, hPlotDoc, C.hCharRoot, hCharDoc,
    ]
    SHARED.project.tree[hCharDoc].setRoot(None)  # type: ignore
    SHARED.project.tree[hPlotDoc].setParent(None)  # type: ignore
    filterTab._populateTree()
    assert list(filterTab._treeMap.keys()) == [
        C.hNovelRoot, C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
        C.hPlotRoot, C.hCharRoot
    ]

    # Finish
    button = bSettings.buttonBox.button(QtDialogClose)
    assert button is not None
    bSettings._dialogButtonClicked(button)
    # qtbot.stop()


@pytest.mark.gui
def testToolBuildSettings_Headings(qtbot, nwGUI):
    """Test the Headings Tab of the GuiBuildSettings dialog."""
    build = BuildSettings()

    ttTitle = f"Part: {nwHeadFmt.TITLE}"
    chTitle = f"Chapter: {nwHeadFmt.TITLE}"
    unTitle = f"Interlude: {nwHeadFmt.TITLE}"
    scTitle = f"Scene: {nwHeadFmt.TITLE}"
    shTitle = f"Hard Scene: {nwHeadFmt.TITLE}"
    sxTitle = f"Section: {nwHeadFmt.TITLE}"

    build.setValue("headings.fmtPart", ttTitle)
    build.setValue("headings.fmtChapter", chTitle)
    build.setValue("headings.fmtUnnumbered", unTitle)
    build.setValue("headings.fmtScene", scTitle)
    build.setValue("headings.fmtAltScene", shTitle)
    build.setValue("headings.fmtSection", sxTitle)
    build.setValue("headings.hideScene", False)
    build.setValue("headings.hideSection", False)

    # Create the dialog and populate it
    bSettings = GuiBuildSettings(nwGUI, build)
    bSettings.show()
    bSettings.loadContent()

    headTab = bSettings.optTabHeadings
    button = bSettings.sidebar._group.button(bSettings.OPT_HEADINGS)
    assert button is not None
    button.click()
    assert bSettings.toolStack.currentWidget() is headTab

    # Check initial values
    assert headTab.fmtPart.text() == ttTitle
    assert headTab.fmtChapter.text() == chTitle
    assert headTab.fmtUnnumbered.text() == unTitle
    assert headTab.fmtScene.text() == scTitle
    assert headTab.fmtAScene.text() == shTitle
    assert headTab.fmtSection.text() == sxTitle
    assert headTab.swtScene.isChecked() is False
    assert headTab.swtSection.isChecked() is False

    # Edit in Turn
    # ============

    # Nothing to apply
    assert headTab._editing == 0
    assert headTab.editTextBox.isEnabled() is False
    headTab.btnApply.click()
    assert headTab.editTextBox.isEnabled() is False

    # Title
    headTab.btnPart.click()
    assert headTab._editing == headTab.EDIT_TITLE
    assert headTab.editTextBox.isEnabled() is True
    assert headTab.editTextBox.toPlainText() == ttTitle
    headTab.btnApply.click()
    assert headTab.editTextBox.isEnabled() is False

    # Chapter
    headTab.btnChapter.click()
    assert headTab._editing == headTab.EDIT_CHAPTER
    assert headTab.editTextBox.isEnabled() is True
    assert headTab.editTextBox.toPlainText() == chTitle
    headTab.btnApply.click()
    assert headTab.editTextBox.isEnabled() is False

    # Unnumbered
    headTab.btnUnnumbered.click()
    assert headTab._editing == headTab.EDIT_UNNUM
    assert headTab.editTextBox.isEnabled() is True
    assert headTab.editTextBox.toPlainText() == unTitle
    headTab.btnApply.click()
    assert headTab.editTextBox.isEnabled() is False

    # Scene
    headTab.btnScene.click()
    assert headTab._editing == headTab.EDIT_SCENE
    assert headTab.editTextBox.isEnabled() is True
    assert headTab.editTextBox.toPlainText() == scTitle
    headTab.btnApply.click()
    assert headTab.editTextBox.isEnabled() is False

    # Hard Scene
    headTab.btnAScene.click()
    assert headTab._editing == headTab.EDIT_HSCENE
    assert headTab.editTextBox.isEnabled() is True
    assert headTab.editTextBox.toPlainText() == shTitle
    headTab.btnApply.click()
    assert headTab.editTextBox.isEnabled() is False

    # Section
    headTab.btnSection.click()
    assert headTab._editing == headTab.EDIT_SECTION
    assert headTab.editTextBox.isEnabled() is True
    assert headTab.editTextBox.toPlainText() == sxTitle
    headTab.btnApply.click()
    assert headTab.editTextBox.isEnabled() is False

    # Edit a Heading
    # ==============
    sBuild = bSettings._build
    assert sBuild.buildID == build.buildID

    # Create new format of all bits
    headTab.btnChapter.click()
    allFmt = (
        f"{nwHeadFmt.TITLE}{nwHeadFmt.CH_NUM}{nwHeadFmt.CH_WORD}{nwHeadFmt.CH_ROMU}"
        f"{nwHeadFmt.CH_ROML}{nwHeadFmt.SC_NUM}{nwHeadFmt.SC_ABS}"
    )
    headTab.editTextBox.clear()
    assert headTab.editTextBox.toPlainText() == ""
    headTab.aInsTitle.trigger()
    headTab.aInsChNum.trigger()
    headTab.aInsChWord.trigger()
    headTab.aInsChRomU.trigger()
    headTab.aInsChRomL.trigger()
    headTab.aInsScNum.trigger()
    headTab.aInsScAbs.trigger()
    assert headTab.editTextBox.toPlainText() == allFmt
    headTab.btnApply.click()
    assert sBuild.getStr("headings.fmtChapter") == allFmt

    # Check complex format
    headTab.btnChapter.click()
    headTab.editTextBox.setPlainText(f"Chapter {nwHeadFmt.CH_NUM}\n{nwHeadFmt.TITLE}\n")
    headTab.btnApply.click()
    assert sBuild.getStr("headings.fmtChapter") == (
        f"Chapter {nwHeadFmt.CH_NUM}{nwHeadFmt.BR}{nwHeadFmt.TITLE}"
    )

    # Set all to plain title
    headTab.btnPart.click()
    headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
    headTab.btnApply.click()
    assert sBuild.getStr("headings.fmtPart") == nwHeadFmt.TITLE

    headTab.btnChapter.click()
    headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
    headTab.btnApply.click()
    assert sBuild.getStr("headings.fmtChapter") == nwHeadFmt.TITLE

    headTab.btnUnnumbered.click()
    headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
    headTab.btnApply.click()
    assert sBuild.getStr("headings.fmtUnnumbered") == nwHeadFmt.TITLE

    headTab.btnScene.click()
    headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
    headTab.btnApply.click()
    assert sBuild.getStr("headings.fmtScene") == nwHeadFmt.TITLE

    headTab.btnAScene.click()
    headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
    headTab.btnApply.click()
    assert sBuild.getStr("headings.fmtAltScene") == nwHeadFmt.TITLE

    headTab.btnSection.click()
    headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
    headTab.btnApply.click()
    assert sBuild.getStr("headings.fmtSection") == nwHeadFmt.TITLE

    # Check hide switches
    headTab.swtScene.setChecked(True)
    headTab.swtSection.setChecked(True)
    headTab.saveContent()
    sBuild = bSettings._build
    assert sBuild.buildID == build.buildID

    assert sBuild.getBool("headings.hideScene") is True
    assert sBuild.getBool("headings.hideSection") is True

    # Finish
    button = bSettings.buttonBox.button(QtDialogClose)
    assert button is not None
    bSettings._dialogButtonClicked(button)
    # qtbot.stop()


@pytest.mark.gui
def testToolBuildSettings_FormatTextContent(qtbot, nwGUI):
    """Test the Text Content settings."""
    build = BuildSettings()

    build.setValue("text.includeBodyText", False)
    build.setValue("text.includeSynopsis", False)
    build.setValue("text.includeComments", False)
    build.setValue("text.includeStory", False)
    build.setValue("text.includeNotes", False)
    build.setValue("text.includeKeywords", False)
    build.setValue("text.ignoredKeywords", "")

    build.setValue("text.addNoteHeadings", False)

    # Create the dialog and populate it
    bSettings = GuiBuildSettings(nwGUI, build)
    bSettings.show()
    bSettings.loadContent()

    fmtTab = bSettings.optTabFormatting
    button = bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 1)
    assert button is not None
    button.click()
    assert bSettings.toolStack.currentWidget() is fmtTab

    # Check initial values
    assert fmtTab.incBodyText.isChecked() is False
    assert fmtTab.incSynopsis.isChecked() is False
    assert fmtTab.incComments.isChecked() is False
    assert fmtTab.incStory.isChecked() is False
    assert fmtTab.incNotes.isChecked() is False
    assert fmtTab.incKeywords.isChecked() is False
    assert fmtTab.ignoredKeywords.text() == ""

    assert fmtTab.addNoteHead.isChecked() is False

    # Toggle switches
    fmtTab.incBodyText.setChecked(True)
    fmtTab.incSynopsis.setChecked(True)
    fmtTab.incComments.setChecked(True)
    fmtTab.incStory.setChecked(True)
    fmtTab.incNotes.setChecked(True)
    fmtTab.incKeywords.setChecked(True)

    fmtTab.addNoteHead.setChecked(True)

    # Test cleanup of ignored keywords
    fmtTab.ignoredKeywords.setText("@stuff, @pizza, @object")  # First two are invalid
    fmtTab._updateIgnoredKeywords("@custom")  # Adding a new should trigger cleanup
    assert fmtTab.ignoredKeywords.text() in ("@custom, @object", "@object, @custom")

    # Save values
    fmtTab.saveContent()
    sBuild = bSettings._build
    assert sBuild.buildID == build.buildID

    assert sBuild.getBool("text.includeBodyText") is True
    assert sBuild.getBool("text.includeSynopsis") is True
    assert sBuild.getBool("text.includeComments") is True
    assert sBuild.getBool("text.includeStory") is True
    assert sBuild.getBool("text.includeNotes") is True
    assert sBuild.getBool("text.includeKeywords") is True
    assert sBuild.getStr("text.ignoredKeywords") in ("@custom, @object", "@object, @custom")

    assert sBuild.getBool("text.addNoteHeadings") is True

    # Finish
    button = bSettings.buttonBox.button(QtDialogClose)
    assert button is not None
    bSettings._dialogButtonClicked(button)
    # qtbot.stop()


@pytest.mark.gui
def testToolBuildSettings_FormatTextFormat(monkeypatch, qtbot, nwGUI):
    """Test the Text Format settings."""
    build = BuildSettings()

    build.setValue("format.buildLang", "en_US")
    build.setValue("format.textFont", "")  # Will fall back to config value
    build.setValue("format.lineHeight", 1.2)

    build.setValue("format.justifyText", False)
    build.setValue("format.stripUnicode", False)
    build.setValue("format.replaceTabs", False)
    build.setValue("format.keepBreaks", True)
    build.setValue("format.showDialogue", False)

    # Create the dialog and populate it
    bSettings = GuiBuildSettings(nwGUI, build)
    bSettings.show()
    bSettings.loadContent()

    fmtTab = bSettings.optTabFormatting
    button = bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 2)
    assert button is not None
    button.click()
    assert bSettings.toolStack.currentWidget() is fmtTab

    # Check initial values
    assert fmtTab.lineHeight.value() == 1.2

    assert fmtTab.justifyText.isChecked() is False
    assert fmtTab.stripUnicode.isChecked() is False
    assert fmtTab.replaceTabs.isChecked() is False
    assert fmtTab.keepBreaks.isChecked() is True
    assert fmtTab.showDialogue.isChecked() is False

    # Change values
    testFont = QFont("Arial", 11)
    fmtTab._textFont = testFont
    fmtTab.textFont.setText(describeFont(testFont))
    fmtTab.lineHeight.setValue(1.15)

    fmtTab.justifyText.setChecked(True)
    fmtTab.stripUnicode.setChecked(True)
    fmtTab.replaceTabs.setChecked(True)
    fmtTab.keepBreaks.setChecked(False)
    fmtTab.showDialogue.setChecked(True)

    # Save values
    fmtTab.saveContent()
    sBuild = bSettings._build
    assert sBuild.buildID == build.buildID

    assert sBuild.getStr("format.textFont") == testFont.toString()
    assert sBuild.getFloat("format.lineHeight") == 1.15

    assert sBuild.getBool("format.justifyText") is True
    assert sBuild.getBool("format.stripUnicode") is True
    assert sBuild.getBool("format.replaceTabs") is True
    assert sBuild.getBool("format.keepBreaks") is False
    assert sBuild.getBool("format.showDialogue") is True

    # Check that the font dialog doesn't fail
    with monkeypatch.context() as mp:
        font = QFont()
        font.setFamily("Times")
        font.setPointSize(10)
        mp.setattr(QFontDialog, "getFont", lambda *a, **k: (font, True))

        fmtTab.btnTextFont.click()
        assert fmtTab._textFont == font

    # Finish
    button = bSettings.buttonBox.button(QtDialogClose)
    assert button is not None
    bSettings._dialogButtonClicked(button)
    # qtbot.stop()


@pytest.mark.gui
def testToolBuildSettings_FormatFirstLineIndent(monkeypatch, qtbot, nwGUI):
    """Test the First Line Indent settings."""
    build = BuildSettings()

    build.setValue("format.firstLineIndent", False)
    build.setValue("format.firstIndentWidth", 1.4)
    build.setValue("format.indentFirstPar", False)

    # Create the dialog and populate it
    bSettings = GuiBuildSettings(nwGUI, build)
    bSettings.show()
    bSettings.loadContent()

    fmtTab = bSettings.optTabFormatting
    button = bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 3)
    assert button is not None
    button.click()
    assert bSettings.toolStack.currentWidget() is fmtTab

    # Check initial values
    assert fmtTab.firstIndent.isChecked() is False
    assert fmtTab.indentWidth.value() == 1.4
    assert fmtTab.indentFirstPar.isChecked() is False

    # Change values
    fmtTab.firstIndent.setChecked(True)
    fmtTab.indentWidth.setValue(2.0)
    fmtTab.indentFirstPar.setChecked(True)

    # Save values
    fmtTab.saveContent()
    sBuild = bSettings._build
    assert sBuild.buildID == build.buildID

    assert sBuild.getBool("format.firstLineIndent") is True
    assert sBuild.getFloat("format.firstIndentWidth") == 2.0
    assert sBuild.getBool("format.indentFirstPar") is True

    # Finish
    button = bSettings.buttonBox.button(QtDialogClose)
    assert button is not None
    bSettings._dialogButtonClicked(button)
    # qtbot.stop()


@pytest.mark.gui
def testToolBuildSettings_FormatPageLayout(monkeypatch, qtbot, nwGUI):
    """Test the First Line Indent settings."""
    build = BuildSettings()

    build.setValue("format.pageUnit", "mm")
    build.setValue("format.pageSize", "Custom")
    build.setValue("format.pageWidth", 180.0)
    build.setValue("format.pageHeight", 250.0)
    build.setValue("format.topMargin", 25.0)
    build.setValue("format.bottomMargin", 25.0)
    build.setValue("format.leftMargin", 15.0)
    build.setValue("format.rightMargin", 15.0)

    # Create the dialog and populate it
    bSettings = GuiBuildSettings(nwGUI, build)
    bSettings.show()
    bSettings.loadContent()

    fmtTab = bSettings.optTabFormatting
    button = bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 4)
    assert button is not None
    button.click()
    assert bSettings.toolStack.currentWidget() is fmtTab

    # Check initial values
    assert fmtTab.pageUnit.currentData() == "mm"
    assert fmtTab.pageSize.currentData() == "Custom"
    assert fmtTab.pageWidth.value() == 180.0
    assert fmtTab.pageHeight.value() == 250.0
    assert fmtTab.topMargin.value() == 25.0
    assert fmtTab.bottomMargin.value() == 25.0
    assert fmtTab.leftMargin.value() == 15.0
    assert fmtTab.rightMargin.value() == 15.0

    # Change values
    fmtTab.pageUnit.setCurrentIndex(fmtTab.pageUnit.findData("cm"))
    fmtTab.pageSize.setCurrentIndex(fmtTab.pageSize.findData("A4"))

    # Save values
    fmtTab.saveContent()

    assert fmtTab.pageUnit.currentData() == "cm"
    assert fmtTab.pageSize.currentData() == "A4"
    assert fmtTab.pageWidth.value() == 21.0
    assert fmtTab.pageHeight.value() == 29.7
    assert fmtTab.topMargin.value() == 2.5
    assert fmtTab.bottomMargin.value() == 2.5
    assert fmtTab.leftMargin.value() == 1.5
    assert fmtTab.rightMargin.value() == 1.5

    # Finish
    button = bSettings.buttonBox.button(QtDialogClose)
    assert button is not None
    bSettings._dialogButtonClicked(button)
    # qtbot.stop()


@pytest.mark.gui
def testToolBuildSettings_FormatOutput(qtbot, nwGUI):
    """Test the format-specific settings."""
    build = BuildSettings()

    build.setValue("doc.pageHeader", nwHeadFmt.DOC_AUTO)
    build.setValue("doc.pageCountOffset", 0)
    build.setValue("doc.colorHeadings", True)
    build.setValue("doc.scaleHeadings", True)
    build.setValue("doc.boldHeadings", True)

    build.setValue("html.addStyles", False)
    build.setValue("html.preserveTabs", False)

    # Create the dialog and populate it
    bSettings = GuiBuildSettings(nwGUI, build)
    bSettings.show()
    bSettings.loadContent()

    fmtTab = bSettings.optTabFormatting
    button = bSettings.sidebar._group.button(bSettings.OPT_FORMATTING + 5)
    assert button is not None
    button.click()
    assert bSettings.toolStack.currentWidget() is fmtTab

    # Check initial values
    assert fmtTab.odtPageHeader.text() == nwHeadFmt.DOC_AUTO
    assert fmtTab.odtPageCountOffset.value() == 0
    assert fmtTab.colorHeadings.isChecked() is True
    assert fmtTab.scaleHeadings.isChecked() is True
    assert fmtTab.boldHeadings.isChecked() is True

    assert fmtTab.htmlAddStyles.isChecked() is False
    assert fmtTab.htmlPreserveTabs.isChecked() is False

    # Change Values
    fmtTab.odtPageCountOffset.setValue(1)
    fmtTab.odtPageHeader.setText("Stuff")

    # Toggle all
    fmtTab.colorHeadings.setChecked(False)
    fmtTab.scaleHeadings.setChecked(False)
    fmtTab.boldHeadings.setChecked(False)
    fmtTab.htmlAddStyles.setChecked(True)
    fmtTab.htmlPreserveTabs.setChecked(True)

    # Save values
    fmtTab.saveContent()
    sBuild = bSettings._build
    assert sBuild.buildID == build.buildID

    assert sBuild.getStr("doc.pageHeader") == "Stuff"
    assert sBuild.getInt("doc.pageCountOffset") == 1
    assert sBuild.getBool("doc.colorHeadings") is False
    assert sBuild.getBool("doc.scaleHeadings") is False
    assert sBuild.getBool("doc.boldHeadings") is False

    assert sBuild.getBool("html.addStyles") is True
    assert sBuild.getBool("html.preserveTabs") is True

    # Reset header format
    fmtTab.btnPageHeader.click()
    assert fmtTab.odtPageHeader.text() == nwHeadFmt.DOC_AUTO

    # Finish
    button = bSettings.buttonBox.button(QtDialogClose)
    assert button is not None
    bSettings._dialogButtonClicked(button)
    # qtbot.stop()
