import St from "gi://St";
import Clutter from "gi://Clutter";
import GLib from "gi://GLib";

const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD;
const PRIMARY_CLIPBOARD_TYPE = St.ClipboardType.PRIMARY; // For pastes into terminal emulators.

const VirtualKeyboard = (() => {
  let VirtualKeyboard;
  return () => {
    if (!VirtualKeyboard) {
      VirtualKeyboard = Clutter.get_default_backend()
        .get_default_seat()
        .create_virtual_device(Clutter.InputDeviceType.KEYBOARD_DEVICE);
    }
    return VirtualKeyboard;
  };
})();

const GENDERS = ["", "\u200D\u2640\uFE0F", "\u200D\u2642\uFE0F"];
const GENDERS2 = ["🧑", "👩", "👨"];
const TONES = ["", "🏻", "🏼", "🏽", "🏾", "🏿"];

export class EmojiButton {
  constructor(emojiCopy, baseCharacter, keywords) {
    this.baseCharacter = baseCharacter;
    this.emojiCopy = emojiCopy;
    this._settings = this.emojiCopy._settings;
    this.clipboard = St.Clipboard.get_default();

    let tonable = false;
    let genrable = false;
    let gendered = false;
    for (let j = 0; j < keywords.length; j++) {
      if (keywords[j] == "HAS_TONE") {
        tonable = true;
      } else if (keywords[j] == "HAS_GENDER") {
        genrable = true;
      } else if (keywords[j] == "IS_GENDERED") {
        gendered = true;
      }
    }
    this.tags = [tonable, genrable, gendered];
    this.keywords = keywords;

    // Tooltip label (shared for all EmojiButtons)
    if (!EmojiButton.tooltipLabel) {
      EmojiButton.tooltipLabel = new St.Label({
        style_class: 'emoji-tooltip',
        visible: false,
        opacity: 230,
      });
      // Add to the main UI group
      global.stage.add_child(EmojiButton.tooltipLabel);
    }
  }

  _cleanKeywords() {
    return this.keywords
      .replaceAll("HAS_TONE", "")
      .replaceAll("HAS_GENDER", "")
      .replaceAll("IS_GENDERED", "")
      .replaceAll("OK", "")
      .trim();
  }

