/**
 * StatusPicker — compact dropdown for selecting user online status.
 * Shows a colored status dot; clicking it opens a floating menu
 * with all status options. Intended for use in the UserBar.
 */

import { createElement, appendChildren } from "@lib/dom";
import { createIcon } from "@lib/icons";
import type { MountableComponent } from "@lib/safe-render";
import type { UserStatus } from "@lib/types";
import { MAX_CUSTOM_STATUS_LEN } from "@lib/userStatus";

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

export interface StatusPickerOptions {
  readonly currentStatus: UserStatus;
  readonly onStatusChange: (status: UserStatus) => void;
  /** The custom status line to pre-fill the input with. */
  readonly currentCustomStatus?: string;
  /** Called when the user commits a custom status (Enter or blur). Passing an
   *  empty string means "clear it". Omitted = the input is not rendered. */
  readonly onCustomStatusChange?: (text: string) => void;
}

export type StatusPickerComponent = MountableComponent & {
  /** Update the displayed status without recreating the picker. */
  setStatus(status: UserStatus): void;
  /** Update the custom status input without recreating the picker. */
  setCustomStatus(text: string): void;
};

// ---------------------------------------------------------------------------
// Status definitions
// ---------------------------------------------------------------------------

interface StatusDef {
  readonly value: UserStatus;
  readonly label: string;
  readonly color: string;
}

/**
 * "invisible" is its own value now, not "offline" wearing a different label.
 * The server stores it as chosen and shows everyone else offline, so the
 * picker can finally send what it means — and the status survives a reconnect
 * instead of flashing back to online.
 */
const STATUS_DEFS: readonly StatusDef[] = [
  { value: "online", label: "Online", color: "#3ba55d" },
  { value: "idle", label: "Idle", color: "#faa61a" },
  { value: "dnd", label: "Do Not Disturb", color: "#ed4245" },
  { value: "invisible", label: "Invisible", color: "#747f8d" },
];

function colorForStatus(status: UserStatus): string {
  return STATUS_DEFS.find((d) => d.value === status)?.color ?? "#747f8d";
}

// ---------------------------------------------------------------------------
// Component factory
// ---------------------------------------------------------------------------

