from gi.repository import Adw, Gio, GLib

from enum import StrEnum
import logging
from typing import Optional

from iotas import const

settings = Gio.Settings.new(const.APP_ID)

STYLE = "style-variant"
WINDOW_SIZE = "window-size"
FIRST_START = "first-start"
FONT_SIZE = "font-size"
LINE_LENGTH = "line-length"
USE_MONOSPACE_FONT = "use-monospace-font"
EDITOR_THEME = "editor-theme"
EDITOR_FORMATTING_BAR_VISIBILTY = "editor-formatting-bar-visibility"
EDITOR_HEADER_BAR_VISIBILTY = "editor-header-bar-visibility"
BACKUP_NOTE_EXTENSION = "backup-note-extension"
PERSIST_SIDEBAR = "persist-sidebar"
INDEX_CATEGORY_STYLE = "index-category-style"

NEXTCLOUD_ENDPOINT = "nextcloud-endpoint"
NEXTCLOUD_USERNAME = "nextcloud-username"
NEXTCLOUD_PRUNE_THRESHOLD = "nextcloud-prune-threshold"
SYNC_INTERVAL = "sync-interval"
NETWORK_TIMEOUT = "network-timeout"

SPELLING_ENABLED = "spelling-enabled"
SPELLING_LANGUAGE = "spelling-language"

SHOW_STARTUP_SECRET_SERVICE_FAILURE = "show-startup-secret-service-failure"
SHOW_SYNCING_DEBUG_NOTIFICATION = "show-syncing-debug-notification"

MARKDOWN_RENDER = "markdown-render-enabled"
MARKDOWN_DETECT_SYNTAX = "markdown-syntax-detection-enabled"
MARKDOWN_KEEP_WEBKIT_PROCESS = "markdown-keep-webkit-process"
MARKDOWN_TEX_SUPPORT = "markdown-tex-support"
MARKDOWN_USE_MONOSPACE_FONT = "markdown-use-monospace-font"
MARKDOWN_RENDER_MONOSPACE_FONT_RATIO = "markdown-render-proportional-to-monospace-font-ratio"
MARKDOWN_DEFAULT_TO_RENDER = "markdown-default-to-render"

LAST_LAUNCHED_VERSION = "last-launched-version"
LAST_EXPORT_DIRECTORY = "last-export-directory"
EXTRA_EXPORT_FORMATS = "extra-export-formats"
SHOW_EXTENDED_PREFERENCES = "show-extended-preferences"


class HeaderBarVisibility(StrEnum):
    """Top or bottom bar visibility."""

    ALWAYS_VISIBLE = "always-visible"
    AUTO_HIDE = "auto-hide"
    AUTO_HIDE_FULLSCREEN_ONLY = "auto-hide-fullscreen-only"
    DISABLED = "disabled"


def get_window_size() -> GLib.Variant:
    """Get window size.

    :return: The size
    :rtype: GLib.Variant
    """
    return settings.get_value(WINDOW_SIZE)


def set_window_size(width: int, height: int) -> None:
    """Set window size.

    :param int width: Width
    :param int height: Height
    """
    g_variant = GLib.Variant("ai", (width, height))
    settings.set_value(WINDOW_SIZE, g_variant)


def get_first_start() -> bool:
    """Get whether doing first startup.

    :return: First startup
    :rtype: bool
    """
    return settings.get_value(FIRST_START)


def set_first_start(value: bool) -> None:
    """Set whether first start.

    :param bool value: New value
    """
    settings.set_boolean(FIRST_START, value)


def get_use_monospace_font() -> bool:
    """Get whether to use a monospace font.

    :return: Using monospace font
    :rtype: bool
    """
    return settings.get_value(USE_MONOSPACE_FONT)


def set_use_monospace_font(value: bool) -> None:
    """Set whether to use a monospace font.

    :param bool value: New value
    """
    settings.set_boolean(USE_MONOSPACE_FONT, value)


def get_font_size() -> int:
    """Get font size.

    :return: Size
    :rtype: int
    """
    return settings.get_int(FONT_SIZE)