  build(category) {
    this.super_btn = new St.Button({
      style_class: "EmojisItemStyle",
      can_focus: true,
      label: this.baseCharacter,
    });

    // Copy the emoji to the clipboard with adequate tags and behavior
    this.super_btn.connect("button-press-event", this.onButtonPress.bind(this));
    this.super_btn.connect("key-press-event", this.onKeyPress.bind(this));

    if (category == null || this.keywords.length === 0) {
      return;
    }

    // Update the category label on hover, allowing the user to know the
    // name of the emoji he's copying.
    this.super_btn.connect("notify::hover", (actor, _) => {
      // Show the emoji name in the category label
      if (actor.hover) {
        const cleanKeywords = this._cleanKeywords();
        category.super_item.label.text = cleanKeywords;

        // Starts a timer to show the tooltip
        if (this._tooltipTimeoutId) {
          GLib.source_remove(this._tooltipTimeoutId);
          this._tooltipTimeoutId = null;
        }
        this._tooltipTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
          const [x, y] = global.get_pointer();
          const clean = this._cleanKeywords();
          const words = clean.split(" ");
          EmojiButton.tooltipLabel.text = `${words[0] || ''} ${words[1] || ''}`.trim();
          EmojiButton.tooltipLabel.set_position(x + 16, y - 8);
          EmojiButton.tooltipLabel.visible = true;
          this._tooltipTimeoutId = null;
          return GLib.SOURCE_REMOVE;
        });
      } else {
        // Set back the category label
        category.super_item.label.text = category.categoryName;
        EmojiButton.tooltipLabel.visible = false;
        // Cancel the tooltip timer if mouse leaves early
        if (this._tooltipTimeoutId) {
          // Remove the timeout source
          GLib.source_remove(this._tooltipTimeoutId);
          this._tooltipTimeoutId = null;
        }
      }
    });

    // Move tooltip with mouse
    this.super_btn.connect('motion-event', (_, event) => {
      if (EmojiButton.tooltipLabel.visible) {
        const [x, y] = event.get_coords();
        EmojiButton.tooltipLabel.set_position(x + 16, y - 8);
      }
      return Clutter.EVENT_PROPAGATE;
    });
  }

  destroy() {
    if (this._pasteHackCallbackId) {
      GLib.source_remove(this._pasteHackCallbackId);
      this._pasteHackCallbackId = null;
    }
    if (this._tooltipTimeoutId) {
      GLib.source_remove(this._tooltipTimeoutId);
      this._tooltipTimeoutId = null;
    }
    this.super_btn.destroy();
  }

  updateStyle(forcedStyle) {
    const fontStyle = forcedStyle || 
      `font-size: ${this._settings.get_int("emojisize")}px; color: #FFFFFF;`;
    this.super_btn.style = fontStyle;
  }

  onKeyPress(_, e) {
    const symbol = e.get_key_symbol();
    // Main return key (GS > 3.35)     Main return key (GS < 3.35)           Numpad return key
    if (
      symbol == Clutter.KEY_Return || symbol == Clutter.Return ||
      symbol == Clutter.KP_Enter
    ) {
      const emojiToCopy = this.getTaggedEmoji();
      this.emojiCopy.sqlite.increment_selection(emojiToCopy);
      const state = typeof e.get_state === 'function' ? e.get_state() : 0;
      const majPressed = (state & Clutter.ModifierType.SHIFT_MASK) !== 0;
      const ctrlPressed = (state & Clutter.ModifierType.CONTROL_MASK) !== 0;
      if (majPressed) {
        return this.addToClipboardAndStay(emojiToCopy);
      } else if (ctrlPressed) {
        return this.replaceClipboardAndStay(emojiToCopy);
      } else {
        return this.replaceClipboardAndClose(emojiToCopy);
      }
    }
    return Clutter.EVENT_PROPAGATE;
  }

  /*
   * This method is called at each click and copies the emoji to the clipboard.
   * The exact behavior of the method depends on the mouse button used:
   * - left click overwrites clipboard content with the emoji and closes the menu;
   * - middle click too, but does not close the menu;
   * - right click adds the emoji at the end of the current clipboard content (and does not close the menu).
   */
  onButtonPress(_, event) {
    const mouseButton = event.get_button();
    const emojiToCopy = this.getTaggedEmoji();
    if (emojiToCopy == null) {
      return Clutter.EVENT_PROPAGATE;
    }
    this.emojiCopy.sqlite.increment_selection(emojiToCopy);

    if (mouseButton == 1) {
      return this.replaceClipboardAndClose(emojiToCopy);
    } else if (mouseButton == 2) {
      return this.replaceClipboardAndStay(emojiToCopy);
    } else if (mouseButton == 3) {
      return this.addToClipboardAndStay(emojiToCopy);
    }
    return Clutter.EVENT_PROPAGATE;
  }

  replaceClipboardAndClose(emojiToCopy) {
    this.clipboard.set_text(
      CLIPBOARD_TYPE,
      emojiToCopy,
    );
    this.clipboard.set_text(
      PRIMARY_CLIPBOARD_TYPE,
      emojiToCopy,
    );
    this.emojiCopy.get_super_btn().menu.close();

    if (this._settings.get_boolean("paste-on-select")) {
      this.triggerPasteHack();
    }

    return Clutter.EVENT_STOP;
  }

  replaceClipboardAndStay(emojiToCopy) {
    this.clipboard.set_text(
      CLIPBOARD_TYPE,
      emojiToCopy,
    );
    this.clipboard.set_text(
      PRIMARY_CLIPBOARD_TYPE,
      emojiToCopy,
    );

    return Clutter.EVENT_STOP;
  }

  addToClipboardAndStay(emojiToCopy) {
    this.clipboard.get_text(CLIPBOARD_TYPE, (_, text) => {
      this.clipboard.set_text(
        CLIPBOARD_TYPE,
        text + emojiToCopy,
      );
      this.clipboard.set_text(
        PRIMARY_CLIPBOARD_TYPE,
        text + emojiToCopy,
      );
    });

    return Clutter.EVENT_STOP;
  }

  /*
   * This returns an emoji corresponding to .super_btn.label with tags applied
   * to it. `tags` is an array of 3 booleans, which describe how a composite
   * emoji is built:
   * - tonable -> return emoji concatenated with the selected skin tone;
   * - genrable -> return emoji concatenated with the selected gender;
   * - gendered -> the emoji is already gendered, which modifies the way skin
   *   tone is applied ([man|woman] + [skin tone if any] + [other symbol(s)]).
   * If all tags are false, it returns unmodified .super_btn.label
   */
  getTaggedEmoji() {
    const currentEmoji = this.super_btn.label;
    if (currentEmoji === "") return;

    const [tonable, genrable, gendered] = this.tags;
    let temp = currentEmoji;
    
    if (tonable) {
      const toneIndex = this._settings.get_int("skin-tone");
      if (gendered) {
        const genderIndex = this._settings.get_int("gender");
        temp = currentEmoji.replace(
          GENDERS2[0],
          GENDERS2[genderIndex] + TONES[toneIndex],
        );
      } else {
        temp += TONES[toneIndex];
      }
    }
    
    if (genrable) {
      temp += GENDERS[this._settings.get_int("gender")];
    }
    
    this.emojiCopy.searchItem.shiftFor(temp);
    return temp;
  }

  // PR #189 from khaled-0 at maoschanz/emoji-selector-for-gnome
  // Originally from "clipboard-histroy@alexsaveau.dev"
  triggerPasteHack() {
    this._pasteHackCallbackId = GLib.timeout_add(
      GLib.PRIORITY_DEFAULT,
      1,
      () => {
        const KEY_Shift_L = 42;
        const KEY_Insert = 110;

        const eventTime = Clutter.get_current_event_time() * 1000;
        VirtualKeyboard().notify_key(
          eventTime,
          KEY_Shift_L,
          Clutter.KeyState.PRESSED,
        );
        VirtualKeyboard().notify_key(
          eventTime,
          KEY_Insert,
          Clutter.KeyState.PRESSED,
        );
        VirtualKeyboard().notify_key(
          eventTime,
          KEY_Insert,
          Clutter.KeyState.RELEASED,
        );
        VirtualKeyboard().notify_key(
          eventTime,
          KEY_Shift_L,
          Clutter.KeyState.RELEASED,
        );

        this._pasteHackCallbackId = undefined;
        return GLib.SOURCE_REMOVE;
      },
    );
  }
}
