import Adw from "gi://Adw";
import Gio from "gi://Gio";
import GioUnix from "gi://GioUnix";
import Gtk from "gi://Gtk";
import { ExtensionPreferences } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
const LOG_LEVELS = [
    "debug",
    "info",
    "warn",
    "error",
];
export default class JunkNotificationCleanerPreferences extends ExtensionPreferences {
    model;
    getPreferencesWidget() {
        this.model = new PreferencesModel(this.getSettings());
        const page = new Adw.PreferencesPage();
        page.set_title("Settings");
        page.set_icon_name("preferences-system-symbolic");
        page.add(this.buildGeneralGroup());
        page.add(this.buildLoggingGroup());
        page.add(this.buildExcludedAppsGroup());
        return page;
    }
    buildGeneralGroup() {
        const group = new Adw.PreferencesGroup();
        group.set_title("General Settings");
        group.add(this.buildSwitchRow({
            key: "delete-on-focus",
            title: "Delete on Focus",
            subtitle: "Delete notifications when an application window is focused.",
        }));
        group.add(this.buildSwitchRow({
            key: "delete-on-close",
            title: "Delete on Close",
            subtitle: "Delete notifications when an application window is closed.",
        }));
        return group;
    }
    buildSwitchRow(opts) {
        const row = new Adw.ActionRow({
            title: opts.title,
            subtitle: opts.subtitle,
        });
        const toggle = new Gtk.Switch({ valign: Gtk.Align.CENTER });
        this.model.bindDeleteTrigger(opts.key, toggle, "active");
        row.add_suffix(toggle);
        return row;
    }
    buildLoggingGroup() {
        const group = new Adw.PreferencesGroup();
        group.set_title("Logging");
        const row = new Adw.ActionRow({
            title: "Log Level",
            subtitle: "Set the logging level for troubleshooting notification matching.",
        });
        const dropdown = new Gtk.DropDown({
            model: Gtk.StringList.new([...LOG_LEVELS]),
            valign: Gtk.Align.CENTER,
        });
        this.bindLogLevelDropdown(dropdown);
        row.add_suffix(dropdown);
        group.add(row);
        return group;
    }
    bindLogLevelDropdown(dropdown) {
        const sync = () => {
            dropdown.set_selected(this.model.getLogLevelIndex());
        };
        sync();
        const changedId = this.model.onLogLevelChanged(sync);
        dropdown.connect("notify::selected", (dd) => {
            this.model.setLogLevelIndex(dd.get_selected());
        });
        dropdown.connect("destroy", () => {
            this.model.disconnect(changedId);
        });
    }
    buildExcludedAppsGroup() {
        const group = new Adw.PreferencesGroup();
        group.set_title("Excluded Applications");
        group.set_description("Applications whose notifications will not be automatically deleted.");
        const listBox = this.buildExcludedAppsList();
        const addButton = new Gtk.Button({
            icon_name: "list-add-symbolic",
            tooltip_text: "Add application",
            css_classes: ["flat"],
            valign: Gtk.Align.CENTER,
        });
        addButton.connect("clicked", (btn) => {
            const parent = btn.get_root();
            this.openAppSelector(parent, (appId) => {
                this.model.addExcludedApp(appId);
            });
        });
        group.set_header_suffix(addButton);
        group.add(listBox);
        return group;
    }
    buildExcludedAppsList() {
        const listBox = new Gtk.ListBox({
            selection_mode: Gtk.SelectionMode.NONE,
            css_classes: ["boxed-list"],
        });
        listBox.set_placeholder(new Gtk.Label({
            label: "No excluded applications.",
            css_classes: ["dim-label"],
            margin_top: 12,
            margin_bottom: 12,
        }));
        this.rebuildExcludedAppsList(listBox);
        const handlerId = this.model.onExcludedAppsChanged(() => {
            this.rebuildExcludedAppsList(listBox);
        });
        listBox.connect("destroy", () => {
            this.model.disconnect(handlerId);
        });
        return listBox;
    }
    rebuildExcludedAppsList(listBox) {
        listBox.remove_all();
        for (const appId of this.model.getExcludedApps()) {
            listBox.append(this.buildExcludedAppRow(appId));
        }
    }
    buildExcludedAppRow(appId) {
        const app = tryLoadDesktopApp(appId);
        const row = buildAppRow(app, appId);
        if (!app)
            row.set_subtitle("Uninstalled");
        const removeButton = new Gtk.Button({
            icon_name: "user-trash-symbolic",
            tooltip_text: "Remove",
            css_classes: ["flat"],
            valign: Gtk.Align.CENTER,
        });
        removeButton.connect("clicked", () => {
            this.model.removeExcludedApp(appId);
        });
        row.add_suffix(removeButton);
        return row;
    }
    openAppSelector(parent, onSelected) {
        const window = new Adw.Window({
            title: "Add Excluded Application",
            modal: true,
            default_width: 420,
            default_height: 520,
        });
        if (parent)
            window.set_transient_for(parent);
        const search = new Gtk.SearchEntry({
            placeholder_text: "Search applications",
            hexpand: true,
        });
        const headerBar = new Adw.HeaderBar();
        headerBar.set_title_widget(search);
        const appList = buildSelectableAppList(this.getSelectableApps(), (appId) => {
            onSelected(appId);
            window.close();
        });
        wireSearchFilter(appList, search);
        addCloseOnEscape(window);
        const toolbarView = new Adw.ToolbarView();
        toolbarView.add_top_bar(headerBar);
        toolbarView.set_content(new Gtk.ScrolledWindow({ child: appList }));
        window.set_content(toolbarView);
        window.present();
        search.grab_focus();
    }
    getSelectableApps() {
        const excluded = new Set(this.model.getExcludedApps());
        return Gio.AppInfo.get_all()
            .filter((a) => a.should_show() && a.get_id() && !excluded.has(getAppId(a)))
            .sort((a, b) => a.get_name().localeCompare(b.get_name()));
    }
}
function buildSelectableAppList(apps, onActivated) {
    const appList = new Gtk.ListBox({
        selection_mode: Gtk.SelectionMode.NONE,
        css_classes: ["boxed-list"],
        margin_top: 8,
        margin_bottom: 8,
        margin_start: 8,
        margin_end: 8,
    });
    for (const app of apps) {
        const appId = getAppId(app);
        const row = buildAppRow(app, appId, { activatable: true });
        appList.append(row);
        row.connect("activated", () => {
            onActivated(appId);
        });
    }
    return appList;
}
// DesktopAppInfo.new is typed as non-null but returns null when the
// .desktop file is missing (e.g. the user uninstalled the app).
function tryLoadDesktopApp(appId) {
    return GioUnix.DesktopAppInfo.new(`${appId}.desktop`);
}
function buildAppRow(app, appId, extra = {}) {
    const title = app?.get_name() ?? appId;
    const row = new Adw.ActionRow({ ...extra, title, subtitle: appId });
    const icon = app?.get_icon();
    if (icon) {
        const image = Gtk.Image.new_from_gicon(icon);
        image.set_pixel_size(32);
        row.add_prefix(image);
    }
    return row;
}
function wireSearchFilter(appList, search) {
    appList.set_filter_func((row) => {
        const query = search.get_text().toLowerCase().trim();
        if (query === "")
            return true;
        const actionRow = row;
        const title = actionRow.get_title().toLowerCase();
        const subtitle = actionRow.get_subtitle()?.toLowerCase() ?? "";
        return title.includes(query) || subtitle.includes(query);
    });
    search.connect("search-changed", () => {
        appList.invalidate_filter();
    });
}
function addCloseOnEscape(window) {
    const controller = new Gtk.ShortcutController();
    controller.add_shortcut(new Gtk.Shortcut({
        trigger: Gtk.ShortcutTrigger.parse_string("Escape"),
        action: Gtk.ShortcutAction.parse_string("action(window.close)"),
    }));
    window.add_controller(controller);
}
function getAppId(app) {
    return (app.get_id() ?? "").replace(/\.desktop$/, "");
}
class PreferencesModel {
    settings;
    constructor(settings) {
        this.settings = settings;
    }
    bindDeleteTrigger(key, target, property) {
        this.settings.bind(key, target, property, Gio.SettingsBindFlags.DEFAULT);
    }
    getExcludedApps() {
        return this.settings.get_strv("excluded-apps");
    }
    addExcludedApp(appId) {
        const current = this.getExcludedApps();
        if (current.includes(appId))
            return;
        this.settings.set_strv("excluded-apps", [...current, appId]);
    }
    removeExcludedApp(appId) {
        this.settings.set_strv("excluded-apps", this.getExcludedApps().filter((id) => id !== appId));
    }
    onExcludedAppsChanged(handler) {
        return this.settings.connect("changed::excluded-apps", handler);
    }
    getLogLevelIndex() {
        return this.settings.get_enum("log-level");
    }
    setLogLevelIndex(idx) {
        this.settings.set_enum("log-level", idx);
    }
    onLogLevelChanged(handler) {
        return this.settings.connect("changed::log-level", handler);
    }
    disconnect(handlerId) {
        this.settings.disconnect(handlerId);
    }
}
