import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Check, ListTodo, Palette, Plus, Send, Tag, X } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { useVisualViewport } from "@/hooks/use-visual-viewport";
import {
  type ChecklistItem,
  type NoteKind,
  type NoteLabel,
  NOTE_COLORS,
  colorAccent,
  colorBg,
  useCreateLabel,
  useCreateNote,
} from "@/lib/notes-api";
import { type FolderSelection } from "@/components/notes/folders-rail";

export function newItemId(): string {
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
    return crypto.randomUUID();
  }
  return `it_${Math.random().toString(36).slice(2)}_${Date.now().toString(36)}`;
}

export function ChecklistEditor({
  items,
  onChange,
  testIdPrefix,
}: {
  items: ChecklistItem[];
  onChange: (next: ChecklistItem[]) => void;
  testIdPrefix: string;
}) {
  const { t } = useTranslation();
  const inputRefs = useRef<Map<string, HTMLInputElement | null>>(new Map());
  const focusItemIdRef = useRef<string | null>(null);

  useEffect(() => {
    const id = focusItemIdRef.current;
    if (!id) return;
    const el = inputRefs.current.get(id);
    if (el) {
      el.focus();
      focusItemIdRef.current = null;
    }
  }, [items]);

  const setItem = (id: string, patch: Partial<ChecklistItem>) => {
    onChange(items.map((it) => (it.id === id ? { ...it, ...patch } : it)));
  };
  const removeItem = (id: string) => {
    onChange(items.filter((it) => it.id !== id));
  };
  const appendItem = () => {
    const item: ChecklistItem = { id: newItemId(), text: "", done: false };
    focusItemIdRef.current = item.id;
    onChange([...items, item]);
  };

  return (
    <div className="space-y-1.5" data-testid={`${testIdPrefix}-list`}>
      {items.length === 0 && (
        <div className="text-xs text-muted-foreground italic px-1">
          {t("notes.checklist.emptyHint", "No items yet — add one below.")}
        </div>
      )}
      {items.map((it) => (
        <div
          key={it.id}
          className="flex items-center gap-2"
          data-testid={`${testIdPrefix}-item-${it.id}`}
        >
          <input
            type="checkbox"
            checked={it.done}
            onChange={(e) => setItem(it.id, { done: e.target.checked })}
            className="h-4 w-4 rounded border-black/30 cursor-pointer accent-amber-500"
            aria-label={t("notes.checklist.itemPlaceholder", "List item")}
            data-testid={`${testIdPrefix}-check-${it.id}`}
          />
          <Input
            ref={(el) => {
              if (el) inputRefs.current.set(it.id, el);
              else inputRefs.current.delete(it.id);
            }}
            value={it.text}
            onChange={(e) => setItem(it.id, { text: e.target.value })}
            onKeyDown={(e) => {
              if (e.key === "Enter") {
                e.preventDefault();
                appendItem();
              } else if (
                e.key === "Backspace" &&
                it.text.length === 0 &&
                items.length > 1
              ) {
                e.preventDefault();
                removeItem(it.id);
              }
            }}
            placeholder={t("notes.checklist.itemPlaceholder", "List item")}
            className={`h-8 border-0 bg-transparent shadow-none focus-visible:ring-0 px-1 ${
              it.done ? "line-through decoration-foreground/60 text-foreground/55" : "text-foreground"
            }`}
            data-testid={`${testIdPrefix}-text-${it.id}`}
          />
          <button
            type="button"
            onClick={() => removeItem(it.id)}
            className="p-1 rounded text-muted-foreground hover:text-destructive"
            aria-label={t("notes.checklist.removeItem", "Remove item")}
            data-testid={`${testIdPrefix}-remove-${it.id}`}
          >
            <X size={14} />
          </button>
        </div>
      ))}
      <button
        type="button"
        onClick={appendItem}
        className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground px-1 py-1"
        data-testid={`${testIdPrefix}-add`}
      >
        <Plus size={12} />
        {t("notes.checklist.addItem", "Add item")}
      </button>
    </div>
  );
}

// Re-export colorAccent so callers (e.g. ChecklistView in notes.tsx)
// can access it via this module without bypassing it through notes-api.
export { colorAccent };