def set_font_size(value: int) -> None:
    """Set font size.

    :param int value: New value
    """
    settings.set_int(FONT_SIZE, value)


def get_default_font_size() -> int:
    """Get default font size.

    :return: Default size
    :rtype: int
    """
    return settings.get_default_value(FONT_SIZE).get_int32()


def get_line_length() -> int:
    """Get line length.

    :return: Size in pixels
    :rtype: int
    """
    return settings.get_int(LINE_LENGTH)


def set_line_length(value: int) -> None:
    """Set line length.

    :param int value: New value
    """
    settings.set_int(LINE_LENGTH, value)


def get_default_line_length() -> int:
    """Get default line length.

    :return: Default length
    :rtype: int
    """
    return settings.get_default_value(LINE_LENGTH).get_int32()


def get_line_length_max() -> int:
    """Get line length maximum.

    :return: Size in pixels
    :rtype: int
    """
    return settings.get_property("settings-schema").get_key(LINE_LENGTH).get_range()[1][-1]


def get_spelling_enabled() -> bool:
    """Get whether spelling enabled.

    :return: Spelling enabled
    :rtype: bool
    """
    return settings.get_value(SPELLING_ENABLED)


def set_spelling_enabled(value: bool) -> None:
    """Set spelling enabled.

    :param bool value: New value
    """
    settings.set_boolean(SPELLING_ENABLED, value)


def get_spelling_language() -> Optional[str]:
    """Get spelling language.

    :return: Language tag or None if empty
    :rtype: str
    """
    lang = settings.get_string(SPELLING_LANGUAGE)
    if lang.strip() == "":
        lang = None
    return lang


def set_spelling_language(value: str) -> None:
    """Set spelling language.

    :param str value: New value
    """
    settings.set_string(SPELLING_LANGUAGE, value)


def get_style() -> str:
    """Get style.

    :return: Style
    :rtype: str
    """
    return settings.get_string(STYLE)


def set_style(value: str) -> None:
    """Set style.

    :param str value: New value
    """
    settings.set_string(STYLE, value)


def get_sync_interval() -> int:
    """Get sync interval.

    :return: Interval
    :rtype: int
    """
    return settings.get_int(SYNC_INTERVAL)


def set_sync_interval(value: int) -> None:
    """Set sync interval.

    :param int value: New value
    """
    settings.set_int(SYNC_INTERVAL, value)


def get_index_category_style() -> str:
    """Get the index category label style.

    :return: The style
    :rtype: str
    """
    return settings.get_string(INDEX_CATEGORY_STYLE)


def set_index_category_style(value: str) -> None:
    """Set the index category style.

    :param str value: New value
    """
    settings.set_string(INDEX_CATEGORY_STYLE, value)


def get_pin_sidebar() -> bool:
    """Get whether to pin the index sidebar (in large windows).

    :return: Whether to hide
    :rtype: bool
    """
    return settings.get_value(PERSIST_SIDEBAR)


def set_pin_sidebar(value: bool) -> None:
    """Set whether to pin the index sidebar (in large windows).

    :param bool value: New value
    """
    settings.set_boolean(PERSIST_SIDEBAR, value)


def get_editor_theme() -> str:
    """Get editor theme.

    :return: Theme
    :rtype: str
    """
    return settings.get_string(EDITOR_THEME)


def set_editor_theme(value: str) -> None:
    """Set editor theme.

    :param str value: New value
    """
    settings.set_string(EDITOR_THEME, value)


def get_editor_formatting_bar_visibility() -> Optional[HeaderBarVisibility]:
    """Get the formatting bar visibility.

    :return: Visibility, or None
    :rtype: HeaderBarVisibility, optional
    """
    setting_value = settings.get_string(EDITOR_FORMATTING_BAR_VISIBILTY)
    try:
        obj = HeaderBarVisibility(setting_value)
    except ValueError as e:
        logging.warning(f"Couldn't parse formatting bar visibility from '{setting_value}': {e}")
        return None
    else:
        return obj


