"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import clsx from "clsx";
import { useFeatureFlags, useSdk, type Attachment } from "@lynkd/messaging-inbox-sdk";
import { useUi } from "@/lib/ui-state";
import { EmojiPicker } from "./EmojiPicker";
import { LanguageMenu } from "./LanguageMenu";
import { Avatar } from "@/components/ui/primitives";
import { replaceShortcodes, searchEmojis } from "@/lib/emoji";
import {
  Bold, Italic, Strikethrough, Code, Link2, List, ListOrdered,
  Paperclip, Smile, AtSign, Slash, Send, Video, Clock, Hash, X, FileText,
  Languages, RefreshCw, Check, Sparkles,
} from "lucide-react";

export interface SendOpts {
  scheduledFor?: number;
  attachments?: Attachment[];
  alsoSendToChannel?: boolean;
}

/** Serialize the contentEditable DOM into markdown, then expand :shortcodes:. */
function serialize(root: HTMLElement): string {
  const walk = (node: ChildNode): string => {
    if (node.nodeType === Node.TEXT_NODE) return node.textContent ?? "";
    if (node.nodeType !== Node.ELEMENT_NODE) return "";
    const el = node as HTMLElement;
    const tag = el.tagName.toLowerCase();
    const inner = () => Array.from(el.childNodes).map(walk).join("");
    switch (tag) {
      case "br": return "\n";
      case "b": case "strong": return `**${inner()}**`;
      case "i": case "em": return `_${inner()}_`;
      case "s": case "strike": case "del": return `~~${inner()}~~`;
      case "code": return "`" + inner() + "`";
      case "a": return `[${inner()}](${el.getAttribute("href") || "https://"})`;
      case "ul": return Array.from(el.children).map((li) => `\n• ${Array.from(li.childNodes).map(walk).join("")}`).join("");
      case "ol": return Array.from(el.children).map((li, i) => `\n${i + 1}. ${Array.from(li.childNodes).map(walk).join("")}`).join("");
      case "div": case "p": { const t = inner(); return t ? "\n" + t : ""; }
      default: return inner();
    }
  };
  return replaceShortcodes(Array.from(root.childNodes).map(walk).join("").replace(/\n{3,}/g, "\n\n").trim());
}

type Suggestion = { key: string; label: string; sub?: string; insert: string; icon?: React.ReactNode };