export function KindToggle({
  kind,
  onChange,
  testId,
}: {
  kind: NoteKind;
  onChange: (k: NoteKind) => void;
  testId?: string;
}) {
  const { t } = useTranslation();
  const next: NoteKind = kind === "text" ? "checklist" : "text";
  return (
    <button
      type="button"
      onClick={() => onChange(next)}
      data-testid={testId}
      aria-pressed={kind === "checklist"}
      aria-label={t("notes.checklist.toggle", "To-do list")}
      title={t("notes.checklist.toggle", "To-do list")}
      className={`p-1 rounded-md hover:bg-black/5 ${
        kind === "checklist"
          ? "text-amber-600 bg-amber-100/60"
          : "text-muted-foreground"
      }`}
    >
      <ListTodo size={15} />
    </button>
  );
}

export function ColorPicker({
  value,
  onChange,
}: {
  value: string;
  onChange: (c: string) => void;
}) {
  return (
    <Popover>
      <PopoverTrigger asChild>
        <button
          className="p-1 rounded-md hover:bg-black/5 text-muted-foreground"
          aria-label="Color"
        >
          <Palette size={15} />
        </button>
      </PopoverTrigger>
      <PopoverContent className="w-auto p-2">
        <div className="grid grid-cols-5 gap-1.5">
          {NOTE_COLORS.map((c) => (
            <button
              key={c.id}
              onClick={() => onChange(c.id)}
              className={`w-7 h-7 rounded-full border border-black/10 ${c.bg} ${
                value === c.id ? "ring-2 ring-offset-1 " + c.ring : ""
              }`}
              aria-label={c.id}
            />
          ))}
        </div>
      </PopoverContent>
    </Popover>
  );
}

export function LabelMenu({
  labels,
  selected,
  onChange,
}: {
  labels: NoteLabel[];
  selected: number[];
  onChange: (ids: number[]) => void;
}) {
  const { t } = useTranslation();
  const create = useCreateLabel();
  const [name, setName] = useState("");

  const toggle = (id: number) => {
    if (selected.includes(id)) onChange(selected.filter((x) => x !== id));
    else onChange([...selected, id]);
  };

  return (
    <Popover>
      <PopoverTrigger asChild>
        <button
          className="p-1 rounded-md hover:bg-black/5 text-muted-foreground"
          aria-label="Labels"
        >
          <Tag size={15} />
        </button>
      </PopoverTrigger>
      <PopoverContent className="w-56 p-2">
        <div className="space-y-1 max-h-48 overflow-y-auto">
          {labels.length === 0 && (
            <div className="text-xs text-muted-foreground px-2 py-1">
              {t("notes.noLabelsYet", "No labels yet")}
            </div>
          )}
          {labels.map((l) => (
            <button
              key={l.id}
              onClick={() => toggle(l.id)}
              className="w-full flex items-center justify-between px-2 py-1 text-sm rounded hover:bg-slate-100"
            >
              <span className="flex items-center gap-2">
                <Tag size={12} /> {l.name}
              </span>
              {selected.includes(l.id) && <Check size={14} />}
            </button>
          ))}
        </div>
        <div className="mt-2 flex gap-1 border-t pt-2">
          <Input
            value={name}
            onChange={(e) => setName(e.target.value)}
            placeholder={t("notes.newLabel", "New label")}
            className="h-8 text-sm"
            onKeyDown={(e) => {
              if (e.key === "Enter" && name.trim()) {
                create.mutate(name.trim(), { onSuccess: () => setName("") });
              }
            }}
          />
          <Button
            size="sm"
            variant="ghost"
            disabled={!name.trim()}
            onClick={() =>
              create.mutate(name.trim(), { onSuccess: () => setName("") })
            }
          >
            <Plus size={14} />
          </Button>
        </div>
      </PopoverContent>
    </Popover>
  );
}