def get_editor_formatting_bar_visible_for_window_state() -> bool:
    """Get whether the formatting bar is configured visible for the window maximised state."

    :return: Whether visible
    :rtype: bool
    """
    setting_value = settings.get_string(EDITOR_FORMATTING_BAR_VISIBILTY)
    try:
        obj = HeaderBarVisibility(setting_value)
    except ValueError as e:
        logging.warning(f"Couldn't parse formatting bar visibility from '{setting_value}': {e}")
        return False
    else:
        visible = False
        if get_markdown_detect_syntax():
            if obj == HeaderBarVisibility.ALWAYS_VISIBLE:
                visible = True
            elif obj == HeaderBarVisibility.AUTO_HIDE_FULLSCREEN_ONLY:
                app = Gio.Application.get_default()
                if not app:
                    logging.error("Couldn't get app?")
                    return False
                window = app.get_active_window()
                visible = not window.is_fullscreen()
        return visible


def set_editor_formatting_bar_visibility(value: HeaderBarVisibility) -> None:
    """Set the formatting bar visibility.

    :param HeaderBarVisibility value: New value
    """
    settings.set_string(EDITOR_FORMATTING_BAR_VISIBILTY, str(value))


def get_editor_header_bar_visibility() -> Optional[HeaderBarVisibility]:
    """Get the header bar visibility.

    :return: The visibility setting
    :rtype: HeaderBarVisibility
    """
    setting_value = settings.get_string(EDITOR_HEADER_BAR_VISIBILTY)
    try:
        obj = HeaderBarVisibility(setting_value)
    except ValueError as e:
        logging.warning(f"Couldn't parse header bar visibility from '{setting_value}': {e}")
        return None
    else:
        return obj


def get_editor_header_bar_visible_for_window_state() -> bool:
    """Get whether the header bar is configured visible for the window maximised state."

    :return: Whether visible
    :rtype: bool
    """
    setting_value = settings.get_string(EDITOR_HEADER_BAR_VISIBILTY)
    try:
        obj = HeaderBarVisibility(setting_value)
    except ValueError as e:
        logging.warning(f"Couldn't parse header bar visibility from '{setting_value}': {e}")
        return False
    else:
        visible = False
        if obj == HeaderBarVisibility.ALWAYS_VISIBLE:
            visible = True
        elif obj == HeaderBarVisibility.AUTO_HIDE_FULLSCREEN_ONLY:
            app = Gio.Application.get_default()
            if not app:
                logging.error("Couldn't get app?")
                return False
            window = app.get_active_window()
            visible = not window.is_fullscreen()
        return visible


def set_editor_header_bar_visibility(value: HeaderBarVisibility) -> None:
    """Set the header bar visibility.

    :param HeaderBarVisibility value: New value
    """
    settings.set_string(EDITOR_HEADER_BAR_VISIBILTY, str(value))


def get_markdown_render_enabled() -> bool:
    """Get markdown render is enabled.

    :return: Markdown render enabled
    :rtype: bool
    """
    return settings.get_value(MARKDOWN_RENDER)


def set_markdown_render_enabled(value: bool) -> None:
    """Set markdown render enabled.

    :param bool value: New value
    """
    settings.set_boolean(MARKDOWN_RENDER, value)


def get_markdown_detect_syntax() -> bool:
    """Get markdown syntax detection is enabled.

    :return: Enabled
    :rtype: bool
    """
    return settings.get_value(MARKDOWN_DETECT_SYNTAX)


def set_markdown_detect_syntax(value: bool) -> None:
    """Set markdown syntax detection is enabled.

    :param bool value: New value
    """
    settings.set_boolean(MARKDOWN_DETECT_SYNTAX, value)


def get_markdown_keep_webkit_process() -> bool:
    """Get markdown WebKit process being retained.

    :return: WebKit process being retained
    :rtype: bool
    """
    return settings.get_value(MARKDOWN_KEEP_WEBKIT_PROCESS)


def set_markdown_keep_webkit_process(value: bool) -> None:
    """Set markdown WebKit process being retained.

    :param bool value: New value
    """
    settings.set_boolean(MARKDOWN_KEEP_WEBKIT_PROCESS, value)


