"use strict";

import {Controller} from "@hotwired/stimulus";

/**
 * Composer mention picker for the activity/comment thread.
 *
 * Detects an "@query" in the textarea (or the @ toolbar button), shows a picker
 * of the available audiences (passed in as the `audiences` value), and on pick
 * inserts a token and appends the resolved descriptor to the hidden
 * `notifyTargetsJson` field. The notify chips themselves are rendered
 * server-side from that field, so we never fight the Live Component morph.
 */
export default class extends Controller {
  static values = { audiences: Object };
  static targets = ["input", "targets", "picker"];

  connect() {
    this.mentionStart = null;
    this.onDocClick = this.onDocClick.bind(this);
    document.addEventListener("mousedown", this.onDocClick);
  }

  disconnect() {
    document.removeEventListener("mousedown", this.onDocClick);
  }

  get selected() {
    try {
      const parsed = JSON.parse(this.targetsTarget.value || "[]");
      return Array.isArray(parsed) ? parsed : [];
    } catch (e) {
      return [];
    }
  }

  onInput() {
    const query = this.detectQuery();
    if (query === null) {
      this.closePicker();
      return;
    }
    this.mentionStart = query.start;
    this.renderPicker(query.query);
  }

  onKeydown(event) {
    if (event.key === "Escape" && this.isOpen()) {
      event.preventDefault();
      this.closePicker();
    }
  }

  openPicker(event) {
    if (event) {
      event.preventDefault();
    }
    const ta = this.inputTarget;
    ta.focus();
    const caret = ta.selectionStart ?? ta.value.length;
    const before = ta.value.slice(0, caret);
    if (!/(^|\s)@$/.test(before)) {
      this.insertAtCaret(before && !/\s$/.test(before) ? " @" : "@");
    }
    this.mentionStart = this.inputTarget.value.lastIndexOf("@");
    this.renderPicker("");
  }

  pick(item) {
    const ta = this.inputTarget;
    const start = this.mentionStart ?? ta.value.lastIndexOf("@");
    const caret = ta.selectionStart ?? ta.value.length;
    const label = "@" + (item.label || "");
    const before = ta.value.slice(0, start);
    const after = ta.value.slice(caret).replace(/^\s/, "");
    ta.value = `${before}${label} ${after}`;
    this.fire(ta);

    const selected = this.selected;
    const exists = selected.some(
      (s) => s.type === item.type && s.g === item.g && (s.id ?? null) === (item.id ?? null)
    );
    if (!exists) {
      selected.push({ type: item.type, g: item.g, id: item.id ?? null, label: item.label });
    }
    this.commit(selected);
    this.closePicker();
    ta.focus();
  }

  removeTarget(event) {
    const index = Number(event.params.index);
    const selected = this.selected;
    if (Number.isInteger(index) && index >= 0 && index < selected.length) {
      selected.splice(index, 1);
      this.commit(selected);
    }
  }

  detectQuery() {
    const ta = this.inputTarget;
    const caret = ta.selectionStart ?? ta.value.length;
    const text = ta.value;
    let i = caret - 1;
    while (i >= 0 && !/\s/.test(text[i])) {
      if (text[i] === "@") {
        return { start: i, query: text.slice(i + 1, caret) };
      }
      i--;
    }
    return null;
  }

  renderPicker(query) {
    const q = (query || "").toLowerCase();
    const audiences = this.audiencesValue || {};
    const groups = (audiences.groups || []).filter((g) => (g.label || "").toLowerCase().includes(q));
    const people = (audiences.people || []).filter((p) => (p.label || "").toLowerCase().includes(q));
    const rows = [...groups, ...people];

    if (rows.length === 0) {
      this.pickerTarget.innerHTML = `<div class="px-3 py-2 text-xs text-gray-400">Keine Treffer</div>`;
    } else {
      const colors = { host: "#2563eb", account: "#15803d", admin: "#a855f7" };
      this.pickerTarget.innerHTML =
        `<div class="py-1">` +
        rows
          .map((r, i) => {
            const kind = r.type === "group" ? (r.internal ? "intern" : "Gruppe") : r.sub || "";
            const bg = colors[r.g] || "#6b7280";
            return `<button type="button" data-i="${i}" class="cm-item flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm hover:bg-gray-50 dark:hover:bg-gray-900">
              <span class="inline-flex size-5 items-center justify-center rounded-full text-[10px] font-bold text-white" style="background:${bg}">${this.esc((r.label || "?").slice(0, 1).toUpperCase())}</span>
              <span class="min-w-0 flex-1 truncate"><span class="font-medium text-gray-900 dark:text-white">${this.esc(r.label || "")}</span> <span class="text-gray-400">${this.esc(r.sub || "")}</span></span>
              <span class="text-[10px] text-gray-400">${this.esc(kind)}</span>
            </button>`;
          })
          .join("") +
        `</div>`;

      this.pickerTarget.querySelectorAll(".cm-item").forEach((btn) => {
        btn.addEventListener("mousedown", (ev) => {
          ev.preventDefault();
          this.pick(rows[Number(btn.dataset.i)]);
        });
      });
    }

    this.pickerTarget.classList.remove("hidden");
  }

  isOpen() {
    return this.hasPickerTarget && !this.pickerTarget.classList.contains("hidden");
  }

  closePicker() {
    if (!this.hasPickerTarget) {
      return;
    }
    this.pickerTarget.classList.add("hidden");
    this.pickerTarget.innerHTML = "";
    this.mentionStart = null;
  }

  onDocClick(event) {
    if (this.isOpen() && !this.element.contains(event.target)) {
      this.closePicker();
    }
  }

  commit(selected) {
    this.targetsTarget.value = JSON.stringify(selected);
    this.fire(this.targetsTarget);
  }

  insertAtCaret(text) {
    const ta = this.inputTarget;
    const start = ta.selectionStart ?? ta.value.length;
    ta.value = ta.value.slice(0, start) + text + ta.value.slice(start);
    ta.selectionStart = ta.selectionEnd = start + text.length;
    this.fire(ta);
  }

  fire(el) {
    el.dispatchEvent(new Event("input", { bubbles: true }));
  }

  esc(value) {
    return String(value).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]));
  }
}