export function Composer({
  labels,
  folderSelection,
  onSend,
  hideSend = false,
  openTrigger = 0,
  onClose,
}: {
  labels: NoteLabel[];
  folderSelection: FolderSelection;
  onSend: (id: number) => void;
  // shared-folder editors create notes that get stamped to
  // the folder owner; the owner-only `/notes/:id/send` route would 403
  // for them, so the composer hides its Send button in that context.
  hideSend?: boolean;
  // Numeric trigger bumped by the page when an external action (e.g.
  // the meetings quick-note tab) wants the composer expanded and
  // focused. Each new value opens + focuses; the count itself is
  // irrelevant — only the change matters.
  openTrigger?: number;
  // Optional: fired whenever the composer transitions back to its
  // collapsed state (save / outside-click auto-save / send success /
  // empty reset). Used by hosts (e.g. /executive-meetings) that wrap
  // the composer in a dismissable panel so they can drop their
  // "panel open" flag and restore the original entrypoint (yellow
  // side tab). /notes doesn't pass this — its composer stays mounted
  // and just collapses to "+ Take a note...".
  onClose?: () => void;
}) {
  const { t } = useTranslation();
  const [open, setOpen] = useState(false);
  const [content, setContent] = useState("");
  const [color, setColor] = useState("yellow");
  const [labelIds, setLabelIds] = useState<number[]>([]);
  const [kind, setKind] = useState<NoteKind>("text");
  const [items, setItems] = useState<ChecklistItem[]>([]);
  const create = useCreateNote();
  // Resolve the folder the new note should land in based on the active
  // selection. "all" → null (unfiled / global), "unfiled" → null,
  // "folder" → that folder id. Without this the composer always created
  // notes in Unfiled, so a note made from inside a folder vanished from
  // the current view.
  const targetFolderId =
    folderSelection.kind === "folder" ? folderSelection.id : null;
  const ref = useRef<HTMLDivElement>(null);
  const contentRef = useRef<HTMLTextAreaElement | null>(null);
  const contentFocused = useRef(false);
  const { keyboardInset } = useVisualViewport();
  // Same keyboard-aware scroll-into-view as the thread reply box: only
  // fires while a software keyboard is actually open and the composer
  // textarea is focused. Desktop focus is unaffected.
  useEffect(() => {
    if (keyboardInset <= 80) return;
    if (!contentFocused.current) return;
    contentRef.current?.scrollIntoView({ block: "center" });
  }, [keyboardInset]);

  useEffect(() => {
    if (!open) return;
    const onClick = (e: MouseEvent) => {
      const target = e.target as Node | null;
      if (ref.current?.contains(target)) return;
      // Ignore clicks inside Radix popover/portal content (the color and
      // label menus render in a portal outside the composer's DOM tree).
      // Without this guard, picking a swatch would trigger save() on the
      // mousedown — before the swatch's onClick updated `color` — so the
      // new color was never persisted.
      const el = target instanceof Element ? target : null;
      if (
        el?.closest(
          "[data-radix-popper-content-wrapper],[data-radix-portal],[role='dialog']",
        )
      ) {
        return;
      }
      save();
    };
    document.addEventListener("mousedown", onClick);
    return () => document.removeEventListener("mousedown", onClick);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, content, color, labelIds, kind, items]);

  const reset = () => {
    setContent("");
    setColor("yellow");
    setLabelIds([]);
    setKind("text");
    setItems([]);
    setOpen(false);
    onClose?.();
  };

  // External open trigger. React both to subsequent bumps AND to the
  // initial mount when the trigger is already > 0 — this covers the
  // case where the page resets folder selection and the Composer
  // mounts fresh with a non-zero trigger.
  const lastOpenTrigger = useRef(0);
  useEffect(() => {
    if (openTrigger === 0) return;
    if (openTrigger === lastOpenTrigger.current) return;
    lastOpenTrigger.current = openTrigger;
    setOpen(true);
    setKind("text");
    requestAnimationFrame(() => {
      const el = contentRef.current;
      if (!el) return;
      el.focus();
      el.scrollIntoView({ block: "center", behavior: "smooth" });
    });
  }, [openTrigger]);

  // Build the create-note payload from current composer state and a
  // boolean indicating whether the composer is empty (no body /
  // checklist items). Shared by save() and sendNew() so the
  // "save before send" path uses the exact same data the bare Save
  // button would have submitted.
  const buildPayload = () => {
    const cleanItems =
      kind === "checklist"
        ? items
            .map((it) => ({ ...it, text: it.text.trim() }))
            .filter((it) => it.text.length > 0)
        : [];
    const isEmpty =
      kind === "checklist" ? cleanItems.length === 0 : !content.trim();
    return {
      isEmpty,
      payload: {
        content: kind === "checklist" ? "" : content.trim(),
        color,
        labelIds,
        kind,
        items: kind === "checklist" ? cleanItems : null,
        folderId: targetFolderId,
      },
    };
  };

  const save = () => {
    const { isEmpty, payload } = buildPayload();
    if (isEmpty) {
      reset();
      return;
    }
    create.mutate(payload, { onSuccess: reset });
  };

  // Send button inside the composer. The plan agreed with the user is
  // "save first, then open the send dialog for the new note". On
  // success we reset the composer (mirroring save()) and hand the
  // freshly created note's id up to the page so it can mount
  // <SendNoteDialog>. On failure we leave the composer state intact
  // so the user can retry — same as the existing save error behavior.
  const sendNew = () => {
    const { isEmpty, payload } = buildPayload();
    if (isEmpty || create.isPending) return;
    create.mutate(payload, {
      onSuccess: (created) => {
        reset();
        onSend(created.id);
      },
    });
  };

  const sendDisabled =
    create.isPending ||
    (kind === "checklist"
      ? items.every((it) => !it.text.trim())
      : !content.trim());

  return (
    <div
      ref={ref}
      data-testid="notes-composer"
      className={`max-w-xl mx-auto rounded-xl border border-black/10 shadow-sm p-3 transition ${colorBg(
        color,
      )}`}
    >
      {open ? (
        <>
          {kind === "checklist" ? (
            <div className="px-1">
              <ChecklistEditor
                items={items}
                onChange={setItems}
                testIdPrefix="notes-composer-checklist"
              />
            </div>
          ) : (
            <Textarea
              autoFocus
              ref={contentRef}
              value={content}
              onChange={(e) => setContent(e.target.value)}
              onFocus={(e) => {
                contentFocused.current = true;
                if (keyboardInset > 80) {
                  e.currentTarget.scrollIntoView({ block: "center" });
                }
              }}
              onBlur={() => {
                contentFocused.current = false;
              }}
              placeholder={t("notes.contentPlaceholder", "Take a note...")}
              className="border-0 bg-transparent shadow-none focus-visible:ring-0 px-1 min-h-[80px] resize-none"
              data-testid="notes-composer-content"
            />
          )}
          <div className="flex items-center gap-1 mt-2">
            <ColorPicker value={color} onChange={setColor} />
            <KindToggle
              kind={kind}
              onChange={setKind}
              testId="notes-composer-kind-toggle"
            />
            <LabelMenu
              labels={labels}
              selected={labelIds}
              onChange={setLabelIds}
            />
            <div className="ms-auto flex items-center gap-1">
              <Button size="sm" variant="ghost" onClick={save} data-testid="notes-composer-save">
                {t("notes.save", "Save")}
              </Button>
              {/* Send action inside the composer. Saves first
                  (auto-save on send was explicitly OK'd by the user),
                  then opens <SendNoteDialog> for the new note. Disabled
                  while empty or while the create request is in flight
                  to prevent duplicate notes from rapid clicks. */}
              {!hideSend && (
                <Button
                  size="sm"
                  variant="ghost"
                  onClick={sendNew}
                  disabled={sendDisabled}
                  aria-label={t("notes.send", "Send")}
                  data-testid="notes-composer-send"
                  className="px-2"
                >
                  <Send size={16} />
                </Button>
              )}
            </div>
          </div>
        </>
      ) : (
        <button
          onClick={() => setOpen(true)}
          className="w-full flex items-center gap-2 text-start text-muted-foreground py-1 px-1"
          data-testid="notes-composer-open"
        >
          <Plus
            size={18}
            aria-hidden="true"
            className="shrink-0 text-muted-foreground"
          />
          <span className="truncate">
            {t("notes.takeNote", "Take a note...")}
          </span>
        </button>
      )}
    </div>
  );
}