def get_markdown_tex_support() -> bool:
    """Get whether markdown TeX rendering is supported.

    :return: Markdown TeX supported
    :rtype: bool
    """
    return settings.get_value(MARKDOWN_TEX_SUPPORT)


def set_markdown_tex_support(value: bool) -> None:
    """Set whether markdown TeX rendering is supported.

    :param bool value: New value
    """
    settings.set_boolean(MARKDOWN_TEX_SUPPORT, value)


def get_markdown_use_monospace_font() -> bool:
    """Get whether to use a monospace font for the markdown render.

    :return: Using monospace font
    :rtype: bool
    """
    return settings.get_value(MARKDOWN_USE_MONOSPACE_FONT)


def set_markdown_use_monospace_font(value: bool) -> None:
    """Set whether to use a monospace font for the markdown render.

    :param bool value: New value
    """
    settings.set_boolean(MARKDOWN_USE_MONOSPACE_FONT, value)


def set_markdown_render_monospace_font_ratio(value: float) -> None:
    """Set the adjustment in size from proportional to fixed width font.

    :param bool value: New value
    """
    settings.set_double(MARKDOWN_RENDER_MONOSPACE_FONT_RATIO, value)


def get_markdown_render_monospace_font_ratio() -> float:
    """Get the adjustment in size from proportional to fixed width font.

    :return: Ratio
    :rtype: float
    """
    return settings.get_double(MARKDOWN_RENDER_MONOSPACE_FONT_RATIO)


def get_markdown_default_to_render() -> bool:
    """Get whether to render the markdown when opening the note.

    :return: Defaulting to render
    :rtype: bool
    """
    return settings.get_value(MARKDOWN_DEFAULT_TO_RENDER)


def set_markdown_default_to_render(value: bool) -> None:
    """Set whether to render the markdown when opening the note.

    :param bool value: New value
    """
    settings.set_boolean(MARKDOWN_DEFAULT_TO_RENDER, value)


def get_nextcloud_endpoint() -> str:
    """Get Nextcloud endpoint.

    :return: Endpoint
    :rtype: str
    """
    return settings.get_string(NEXTCLOUD_ENDPOINT)


def set_nextcloud_endpoint(value: str) -> None:
    """Set Nextcloud endpoint.

    :param str value: New value
    """
    settings.set_string(NEXTCLOUD_ENDPOINT, value)


def get_nextcloud_username() -> str:
    """Get Nextcloud username.

    :return: Username
    :rtype: str
    """
    return settings.get_string(NEXTCLOUD_USERNAME)


def set_nextcloud_username(value: str) -> None:
    """Set Nextcloud username.

    :param str value: New value
    """
    settings.set_string(NEXTCLOUD_USERNAME, value)


def nextcloud_sync_configured() -> bool:
    """Get whether sync with Nextcloud is configured.

    This does not mean authentication has been successful this session.

    :return: Whether configured
    :rtype: bool
    """
    sync_username = get_nextcloud_username()
    sync_endpoint = get_nextcloud_endpoint()
    return sync_username != "" and sync_endpoint != ""


def get_nextcloud_prune_threshold() -> int:
    """Get Nextcloud prune threshold.

    :return: Threshold
    :rtype: int
    """
    return settings.get_int(NEXTCLOUD_PRUNE_THRESHOLD)


def set_nextcloud_prune_threshold(value: int) -> None:
    """Set Nextcloud prune threshold.

    :param int value: New value
    """
    settings.set_int(NEXTCLOUD_PRUNE_THRESHOLD, value)


def get_show_startup_secret_service_failure() -> bool:
    """Get to show Secret Service failure at startup.

    :return: Whether to show
    :rtype: bool
    """
    return settings.get_value(SHOW_STARTUP_SECRET_SERVICE_FAILURE)


def get_show_syncing_debug_notification() -> bool:
    """Get whether to show sync debug notifications.

    :return: Whether to show
    :rtype: bool
    """
    return settings.get_value(SHOW_SYNCING_DEBUG_NOTIFICATION)


