from stillControl.constants import UI_DIR
import json
import os
import subprocess

import stillControl.Utils as Utils
import stillControl.LayoutManager as LayoutManager
from stillControl import GSetting
import stillControl.GSettingCustomComboFunctions as GSettingCustomComboFunctions
from stillControl.i18n import _

import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk, Gio, GLib

_SETTING_JSON_DIR = os.path.join(UI_DIR, "settings")
_shell_settings = Gio.Settings.new("org.gnome.shell")
_extension_proxy = Utils.ExtensionProxy()
requires_extension = {}
_widget_required_extensions = {}
extension_conflicts = {}
setting_conflicts = {}
setting_visibility = {}
_conflicting_settings = []
_visibility_settings = []


def setting_reference_available(data, description="setting", warn=True):
    schema = data.get("schema")
    key = data.get("key")
    if schema and key and Utils.setting_exists(schema, key):
        return True

    if warn:
        print(
            f"Warning: Skipping {description} '{schema}::{key}' - "
            "the installed schema does not provide it."
        )
    return False


def register_extension_requirement(extension_uuid, widget):
    if extension_uuid not in requires_extension:
        requires_extension[extension_uuid] = []
    requires_extension[extension_uuid].append(widget)

    if widget not in _widget_required_extensions:
        _widget_required_extensions[widget] = []
    _widget_required_extensions[widget].append(extension_uuid)


def widget_required_extensions_enabled(widget):
    if widget not in _widget_required_extensions:
        return True

    extensions = _extension_proxy.get_extensions()
    for extension_uuid in _widget_required_extensions[widget]:
        if extension_uuid not in extensions:
            return False
        if not extensions[extension_uuid]["enabled"]:
            return False
    return True


def add_folder_button(row, path, tooltip):
    expanded_path = os.path.expanduser(path)

    def open_folder(_button):
        os.makedirs(expanded_path, exist_ok=True)
        subprocess.Popen(["xdg-open", expanded_path])

    def move_before_combo_cluster():
        sibling = button.get_prev_sibling()
        while sibling is not None and not isinstance(sibling, Gtk.Label):
            sibling = sibling.get_prev_sibling()
        if sibling is not None:
            button.insert_before(button.get_parent(), sibling)
        return GLib.SOURCE_REMOVE

    button = Gtk.Button.new_from_icon_name("folder-open-symbolic")
    button.set_valign(Gtk.Align.CENTER)
    button.add_css_class("circular")
    button.add_css_class("flat")
    button.set_tooltip_text(_(tooltip))
    button.connect("clicked", open_folder)
    row.add_suffix(button)
    GLib.idle_add(move_before_combo_cluster)


def parse_options(data):
    values, displays, display_subtitles = [], [], []
    subtitles = len(data[0]) > 2
    for item in data:
        displays.append(_(item[0]))
        values.append(item[1])
        if subtitles:
            display_subtitles.append(_(item[2]))
    return displays, values, display_subtitles


def set_extension_widget_visibility_all():
    extensions_to_check = list(requires_extension.keys())
    extensions = _extension_proxy.get_extensions()
    for extension_uuid in extensions.keys():
        if extension_uuid in extensions_to_check:
            visible = extensions[extension_uuid]["enabled"]
            for widget in requires_extension[extension_uuid]:
                widget.set_visible(visible)
            extensions_to_check.remove(extension_uuid)
    for extension_uuid in extensions_to_check:
        for widget in requires_extension[extension_uuid]:
            widget.set_visible(False)


def register_setting_visibility(data, widget):
    if not setting_reference_available(data, "visibility condition"):
        return

    schema = data["schema"]
    settings = Gio.Settings.new(schema)
    key = data["key"]
    values = data["values"] if type(data["values"]) is list else [data["values"]]

    if schema not in setting_visibility:
        setting_visibility[schema] = []

        # Prevents python from garbage collecting the settings object
        # And preventing the connection from working
        _visibility_settings.append(settings)

        settings.connect("changed", check_setting_visibility)

    setting_visibility[schema].append((key, values, widget))


def check_setting_visibility(settings, changed_key=None, *args):
    schema = settings.props.schema_id
    if schema not in setting_visibility:
        return

    for key, values, widget in setting_visibility[schema]:
        if changed_key is not None and changed_key != key:
            continue

        current_value = Utils.serialize_setting(settings, key)
        widget.set_visible(
            current_value in values and widget_required_extensions_enabled(widget)
        )


def check_setting_visibility_all():
    for schema in setting_visibility.keys():
        check_setting_visibility(Gio.Settings.new(schema))