export function Composer({
  placeholder,
  onSend,
  onStartHuddle,
  compact,
  channelName,
  showAlsoSend,
}: {
  placeholder: string;
  onSend: (body: string, opts?: SendOpts) => void | Promise<void>;
  onStartHuddle?: () => void;
  compact?: boolean;
  channelName?: string;
  showAlsoSend?: boolean;
}) {
  const flags = useFeatureFlags();
  const { toast, openAi } = useUi();
  const { users, channels, client } = useSdk();
  const editorRef = useRef<HTMLDivElement>(null);
  const fileRef = useRef<HTMLInputElement>(null);
  const scheduleRef = useRef<HTMLDivElement>(null);
  const [empty, setEmpty] = useState(true);
  const [emojiOpen, setEmojiOpen] = useState(false);
  const [scheduleOpen, setScheduleOpen] = useState(false);
  const [attachments, setAttachments] = useState<Attachment[]>([]);
  const [alsoSend, setAlsoSend] = useState(false);
  // translate-before-send
  const [translateOpen, setTranslateOpen] = useState(false);
  const [translating, setTranslating] = useState(false);
  const [preview, setPreview] = useState<{ lang: string; original: string; text: string } | null>(null);

  // autocomplete state
  const [ac, setAc] = useState<{ trigger: string; query: string } | null>(null);
  const [acIndex, setAcIndex] = useState(0);

  const refresh = () => setEmpty(!(editorRef.current?.textContent?.trim()));
  const focusEditor = () => editorRef.current?.focus();

  const exec = (cmd: string, value?: string) => { focusEditor(); document.execCommand(cmd, false, value); refresh(); };
  const wrapCode = () => {
    focusEditor();
    const sel = window.getSelection();
    const text = sel && !sel.isCollapsed ? sel.toString() : "code";
    document.execCommand("insertHTML", false, `<code>${escapeHtml(text)}</code>&nbsp;`);
    refresh();
  };
  const makeLink = () => {
    focusEditor();
    const sel = window.getSelection();
    if (sel && !sel.isCollapsed) document.execCommand("createLink", false, "https://");
    else document.execCommand("insertHTML", false, `<a href="https://">link</a>&nbsp;`);
    refresh();
  };
  const insertText = (str: string) => { focusEditor(); document.execCommand("insertText", false, str); refresh(); };

  // ---- translate before send ----
  const requestTranslation = async (lang: string) => {
    setTranslateOpen(false);
    const el = editorRef.current;
    const text = el ? serialize(el) : "";
    if (!text.trim()) { toast("Write a message first, then translate.", "default"); return; }
    setTranslating(true);
    try {
      const res = await client.translate(text, lang);
      if (res.ok && res.text) setPreview({ lang, original: text, text: res.text });
      else toast(res.error || "Translation failed", "danger");
    } catch (e) {
      toast(`Translation failed: ${(e as Error).message}`, "danger");
    } finally {
      setTranslating(false);
    }
  };
  const applyPreview = () => {
    const el = editorRef.current;
    if (!preview || !el) return;
    el.innerText = preview.text; // replace draft with the translated text
    setPreview(null);
    refresh();
    focusEditor();
  };

  // ---- autocomplete detection ----
  const detectToken = () => {
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) { setAc(null); return; }
    const range = sel.getRangeAt(0);
    const node = range.startContainer;
    if (node.nodeType !== Node.TEXT_NODE) { setAc(null); return; }
    const text = (node.textContent ?? "").slice(0, range.startOffset);
    const m = /([@#:])([\w+-]*)$/.exec(text);
    if (!m) { setAc(null); return; }
    const idx = m.index;
    if (idx > 0 && !/\s/.test(text[idx - 1])) { setAc(null); return; }
    setAc({ trigger: m[1], query: m[2] });
    setAcIndex(0);
  };

  const suggestions: Suggestion[] = useMemo(() => {
    if (!ac) return [];
    const q = ac.query.toLowerCase();
    if (ac.trigger === "@") {
      return users
        .filter((u) => u.handle.toLowerCase().includes(q) || u.name.toLowerCase().includes(q))
        .slice(0, 8)
        .map((u) => ({ key: u.id, label: u.name, sub: `@${u.handle}`, insert: `@${u.handle} `, icon: <Avatar user={u} size={22} showPresence={false} rounded="6px" /> }));
    }
    if (ac.trigger === "#") {
      return channels
        .filter((c) => (c.kind === "channel" || c.kind === "private") && c.name.toLowerCase().includes(q))
        .slice(0, 8)
        .map((c) => ({ key: c.id, label: `#${c.name}`, sub: c.topic, insert: `#${c.name} `, icon: <Hash size={16} className="text-muted" /> }));
    }
    // emoji
    return searchEmojis(q, 8).map((e) => ({ key: e.n, label: `:${e.n}:`, insert: e.e, icon: <span className="text-lg">{e.e}</span> }));
  }, [ac, users, channels]);

  const acceptSuggestion = (s: Suggestion) => {
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0) return;
    const range = sel.getRangeAt(0);
    const node = range.startContainer;
    const text = (node.textContent ?? "").slice(0, range.startOffset);
    const m = /([@#:])([\w+-]*)$/.exec(text);
    if (!m || node.nodeType !== Node.TEXT_NODE) return;
    const start = m.index;
    const r = document.createRange();
    r.setStart(node, start);
    r.setEnd(node, range.startOffset);
    r.deleteContents();
    const tn = document.createTextNode(s.insert);
    r.insertNode(tn);
    // caret after inserted
    const after = document.createRange();
    after.setStartAfter(tn);
    after.collapse(true);
    sel.removeAllRanges();
    sel.addRange(after);
    setAc(null);
    refresh();
  };

  async function submit(scheduledFor?: number) {
    const el = editorRef.current;
    if (!el) return;
    const body = serialize(el);
    if (!body && attachments.length === 0) return;
    el.innerHTML = "";
    setEmpty(true);
    const opts: SendOpts = {};
    if (scheduledFor) opts.scheduledFor = scheduledFor;
    if (attachments.length) opts.attachments = attachments;
    if (showAlsoSend && alsoSend) opts.alsoSendToChannel = true;
    setAttachments([]);
    await onSend(body || "(attachment)", opts);
  }

  // files
  const onFiles = (files: FileList | null) => {
    if (!files) return;
    Array.from(files).slice(0, 5).forEach((f) => {
      const isImg = f.type.startsWith("image/");
      const reader = new FileReader();
      reader.onload = () => {
        setAttachments((prev) => [...prev, {
          id: `a_${Date.now()}_${Math.round(f.size)}_${prev.length}`,
          kind: isImg ? "image" : f.type.startsWith("video/") ? "video" : "file",
          name: f.name,
          sizeLabel: humanSize(f.size),
          mime: f.type,
          url: String(reader.result),
        }]);
      };
      reader.readAsDataURL(f);
    });
  };

  useEffect(() => {
    if (!scheduleOpen) return;
    const onDoc = (e: MouseEvent) => { if (scheduleRef.current && !scheduleRef.current.contains(e.target as Node)) setScheduleOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [scheduleOpen]);

  const toolbarBtn = "grid h-7 w-7 place-items-center rounded-md text-muted transition-colors hover:bg-hover hover:text-ink";
  const keepFocus = (e: React.MouseEvent) => e.preventDefault();
  const scheduleOptions = buildScheduleOptions();

  const inList = () => {
    let n: Node | null | undefined = window.getSelection()?.anchorNode;
    while (n && n !== editorRef.current) {
      if (n.nodeType === 1 && /^(LI|UL|OL)$/.test((n as HTMLElement).tagName)) return true;
      n = n.parentNode;
    }
    return false;
  };

  const onKeyDown = (e: React.KeyboardEvent) => {
    if (ac && suggestions.length) {
      if (e.key === "ArrowDown") { e.preventDefault(); setAcIndex((i) => Math.min(i + 1, suggestions.length - 1)); return; }
      if (e.key === "ArrowUp") { e.preventDefault(); setAcIndex((i) => Math.max(i - 1, 0)); return; }
      if (e.key === "Enter" || e.key === "Tab") { e.preventDefault(); acceptSuggestion(suggestions[acIndex]); return; }
      if (e.key === "Escape") { e.preventDefault(); setAc(null); return; }
    }
    // Tab inside a list = indent / Shift+Tab = outdent (nested sub-levels)
    if (e.key === "Tab" && inList()) {
      e.preventDefault();
      document.execCommand(e.shiftKey ? "outdent" : "indent");
      refresh();
      return;
    }
    if (e.key === "Enter" && !(e.nativeEvent as any).isComposing) {
      // Inside a list: Enter makes the next item natively (and exits on an empty
      // item). Shift+Enter — which the composer hint trains people to use — must
      // ALSO make the next bullet/number here, not a soft line break.
      if (inList()) {
        if (e.shiftKey) { e.preventDefault(); document.execCommand("insertParagraph"); }
        refresh();
        return;
      }
      if (!e.shiftKey) { e.preventDefault(); submit(); }
      // plain Shift+Enter outside a list → default soft newline
    }
  };

  return (
    <div className="shrink-0 px-4 pb-4 pt-1">
      <div className="relative rounded-card border border-border bg-panel focus-within:border-border-strong">
        {/* translate-before-send preview */}
        {preview && (
          <div className="absolute bottom-[calc(100%+6px)] left-0 right-0 z-40 overflow-hidden rounded-control border border-border bg-elevated shadow-pop animate-slide-up">
            <div className="flex items-center gap-2 border-b border-border px-3 py-2 text-[12px] font-semibold">
              <Languages size={14} className="text-accent" /> Translated to {preview.lang}
              <button onClick={() => setPreview(null)} className="ml-auto text-muted hover:text-ink"><X size={14} /></button>
            </div>
            <div className="max-h-40 overflow-y-auto px-3 py-2.5">
              <div className="mb-1 text-[10.5px] font-bold uppercase tracking-wide text-dim">Preview</div>
              <div className="whitespace-pre-wrap break-words text-[13.5px] text-ink/90">{preview.text}</div>
              <div className="mt-2 border-t border-border pt-2 text-[11.5px] text-muted"><span className="text-dim">Original:</span> {preview.original}</div>
            </div>
            <div className="flex items-center justify-end gap-2 border-t border-border px-3 py-2">
              <button onClick={() => setPreview(null)} className="rounded-control px-2.5 py-1 text-[12px] font-semibold text-muted hover:bg-hover">Cancel</button>
              <button onClick={applyPreview} className="flex items-center gap-1.5 rounded-control bg-accent px-2.5 py-1 text-[12px] font-semibold text-accent-fg"><Check size={13} /> Use translation</button>
            </div>
          </div>
        )}

        {/* autocomplete popover */}
        {ac && suggestions.length > 0 && (
          <div className="absolute bottom-[calc(100%+6px)] left-0 z-40 w-72 overflow-hidden rounded-control border border-border bg-elevated shadow-pop animate-slide-up">
            <div className="px-3 py-1.5 text-[10.5px] font-bold uppercase tracking-wide text-dim">
              {ac.trigger === "@" ? "People" : ac.trigger === "#" ? "Channels" : "Emoji"}
            </div>
            <div className="max-h-56 overflow-y-auto pb-1">
              {suggestions.map((s, i) => (
                <button
                  key={s.key}
                  onMouseEnter={() => setAcIndex(i)}
                  onMouseDown={(e) => { e.preventDefault(); acceptSuggestion(s); }}
                  className={clsx("flex w-full items-center gap-2.5 px-3 py-1.5 text-left", i === acIndex ? "bg-hover" : "")}
                >
                  <span className="grid h-6 w-6 shrink-0 place-items-center">{s.icon}</span>
                  <span className="min-w-0 flex-1"><span className="block truncate text-[13.5px]">{s.label}</span>{s.sub && <span className="block truncate text-[11.5px] text-muted">{s.sub}</span>}</span>
                </button>
              ))}
            </div>
          </div>
        )}

        {flags.richComposer && !compact && (
          <div className="flex flex-wrap items-center gap-0.5 border-b border-border px-2 py-1.5">
            <button className={toolbarBtn} title="Bold (Ctrl+B)" onMouseDown={keepFocus} onClick={() => exec("bold")}><Bold size={15} /></button>
            <button className={toolbarBtn} title="Italic (Ctrl+I)" onMouseDown={keepFocus} onClick={() => exec("italic")}><Italic size={15} /></button>
            <button className={toolbarBtn} title="Strikethrough" onMouseDown={keepFocus} onClick={() => exec("strikeThrough")}><Strikethrough size={15} /></button>
            <span className="mx-1 h-4 w-px bg-border" />
            <button className={toolbarBtn} title="Code" onMouseDown={keepFocus} onClick={wrapCode}><Code size={15} /></button>
            <button className={toolbarBtn} title="Link" onMouseDown={keepFocus} onClick={makeLink}><Link2 size={15} /></button>
            <span className="mx-1 h-4 w-px bg-border" />
            <button className={toolbarBtn} title="Bulleted list" onMouseDown={keepFocus} onClick={() => exec("insertUnorderedList")}><List size={15} /></button>
            <button className={toolbarBtn} title="Numbered list" onMouseDown={keepFocus} onClick={() => exec("insertOrderedList")}><ListOrdered size={15} /></button>
          </div>
        )}

        <div className="relative">
          {empty && <span className="pointer-events-none absolute left-3.5 top-3 text-[14.5px] text-dim">{placeholder}</span>}
          <div
            ref={editorRef}
            role="textbox"
            aria-label={placeholder}
            aria-multiline="true"
            contentEditable
            suppressContentEditableWarning
            onInput={() => { refresh(); detectToken(); }}
            onKeyUp={detectToken}
            onClick={detectToken}
            onKeyDown={onKeyDown}
            className="lynkd-editor max-h-40 min-h-[44px] w-full overflow-y-auto whitespace-pre-wrap break-words px-3.5 py-3 text-[14.5px] leading-relaxed text-ink outline-none [&_a]:text-info [&_a]:underline [&_code]:rounded [&_code]:bg-hover [&_code]:px-1 [&_code]:font-mono [&_code]:text-accent [&_ul]:list-disc [&_ol]:list-decimal [&_ul]:pl-6 [&_ol]:pl-6"
          />
        </div>

        {/* attachment previews */}
        {attachments.length > 0 && (
          <div className="flex flex-wrap gap-2 px-3 pb-2">
            {attachments.map((a) => (
              <div key={a.id} className="relative flex items-center gap-2 rounded-control border border-border bg-surface p-1.5 pr-6">
                {a.kind === "image" && a.url ? (
                  // eslint-disable-next-line @next/next/no-img-element
                  <img src={a.url} alt={a.name} className="h-9 w-9 rounded object-cover" />
                ) : (
                  <span className="grid h-9 w-9 place-items-center rounded bg-elevated text-muted"><FileText size={16} /></span>
                )}
                <div className="max-w-[140px]"><div className="truncate text-[12px] font-medium">{a.name}</div><div className="text-[10.5px] text-dim">{a.sizeLabel}</div></div>
                <button onClick={() => setAttachments((prev) => prev.filter((x) => x.id !== a.id))} className="absolute right-1 top-1 grid h-4 w-4 place-items-center rounded-full bg-elevated text-muted hover:text-ink" title="Remove"><X size={11} /></button>
              </div>
            ))}
          </div>
        )}

        <div className="flex items-center gap-0.5 px-2 py-1.5">
          {flags.fileUpload && (
            <>
              <button className={toolbarBtn} title="Attach a file" onClick={() => fileRef.current?.click()}><Paperclip size={16} /></button>
              <input ref={fileRef} type="file" multiple className="hidden" onChange={(e) => { onFiles(e.target.files); e.target.value = ""; }} />
            </>
          )}
          {flags.mentions && <button className={toolbarBtn} title="Mention someone" onMouseDown={keepFocus} onClick={() => insertText("@")}><AtSign size={16} /></button>}
          {flags.emojiPicker && (
            <div className="relative">
              <button className={toolbarBtn} title="Emoji" onMouseDown={keepFocus} onClick={() => { setEmojiOpen((o) => !o); setScheduleOpen(false); }}><Smile size={16} /></button>
              {emojiOpen && <EmojiPicker onPick={(e) => { insertText(e); setEmojiOpen(false); }} onClose={() => setEmojiOpen(false)} />}
            </div>
          )}
          {flags.slashCommands && <button className={toolbarBtn} title="Slash command" onMouseDown={keepFocus} onClick={() => insertText("/")}><Slash size={16} /></button>}
          {flags.translation && (
            <div className="relative">
              <button className={clsx(toolbarBtn, translating && "text-accent")} title="Translate before sending" onMouseDown={keepFocus} onClick={() => { setTranslateOpen((o) => !o); setEmojiOpen(false); setScheduleOpen(false); }}>
                {translating ? <RefreshCw size={16} className="animate-spin" /> : <Languages size={16} />}
              </button>
              {translateOpen && <LanguageMenu onPick={requestTranslation} onClose={() => setTranslateOpen(false)} />}
            </div>
          )}
          {flags.huddles && onStartHuddle && <button className={toolbarBtn} title="Start huddle" onClick={onStartHuddle}><Video size={16} /></button>}
          {flags.aiAssist && (
            <button
              type="button"
              onClick={() => openAi()}
              title="Ask the AI assistant"
              className="ml-1 flex items-center gap-1 rounded-control border border-accent/40 bg-accent-soft px-2 py-1 text-[12px] font-bold text-accent transition-colors hover:bg-accent hover:text-accent-fg"
            >
              <Sparkles size={14} /> AI
            </button>
          )}

          <div className="ml-auto flex items-center gap-1">
            {flags.scheduledSend && !compact && (
              <div className="relative" ref={scheduleRef}>
                <button className={clsx(toolbarBtn, "gap-1")} title="Schedule send" onClick={() => { setScheduleOpen((o) => !o); setEmojiOpen(false); }}><Clock size={16} /></button>
                {scheduleOpen && (
                  <div className="absolute bottom-9 right-0 z-30 w-52 overflow-hidden rounded-control border border-border bg-elevated shadow-pop animate-slide-up">
                    {scheduleOptions.map((o) => (
                      <button key={o.label} onClick={() => { submit(o.at); setScheduleOpen(false); }} disabled={empty && attachments.length === 0} className="block w-full px-3 py-2 text-left text-[13px] hover:bg-hover disabled:opacity-40">{o.label}</button>
                    ))}
                  </div>
                )}
              </div>
            )}
            <button onClick={() => submit()} disabled={empty && attachments.length === 0} className="focus-ring flex items-center gap-1.5 rounded-control bg-accent px-3 py-1.5 text-[13px] font-semibold text-accent-fg transition-opacity disabled:opacity-40">
              <Send size={14} /> Send
            </button>
          </div>
        </div>
      </div>

      <div className="flex items-center justify-between px-2 pt-1">
        <div className="text-[11px] text-dim"><b>Shift + Enter</b> for a new line · <b>Enter</b> to send</div>
        {showAlsoSend && (
          <label className="flex cursor-pointer items-center gap-1.5 text-[11.5px] text-muted">
            <input type="checkbox" checked={alsoSend} onChange={(e) => setAlsoSend(e.target.checked)} className="accent-[var(--c-accent)]" />
            Also send to {channelName ? <b className="text-ink">{channelName}</b> : "channel"}
          </label>
        )}
      </div>
    </div>
  );
}

function escapeHtml(s: string): string { return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); }
function humanSize(b: number): string { return b < 1024 ? `${b} B` : b < 1048576 ? `${(b / 1024).toFixed(0)} KB` : `${(b / 1048576).toFixed(1)} MB`; }

function buildScheduleOptions(): { label: string; at: number }[] {
  const now = new Date();
  const in30 = new Date(now.getTime() + 30 * 60_000);
  const tomorrow9 = new Date(now); tomorrow9.setDate(now.getDate() + 1); tomorrow9.setHours(9, 0, 0, 0);
  const nextMon = new Date(now); const d = ((8 - now.getDay()) % 7) || 7; nextMon.setDate(now.getDate() + d); nextMon.setHours(9, 0, 0, 0);
  const fmt = (x: Date) => x.toLocaleString(undefined, { weekday: "short", hour: "numeric", minute: "2-digit" });
  return [
    { label: `In 30 min · ${in30.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" })}`, at: in30.getTime() },
    { label: `Tomorrow · ${fmt(tomorrow9)}`, at: tomorrow9.getTime() },
    { label: `Next Monday · ${fmt(nextMon)}`, at: nextMon.getTime() },
  ];
}