def get_backup_note_extension() -> str:
    """Get file extension for backed up notes.

    :return: Extension
    :rtype: str
    """
    return settings.get_string(BACKUP_NOTE_EXTENSION)


def set_backup_note_extension(value: str) -> None:
    """Set file extension for backed up notes.

    :param str value: New value
    """
    settings.set_string(BACKUP_NOTE_EXTENSION, value)


def get_last_launched_version() -> str:
    """Get the last version which was run.

    :return: Version
    :rtype: str
    """
    return settings.get_string(LAST_LAUNCHED_VERSION)


def set_last_launched_version(value: str) -> None:
    """Set the last version which was run.

    :param str value: New value
    """
    settings.set_string(LAST_LAUNCHED_VERSION, value)


def get_last_export_directory() -> str:
    """Get the last export directory.

    :return: Directory
    :rtype: str
    """
    return settings.get_string(LAST_EXPORT_DIRECTORY)


def set_last_export_directory(value: str) -> None:
    """Set the last export directory.

    :param str value: New value
    """
    settings.set_string(LAST_EXPORT_DIRECTORY, value)


def get_extra_export_formats() -> str:
    """Get any extra export formats.

    :return: Raw formats string
    :rtype: str
    """
    return settings.get_string(EXTRA_EXPORT_FORMATS)


def set_extra_export_formats(value: str) -> None:
    """Set extra export formats.

    :param str value: New value
    """
    settings.set_string(EXTRA_EXPORT_FORMATS, value)


def get_show_extended_preferences() -> bool:
    """Get whether to show a great many preferences.

    :return: Whether to show
    :rtype: bool
    """
    return settings.get_value(SHOW_EXTENDED_PREFERENCES)


def set_show_extended_preferences(value: bool) -> None:
    """Set whether to show a great many preferences.

    :param bool value: New value
    """
    settings.set_boolean(SHOW_EXTENDED_PREFERENCES, value)


def get_network_timeout() -> float:
    """Get the network timeout.

    :return: Timeout in seconds
    :rtype: float
    """
    return settings.get_double(NETWORK_TIMEOUT)


def set_network_timeout(value: float) -> None:
    """Set the network timeout.

    :param int value: New value
    """
    settings.set_double(NETWORK_TIMEOUT, value)


# Piped through the configuration manager for now as a result of the HTML generator using it
# directly as configuration.
def get_toolbar_underlay_padding_height() -> int:
    """Fetch the height of padding to account for overlay header bars.

    :return: Height in pixels
    :rtype: int
    """
    return const.HEADER_BAR_HEIGHT


# Piped through the configuration manager for now as a result of the HTML generator using it
# directly as configuration.
def get_high_contrast() -> bool:
    """Get whether high contrast output should be used for screen rendering.

    :return: Whether visible
    :rtype: bool
    """
    return Adw.StyleManager.get_default().get_high_contrast()


# TODO remove ~early 2025 #####################################################

# Retaining to migrate
MARKDOWN_SYNTAX_HIGHLIGHTING = "markdown-syntax-highlighting-enabled"
HIDE_EDITOR_HEADERBAR = "auto-hide-editor-headerbar"
HIDE_HEADERBAR_WHEN_FULLSCREEN = "hide-editor-headerbar-when-fullscreen"


def get_markdown_syntax_hightlighting_enabled() -> bool:
    """Get markdown syntax highlighting is enabled.

    :return: Highlighting enabled
    :rtype: bool
    """
    return settings.get_value(MARKDOWN_SYNTAX_HIGHLIGHTING)


def get_hide_editor_headerbar() -> bool:
    """Get whether to hide the editor headerbar.

    :return: Whether to hide
    :rtype: bool
    """
    return settings.get_value(HIDE_EDITOR_HEADERBAR)


def get_hide_editor_headerbar_when_fullscreen() -> bool:
    """Get whether to hide the editor headerbar when fullscreen.

    :return: Whether to hide
    :rtype: bool
    """
    return settings.get_value(HIDE_HEADERBAR_WHEN_FULLSCREEN)


###############################################################################