def check_extension_conflicts():
    # Skip during layout switching to avoid cascading updates
    if LayoutManager.is_layout_switching():
        return
    
    for ext in extension_conflicts:
        state = ext in _shell_settings.get_strv("enabled-extensions")
        for conflict in extension_conflicts[ext]:
            widget, message, default_subtitle, fix, gsetting = conflict
            if state:
                if not message:
                    widget.set_visible(False)
                widget.set_sensitive(False)
                widget.set_subtitle(message)
                if fix is not None and gsetting is not None:
                    if Utils.serialize_setting(gsetting.settings, gsetting.key) != fix:
                        Utils.set_unknown_type(gsetting.settings, gsetting.key, fix)
                break
            else:
                widget.set_visible(True)
                widget.set_sensitive(True)
                widget.set_subtitle(default_subtitle)


def check_setting_conflicts(settings, _key, *args):
    # Skip during layout switching to avoid cascading updates
    if LayoutManager.is_layout_switching():
        return
    
    for conflict in setting_conflicts[settings.props.schema_id]:
        key, value, widget, message, default_subtitle, fix, gsetting = conflict
        is_broken = Utils.serialize_setting(settings, key) == value
        # print(f"Checking {settings.props.schema_id} {widget.get_title()} {key} {value} {is_broken}")
        if is_broken:
            if not message:
                widget.set_visible(False)
            widget.set_sensitive(False)
            widget.set_subtitle(message)
            if fix is not None and gsetting is not None:
                if Utils.serialize_setting(gsetting.settings, gsetting.key) != fix:
                    Utils.set_unknown_type(gsetting.settings, gsetting.key, fix)
        else:
            widget.set_visible(True)
            widget.set_sensitive(True)
            widget.set_subtitle(default_subtitle)



def extensions_changed(dbus_proxy, sender, signal, params):
    # Skip updates during layout switching to avoid cascading UI updates
    if LayoutManager.is_layout_switching():
        return
    
    uuid = params[0]
    state = params[1]["enabled"]
    # print(f"Extension {uuid} state changed to {state}")
    if uuid in requires_extension:
        for widget in requires_extension[uuid]:
            widget.set_visible(state)
    check_setting_visibility_all()
    check_extension_conflicts()


_extension_proxy.proxy.connect("g-signal::ExtensionStateChanged", extensions_changed)


def parse_adjustment(data):
    adjustment = Gtk.Adjustment()
    if data.get("lower"):
        adjustment.set_lower(data["lower"])
    if data.get("upper"):
        adjustment.set_upper(data["upper"])
    if data.get("step_increment"):
        adjustment.set_step_increment(data["step_increment"])

    return adjustment