export function createStatusPicker(options: StatusPickerOptions): StatusPickerComponent {
  const ac = new AbortController();
  const { signal } = ac;

  let currentStatus: UserStatus = options.currentStatus;
  let root: HTMLDivElement | null = null;
  let dotEl: HTMLDivElement | null = null;
  let dropdownEl: HTMLDivElement | null = null;
  let checkEls = new Map<UserStatus, HTMLSpanElement>();
  let customInputEl: HTMLInputElement | null = null;
  /** Last text handed to the callback. Guards the blur-after-Enter double
   *  send, which would otherwise cost a second presence_update against the
   *  server's one-per-ten-seconds limit. */
  let lastCommittedCustom = options.currentCustomStatus ?? "";

  // ---- Dropdown visibility --------------------------------------------------

  function isOpen(): boolean {
    return dropdownEl?.classList.contains("status-picker-dropdown--open") === true;
  }

  function openDropdown(): void {
    dropdownEl?.classList.add("status-picker-dropdown--open");
    dotEl?.setAttribute("aria-expanded", "true");
  }

  function closeDropdown(): void {
    dropdownEl?.classList.remove("status-picker-dropdown--open");
    dotEl?.setAttribute("aria-expanded", "false");
  }

  function toggleDropdown(): void {
    if (isOpen()) {
      closeDropdown();
    } else {
      openDropdown();
    }
  }

  // ---- Status update --------------------------------------------------------

  function applyStatus(status: UserStatus): void {
    currentStatus = status;

    // Update trigger dot color
    if (dotEl !== null) {
      dotEl.style.background = colorForStatus(status);
    }

    // Update checkmarks
    for (const [value, el] of checkEls) {
      el.style.display = value === status ? "" : "none";
    }
  }

  // ---- Build DOM ------------------------------------------------------------

  function buildOption(def: StatusDef): HTMLDivElement {
    const row = createElement("div", {
      class: "status-picker-option",
      role: "menuitem",
      tabindex: "0",
    });

    const optDot = createElement("span", { class: "status-picker-option-dot" });
    optDot.style.background = def.color;

    const label = createElement("span", { class: "status-picker-option-label" }, def.label);

    const check = createElement("span", { class: "status-picker-option-check" });
    check.style.display = def.value === currentStatus ? "" : "none";
    const checkIcon = createIcon("check", 16);
    check.appendChild(checkIcon);
    checkEls.set(def.value, check);

    appendChildren(row, optDot, label, check);

    row.addEventListener(
      "click",
      () => {
        applyStatus(def.value);
        closeDropdown();
        options.onStatusChange(def.value);
      },
      { signal },
    );

    row.addEventListener(
      "keydown",
      (e: KeyboardEvent) => {
        if (e.key === "Enter" || e.key === " ") {
          e.preventDefault();
          row.click();
        }
      },
      { signal },
    );

    return row;
  }

  /**
   * The "Set a custom status" row. Only built when a handler was supplied —
   * an input whose value goes nowhere is worse than no input.
   */
  function buildCustomStatusRow(onChange: (text: string) => void): HTMLDivElement {
    const row = createElement("div", { class: "status-picker-custom" });
    const input = createElement("input", {
      class: "status-picker-custom-input",
      type: "text",
      placeholder: "Set a custom status",
      maxlength: String(MAX_CUSTOM_STATUS_LEN),
      "aria-label": "Custom status",
      "data-testid": "custom-status-input",
    });
    input.value = options.currentCustomStatus ?? "";
    customInputEl = input;

    const commit = (): void => {
      const text = input.value.trim().slice(0, MAX_CUSTOM_STATUS_LEN);
      if (text === lastCommittedCustom) return;
      lastCommittedCustom = text;
      input.value = text;
      onChange(text);
    };

    input.addEventListener(
      "keydown",
      (e: KeyboardEvent) => {
        // Keystrokes inside the input must not reach the dropdown's own
        // Enter/Escape handling, which would close the menu mid-edit.
        e.stopPropagation();
        if (e.key === "Enter") {
          e.preventDefault();
          commit();
          closeDropdown();
        } else if (e.key === "Escape") {
          e.preventDefault();
          input.value = lastCommittedCustom;
          closeDropdown();
        }
      },
      { signal },
    );
    input.addEventListener("blur", commit, { signal });
    // The row is inside the dropdown; clicking the input must not be treated
    // as picking a status or as an outside click.
    input.addEventListener("click", (e: MouseEvent) => e.stopPropagation(), { signal });

    row.appendChild(input);
    return row;
  }

  // ---- MountableComponent ---------------------------------------------------

  function mount(container: Element): void {
    root = createElement("div", { class: "status-picker" });

    // Trigger dot
    dotEl = createElement("div", {
      class: "status-picker-dot",
      role: "button",
      tabindex: "0",
      "aria-label": "Change status",
      "aria-haspopup": "true",
      "aria-expanded": "false",
    });
    dotEl.style.background = colorForStatus(currentStatus);
    dotEl.addEventListener(
      "click",
      (e: MouseEvent) => {
        e.stopPropagation();
        toggleDropdown();
      },
      { signal },
    );
    dotEl.addEventListener(
      "keydown",
      (e: KeyboardEvent) => {
        if (e.key === "Enter" || e.key === " ") {
          e.preventDefault();
          toggleDropdown();
        }
      },
      { signal },
    );

    // Dropdown menu
    dropdownEl = createElement("div", {
      class: "status-picker-dropdown",
      role: "menu",
    });
    for (const def of STATUS_DEFS) {
      dropdownEl.appendChild(buildOption(def));
    }
    const onCustomStatusChange = options.onCustomStatusChange;
    if (onCustomStatusChange !== undefined) {
      dropdownEl.appendChild(createElement("div", { class: "status-picker-divider" }));
      dropdownEl.appendChild(buildCustomStatusRow(onCustomStatusChange));
    }

    appendChildren(root, dotEl, dropdownEl);
    container.appendChild(root);

    // Close on outside click
    document.addEventListener(
      "click",
      (e: MouseEvent) => {
        if (isOpen() && root !== null && !root.contains(e.target as Node)) {
          closeDropdown();
        }
      },
      { signal },
    );

    // Close on Escape
    document.addEventListener(
      "keydown",
      (e: KeyboardEvent) => {
        if (e.key === "Escape" && isOpen()) {
          closeDropdown();
          dotEl?.focus();
        }
      },
      { signal },
    );
  }

  function destroy(): void {
    ac.abort();
    checkEls = new Map();
    root?.remove();
    root = null;
    dotEl = null;
    dropdownEl = null;
    customInputEl = null;
  }

  function setStatus(status: UserStatus): void {
    applyStatus(status);
  }

  function setCustomStatus(text: string): void {
    lastCommittedCustom = text;
    if (customInputEl !== null) customInputEl.value = text;
  }

  return { mount, destroy, setStatus, setCustomStatus };
}
