import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
import Gio from 'gi://Gio';
import Adw from 'gi://Adw';

import {
  ExtensionPreferences,
  gettext as _,
} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';

import Fields from './settingsFields.js';

export default class ClipboardHistoryPrefs extends ExtensionPreferences {
  // fillPreferencesWindow() is passed a Adw.PreferencesWindow,
  // we need to wrap our widget in a Adw.PreferencesPage and Adw.PreferencesGroup
  // ourselves.
  // It would be great to port the preferences to standard Adw widgets.
  // https://gjs.guide/extensions/development/preferences.html#prefs-js
  fillPreferencesWindow(window) {
    const settings = this.getSettings();

    const main = new Gtk.Grid({
      margin_top: 10,
      margin_bottom: 10,
      margin_start: 10,
      margin_end: 10,
      row_spacing: 12,
      column_spacing: 18,
      column_homogeneous: false,
      row_homogeneous: false,
    });
    const field_size = new Gtk.SpinButton({
      adjustment: new Gtk.Adjustment({
        lower: 1,
        upper: 100_000,
        step_increment: 100,
      }),
    });
    const window_width_percentage = new Gtk.SpinButton({
      adjustment: new Gtk.Adjustment({
        lower: 0,
        upper: 100,
        step_increment: 5,
      }),
    });
    const field_cache_size = new Gtk.SpinButton({
      adjustment: new Gtk.Adjustment({
        lower: 1,
        upper: 1024,
        step_increment: 5,
      }),
    });
    const field_topbar_preview_size = new Gtk.SpinButton({
      adjustment: new Gtk.Adjustment({
        lower: 1,
        upper: 100,
        step_increment: 10,
      }),
    });
    const field_display_mode = new Gtk.ComboBox({
      model: this._create_display_mode_options(),
    });

    const rendererText = new Gtk.CellRendererText();
    field_display_mode.pack_start(rendererText, false);
    field_display_mode.add_attribute(rendererText, 'text', 0);
    const field_disable_down_arrow = new Gtk.Switch();
    const field_cache_disable = new Gtk.Switch();
    const field_notification_toggle = new Gtk.Switch();
    const field_confirm_clear_toggle = new Gtk.Switch();
    const field_strip_text = new Gtk.Switch();
    const field_paste_on_selection = new Gtk.Switch();
    const field_process_primary_selection = new Gtk.Switch();
    const field_ignore_password_mimes = new Gtk.Switch();
    const field_enable_typeahead_search = new Gtk.Switch();
    const field_move_item_first = new Gtk.Switch();
    const field_keybinding = createKeybindingWidget(settings);
    addKeybinding(
      field_keybinding.model,
      settings,
      'toggle-menu',
      _('Toggle the menu'),
    );
    addKeybinding(
      field_keybinding.model,
      settings,
      'clear-history',
      _('Clear history'),
    );
    addKeybinding(
      field_keybinding.model,
      settings,
      'prev-entry',
      _('Previous entry'),
    );
    addKeybinding(
      field_keybinding.model,
      settings,
      'next-entry',
      _('Next entry'),
    );
    addKeybinding(
      field_keybinding.model,
      settings,
      'toggle-private-mode',
      _('Toggle private mode'),
    );

    const field_keybinding_activation = new Gtk.Switch();
    field_keybinding_activation.connect('notify::active', (widget) => {
      field_keybinding.set_sensitive(widget.active);
    });

    const sizeLabel = new Gtk.Label({
      label: _('Max number of items'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const windowWidthPercentageLabel = new Gtk.Label({
      label: _('Window width (%)'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const cacheSizeLabel = new Gtk.Label({
      label: _('Max clipboard history size (MiB)'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const cacheDisableLabel = new Gtk.Label({
      label: _('Only save favorites to disk'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const notificationLabel = new Gtk.Label({
      label: _('Show notification on copy'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const confirmClearLabel = new Gtk.Label({
      label: _('Ask for confirmation before clearing history'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const moveFirstLabel = new Gtk.Label({
      label: _('Move previously copied items to the top'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const keybindingLabel = new Gtk.Label({
      label: _('Keyboard shortcuts'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const topbarPreviewLabel = new Gtk.Label({
      label: _('Number of characters in status bar'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const displayModeLabel = new Gtk.Label({
      label: _('What to show in status bar'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const disableDownArrowLabel = new Gtk.Label({
      label: _('Remove down arrow in status bar'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const stripTextLabel = new Gtk.Label({
      label: _('Remove whitespace around text'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const pasteOnSelectionLabel = new Gtk.Label({
      label: _('Paste on selection'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const processPrimarySelection = new Gtk.Label({
      label: _('Save selected text to history'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const ignorePasswordMimes = new Gtk.Label({
      label: _('Try to avoid copying passwords (known potentially buggy)'),
      hexpand: true,
      halign: Gtk.Align.START,
    });
    const enableTypeAheadSearch = new Gtk.Label({
      label: _('Enable type-ahead search in menu'),
      hexpand: true,
      halign: Gtk.Align.START,
    });

    const addRow = ((main) => {
      let row = 0;
      return (label, input) => {
        let inputWidget = input;

        if (input instanceof Gtk.Switch) {
          inputWidget = new Gtk.Box({
            orientation: Gtk.Orientation.HORIZONTAL,
          });
          inputWidget.append(input);
        }

        if (label) {
          main.attach(label, 0, row, 1, 1);
          main.attach(inputWidget, 1, row, 1, 1);
        } else {
          main.attach(inputWidget, 0, row, 2, 1);
        }

        row++;
      };
    })(main);

    addRow(windowWidthPercentageLabel, window_width_percentage);
    addRow(sizeLabel, field_size);
    addRow(cacheSizeLabel, field_cache_size);
    addRow(cacheDisableLabel, field_cache_disable);
    addRow(moveFirstLabel, field_move_item_first);
    addRow(stripTextLabel, field_strip_text);
    addRow(pasteOnSelectionLabel, field_paste_on_selection);
    addRow(processPrimarySelection, field_process_primary_selection);
    addRow(ignorePasswordMimes, field_ignore_password_mimes);
    addRow(enableTypeAheadSearch, field_enable_typeahead_search);
    addRow(displayModeLabel, field_display_mode);
    addRow(disableDownArrowLabel, field_disable_down_arrow);
    addRow(topbarPreviewLabel, field_topbar_preview_size);
    addRow(notificationLabel, field_notification_toggle);
    addRow(confirmClearLabel, field_confirm_clear_toggle);
    addRow(keybindingLabel, field_keybinding_activation);
    addRow(null, field_keybinding);

    settings.bind(
      Fields.HISTORY_SIZE,
      field_size,
      'value',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.WINDOW_WIDTH_PERCENTAGE,
      window_width_percentage,
      'value',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.CACHE_FILE_SIZE,
      field_cache_size,
      'value',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.CACHE_ONLY_FAVORITES,
      field_cache_disable,
      'active',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.NOTIFY_ON_COPY,
      field_notification_toggle,
      'active',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.CONFIRM_ON_CLEAR,
      field_confirm_clear_toggle,
      'active',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.MOVE_ITEM_FIRST,
      field_move_item_first,
      'active',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.TOPBAR_DISPLAY_MODE_ID,
      field_display_mode,
      'active',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.DISABLE_DOWN_ARROW,
      field_disable_down_arrow,
      'active',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.TOPBAR_PREVIEW_SIZE,
      field_topbar_preview_size,
      'value',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.STRIP_TEXT,
      field_strip_text,
      'active',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.PASTE_ON_SELECTION,
      field_paste_on_selection,
      'active',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.PROCESS_PRIMARY_SELECTION,
      field_process_primary_selection,
      'active',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.IGNORE_PASSWORD_MIMES,
      field_ignore_password_mimes,
      'active',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.ENABLE_TYPEAHEAD_SEARCH,
      field_enable_typeahead_search,
      'active',
      Gio.SettingsBindFlags.DEFAULT,
    );
    settings.bind(
      Fields.ENABLE_KEYBINDING,
      field_keybinding_activation,
      'active',
      Gio.SettingsBindFlags.DEFAULT,
    );

    const group = new Adw.PreferencesGroup();
    group.add(main);

    const page = new Adw.PreferencesPage();
    page.add(group);

    window.add(page);
  }

  _create_display_mode_options() {
    const options = [
      { name: _('Icon') },
      { name: _('Clipboard contents') },
      { name: _('Both') },
      { name: _('Neither') },
    ];
    const liststore = new Gtk.ListStore();
    liststore.set_column_types([GObject.TYPE_STRING]);
    for (let i = 0; i < options.length; i++) {
      const option = options[i];
      const iter = liststore.append();
      liststore.set(iter, [0], [option.name]);
    }
    return liststore;
  }
}

//binding widgets
//////////////////////////////////
const COLUMN_ID = 0;
const COLUMN_DESCRIPTION = 1;
const COLUMN_KEY = 2;
const COLUMN_MODS = 3;

function addKeybinding(model, settings, id, description) {
  // Get the current accelerator.
  const accelerator = settings.get_strv(id)[0];
  let key, mods;
  if (accelerator == null) {
    [key, mods] = [0, 0];
  } else {
    [, key, mods] = Gtk.accelerator_parse(settings.get_strv(id)[0]);
  }

  // Add a row for the keybinding.
  const row = model.insert(100); // Erm...
  model.set(
    row,
    [COLUMN_ID, COLUMN_DESCRIPTION, COLUMN_KEY, COLUMN_MODS],
    [id, description, key, mods],
  );
}

function createKeybindingWidget(Settings) {
  const model = new Gtk.ListStore();

  model.set_column_types([
    GObject.TYPE_STRING, // COLUMN_ID
    GObject.TYPE_STRING, // COLUMN_DESCRIPTION
    GObject.TYPE_INT, // COLUMN_KEY
    GObject.TYPE_INT,
  ]); // COLUMN_MODS

  const treeView = new Gtk.TreeView();
  treeView.model = model;
  treeView.headers_visible = false;

  let column, renderer;

  // Description column.
  renderer = new Gtk.CellRendererText();

  column = new Gtk.TreeViewColumn();
  column.expand = true;
  column.pack_start(renderer, true);
  column.add_attribute(renderer, 'text', COLUMN_DESCRIPTION);

  treeView.append_column(column);

  // Key binding column.
  renderer = new Gtk.CellRendererAccel();
  renderer.accel_mode = Gtk.CellRendererAccelMode.GTK;
  renderer.editable = true;

  renderer.connect(
    'accel-edited',
    function (renderer, path, key, mods, hwCode) {
      const [ok, iter] = model.get_iter_from_string(path);
      if (!ok) {
        return;
      }

      // Update the UI.
      model.set(iter, [COLUMN_KEY, COLUMN_MODS], [key, mods]);

      // Update the stored setting.
      const id = model.get_value(iter, COLUMN_ID);
      const accelString = Gtk.accelerator_name(key, mods);
      Settings.set_strv(id, [accelString]);
    },
  );

  renderer.connect('accel-cleared', function (renderer, path) {
    const [ok, iter] = model.get_iter_from_string(path);
    if (!ok) {
      return;
    }

    // Update the UI.
    model.set(iter, [COLUMN_KEY, COLUMN_MODS], [0, 0]);

    // Update the stored setting.
    const id = model.get_value(iter, COLUMN_ID);
    Settings.set_strv(id, []);
  });

  column = new Gtk.TreeViewColumn();
  column.pack_end(renderer, false);
  column.add_attribute(renderer, 'accel-key', COLUMN_KEY);
  column.add_attribute(renderer, 'accel-mods', COLUMN_MODS);

  treeView.append_column(column);

  return treeView;
}