def parse_json(builder):
    for filename in os.listdir(_SETTING_JSON_DIR):
        if filename.endswith('.json'):
            file_path = os.path.join(_SETTING_JSON_DIR, filename)
            with open(file_path, "r") as file:
                data = json.load(file)
            for group_name in data:
                group = builder.get_object(group_name)
                if group is None:
                    raise ValueError(f"Group {group_name} not found in the builder")

                # Extra Flags for GSetting Group
                if data[group_name].get("requires_extension"):
                    extension_uuid = data[group_name]["requires_extension"]
                    register_extension_requirement(extension_uuid, group)

                if data[group_name].get("visible_when_setting"):
                    register_setting_visibility(data[group_name]["visible_when_setting"], group)

                # Adding items to GSetting Groups
                # Case statement links to type.
                for setting in data[group_name]["items"]:
                    gsetting = None
                    if setting.get("gsetting") and not setting_reference_available(
                        setting["gsetting"],
                        "setting control",
                        warn=not setting.get("optional", False),
                    ):
                        continue

                    setting_type = setting["type"]
                    match setting_type.replace("_", "-"):
                        case "switch":
                            gsetting = GSetting.from_dict(setting["gsetting"])
                            setting_widget = group.add_switch(gsetting)
                        case "switch-inverse":
                            gsetting = GSetting.from_dict(setting["gsetting"])
                            setting_widget = group.add_switch_inverse(gsetting)
                        case "switch-extension":
                            subtitle = _(setting.get("subtitle", ""))
                            icon = setting.get("icon_name")
                            setting_widget = group.add_extension_switch(_(setting["title"]), subtitle, icon, setting["extension"])
                        case "spin":
                            gsetting = GSetting.from_dict(setting["gsetting"])
                            setting_widget = group.add_spin(
                                gsetting, setting["spin_type"], setting["percent"],
                                parse_adjustment(setting["adjustment"])
                            )
                        case "font":
                            gsetting = GSetting.from_dict(setting["gsetting"])
                            setting_widget = group.add_font(gsetting)
                        case "combo":
                            gsetting = GSetting.from_dict(setting["gsetting"])
                            if setting.get("python_options"):
                                item_function = GSettingCustomComboFunctions.function_ids[setting["python_options"]]
                                displays, values, display_subtitles = item_function()
                            else:
                                displays, values, *display_subtitles = parse_options(setting["options"])
                            setting_widget = group.add_combo(gsetting, values, displays)
                        case "detailed-combo":
                            gsetting = GSetting.from_dict(setting["gsetting"])
                            if setting.get("python_options"):
                                item_function = GSettingCustomComboFunctions.function_ids[setting["python_options"]]
                                displays, values, display_subtitles = item_function()
                            else:
                                displays, values, display_subtitles = parse_options(setting["options"])
                            setting_widget = group.add_detailed_combo(gsetting, values, displays, display_subtitles)
                        case "arc-menu-layout-button":
                            gsetting = GSetting.from_dict(setting["gsetting"])
                            setting_widget = group.add_arc_menu_layout_button(gsetting)
                        case "arc-menu-super-key-combo":
                            setting_widget = group.add_arc_menu_super_key_combo()
                        case "extension-setting-button":
                            subtitle = _(setting.get("subtitle", ""))
                            icon = setting.get("icon_name")
                            setting_widget = group.add_extension_setting_button(
                                _(setting["title"]), subtitle, icon, setting["extension"]
                            )
                            if setting["extension"] not in requires_extension:
                                requires_extension[setting["extension"]] = []
                            requires_extension[setting["extension"]].append(setting_widget)
                        case "dash-to-panel-monitor-dropdown":
                            gsetting = GSetting.from_dict(setting["gsetting"])
                            if setting.get("python_options"):
                                displays, values, *display_subtitles = function_ids[setting["python_options"]]()
                            else:
                                displays, values, *display_subtitles = parse_options(setting["options"])
                            setting_widget = group.add_dash_to_panel_monitor_dropdown(gsetting, values, displays)
                        case "dash-to-panel-monitor-spin":
                            gsetting = GSetting.from_dict(setting["gsetting"])
                            setting_widget = group.add_dash_to_panel_monitor_spin(
                                gsetting, parse_adjustment(setting["adjustment"])
                            )
                        case "dash-to-panel-elements":
                            setting_widget = group.add_dash_to_panel_elements()
                        case _:
                            raise ValueError(f"Unknown setting type {setting_type}")

                    # Apply CSS classes if specified
                    if setting.get("css_classes"):
                        for css_class in setting["css_classes"]:
                            setting_widget.add_css_class(css_class)

                    if setting.get("folder_button"):
                        folder_button = setting["folder_button"]
                        add_folder_button(
                            setting_widget,
                            folder_button["path"],
                            folder_button.get("tooltip", "Open Folder"),
                        )

                    # Extra flags for settings:
                    if setting.get("extension_conflicts"):
                        for item in setting["extension_conflicts"]:
                            uuid = item.get("uuid")
                            message = _(item.get("message", ""))
                            fix = item.get("fix")

                            if uuid not in extension_conflicts:
                                extension_conflicts[uuid] = []

                            extension_conflicts[uuid].append(
                                (setting_widget, message, setting_widget.get_subtitle(), fix, gsetting)
                            )

                    if setting.get("setting_conflicts"):
                        for item in setting["setting_conflicts"]:
                            if not setting_reference_available(item, "conflict condition"):
                                continue

                            schema = item["schema"]
                            settings = Gio.Settings.new(schema)
                            key = item["key"]
                            value = item["value"]
                            message = _(item.get("message", ""))
                            fix = item.get("fix")

                            if schema not in setting_conflicts:
                                setting_conflicts[schema] = []

                                # Prevents python from garbage collecting the settings object
                                # And preventing the connection from working
                                _conflicting_settings.append(settings)

                                settings.connect(f"changed", check_setting_conflicts)
                                # print(setting_conflicts)

                            setting_conflicts[schema].append(
                                (key, value, setting_widget, message, setting_widget.get_subtitle(), fix, gsetting)
                            )

                    if setting.get("extension_required"):
                        register_extension_requirement(setting["extension_required"], setting_widget)

                    if setting.get("visible_when_setting"):
                        register_setting_visibility(setting["visible_when_setting"], setting_widget)

    set_extension_widget_visibility_all()
    check_setting_visibility_all()
    check_extension_conflicts()

    for setting in setting_conflicts.keys():
        check_setting_conflicts(Gio.Settings.new(setting), None)
