import { useEffect, useRef, useState, useCallback, forwardRef, useImperativeHandle } from "react";
import { GiphyPicker, type Gif } from "./GiphyPicker";
import { EmojiPicker as KaomojiPicker } from "./EmojiPicker";
import Picker, { Theme } from "emoji-picker-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
  faBold,
  faItalic,
  faStrikethrough,
  faCode,
  faEyeSlash,
  faFilm,
  faSmile,
  faGrin,
  faPaperPlane,
  faPlus,
  faParagraph,
  faAlignLeft,
  faListUl,
  faListOl,
  faQuoteRight,
  faLink,
  faHeading,
} from "@fortawesome/free-solid-svg-icons";

// ponytail: mirrors backend allowlist; real guard is server-side content-type detection
const SAFE_EXTS = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".mp3", ".wav", ".ogg", ".flac", ".mp4", ".webm", ".mov", ".pdf", ".txt", ".md", ".json", ".csv", ".zip", ".tar", ".gz"]);

// ponytail: minimal round-trip converters for the formatting our toolbar produces
function htmlToMarkdown(html: string): string {
  let md = html;
  // code blocks: <pre><code>...</code></pre>
  md = md.replace(/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, code) => {
    const inner = code.replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]*>/g, "");
    return "\n```\n" + inner + "\n```\n";
  });
  // links
  md = md.replace(/<a [^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, "[$2]($1)");
  // headings
  md = md.replace(/<h1[^>]*>(.*?)<\/h1>/gi, "\n# $1\n");
  md = md.replace(/<h2[^>]*>(.*?)<\/h2>/gi, "\n## $1\n");
  md = md.replace(/<h3[^>]*>(.*?)<\/h3>/gi, "\n### $1\n");
  // blockquotes: handle nested <br> inside
  md = md.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, (_, inner) => {
    const lines = inner.replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]*>/g, "").split("\n");
    return "\n" + lines.map((l: string) => "> " + l.trim()).join("\n") + "\n";
  });
  // unordered lists
  md = md.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (_, inner) => {
    const items = inner.match(/<li[^>]*>([\s\S]*?)<\/li>/gi) || [];
    return "\n" + items.map((li: string) => "- " + li.replace(/<\/?li[^>]*>/gi, "").replace(/<br\s*\/?>/gi, " ").replace(/<[^>]*>/g, "").trim()).join("\n") + "\n";
  });
  // ordered lists
  md = md.replace(/<ol[^>]*>([\s\S]*?)<\/ol>/gi, (_, inner) => {
    const items = inner.match(/<li[^>]*>([\s\S]*?)<\/li>/gi) || [];
    return "\n" + items.map((li: string, i: number) => `${i + 1}. ` + li.replace(/<\/?li[^>]*>/gi, "").replace(/<br\s*\/?>/gi, " ").replace(/<[^>]*>/g, "").trim()).join("\n") + "\n";
  });
  // remaining block elements → newlines
  md = md.replace(/<div[^>]*>/gi, "\n").replace(/<\/div>/gi, "");
  md = md.replace(/<br\s*\/?>/gi, "\n");
  md = md.replace(/<p[^>]*>/gi, "").replace(/<\/p>/gi, "\n");
  // inline formatting (innermost first)
  md = md.replace(/<span class="spoiler"[^>]*>(.*?)<\/span>/gi, "||$1||");
  md = md.replace(/<(?:b|strong)>(.*?)<\/(?:b|strong)>/gi, "**$1**");
  md = md.replace(/<(?:i|em)>(.*?)<\/(?:i|em)>/gi, "*$1*");
  md = md.replace(/<(?:s|strike|del)>(.*?)<\/(?:s|strike|del)>/gi, "~~$1~~");
  md = md.replace(/<code>(.*?)<\/code>/gi, "`$1`");
  // strip remaining tags
  md = md.replace(/<[^>]*>/g, "");
  // decode entities
  md = md.replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
  // collapse ≥3 consecutive newlines
  md = md.replace(/\n{3,}/g, "\n\n");
  return md.trim();
}

function markdownToHtml(md: string): string {
  // split into lines, process block structures
  const lines = md.split("\n");
  const result: string[] = [];
  let inCodeBlock = false;
  let codeBuf: string[] = [];
  let inList: "ul" | "ol" | null = null;

  const flushList = () => {
    if (inList) {
      result.push(`</${inList}>`);
      inList = null;
    }
  };

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    // code block fences
    if (/^```/.test(line.trim())) {
      if (inCodeBlock) {
        result.push(`<pre><code>${escapeHtml(codeBuf.join("\n"))}</code></pre>`);
        codeBuf = [];
        inCodeBlock = false;
      } else {
        flushList();
        inCodeBlock = true;
      }
      continue;
    }
    if (inCodeBlock) { codeBuf.push(line); continue; }

    // headings
    const hMatch = line.match(/^(#{1,6})\s+(.+)/);
    if (hMatch) {
      flushList();
      const level = hMatch[1].length;
      result.push(`<h${level}>${inlineMarkdownToHtml(hMatch[2])}</h${level}>`);
      continue;
    }

    // blockquote
    if (/^>\s?/.test(line)) {
      flushList();
      const content = line.replace(/^>\s?/, "");
      result.push(`<blockquote>${inlineMarkdownToHtml(content)}</blockquote>`);
      continue;
    }

    // unordered list
    const ulMatch = line.match(/^[-*+]\s+(.+)/);
    if (ulMatch) {
      if (inList !== "ul") { flushList(); result.push("<ul>"); inList = "ul"; }
      result.push(`<li>${inlineMarkdownToHtml(ulMatch[1])}</li>`);
      continue;
    }

    // ordered list
    const olMatch = line.match(/^\d+\.\s+(.+)/);
    if (olMatch) {
      if (inList !== "ol") { flushList(); result.push("<ol>"); inList = "ol"; }
      result.push(`<li>${inlineMarkdownToHtml(olMatch[1])}</li>`);
      continue;
    }

    // blank line: flush list
    if (line.trim() === "") {
      flushList();
      result.push("<br>");
      continue;
    }

    // regular paragraph
    flushList();
    result.push(`<div>${inlineMarkdownToHtml(line)}</div>`);
  }
  flushList();
  if (inCodeBlock) result.push(`<pre><code>${escapeHtml(codeBuf.join("\n"))}</code></pre>`);
  return result.join("");
}

function escapeHtml(s: string): string {
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

function inlineMarkdownToHtml(text: string): string {
  let html = escapeHtml(text);
  // links [text](url)
  html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noreferrer">$1</a>');
  html = html.replace(/\|\|(.+?)\|\|/g, '<span class="spoiler">$1</span>');
  html = html.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
  html = html.replace(/\*(.+?)\*/g, "<i>$1</i>");
  html = html.replace(/~~(.+?)~~/g, "<s>$1</s>");
  html = html.replace(/`(.+?)`/g, "<code>$1</code>");
  return html;
}

type EditorMode = "md" | "rt";

interface MessageInputProps {
  value: string;
  onChange: (value: string) => void;
  onSubmit: () => void;
  onPaste?: (e: React.ClipboardEvent<HTMLTextAreaElement>) => void;
  onGifSelect?: (gif: Gif) => void;
  placeholder?: string;
  disabled?: boolean;
}

export const MessageInput = forwardRef<HTMLTextAreaElement, MessageInputProps>(function MessageInput({
  value,
  onChange,
  onSubmit,
  onPaste,
  onGifSelect,
  placeholder = "Message...",
  disabled = false,
}, ref) {
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const richRef = useRef<HTMLDivElement>(null);
  useImperativeHandle(ref, () => textareaRef.current!, []);
  const [showGif, setShowGif] = useState(false);
  const [showKaomoji, setShowKaomoji] = useState(false);
  const [showEmoji, setShowEmoji] = useState(false);
  const [uploading, setUploading] = useState(false);
  const [uploadProgress, setUploadProgress] = useState(0);
  const [isDragOver, setIsDragOver] = useState(false);
  const [mode, setMode] = useState<EditorMode>("md");
  const fileInputRef = useRef<HTMLInputElement>(null);
  const dragCounterRef = useRef(0);
  const richSyncing = useRef(false);

  // sync rich text div when value changes externally
  useEffect(() => {
    if (mode === "rt" && richRef.current && !richSyncing.current) {
      richRef.current.innerHTML = markdownToHtml(value);
    }
  }, [value, mode]);

  // --- textarea helper ---
  const wrapTextarea = useCallback((prefix: string, suffix: string, defaultText = "text") => {
    const ta = textareaRef.current;
    if (!ta) return;
    const start = ta.selectionStart ?? 0;
    const end = ta.selectionEnd ?? 0;
    const selected = ta.value.slice(start, end) || defaultText;
    const result = ta.value.slice(0, start) + prefix + selected + suffix + ta.value.slice(end);
    onChange(result);
    requestAnimationFrame(() => {
      ta.focus();
      ta.setSelectionRange(start + prefix.length, start + prefix.length + selected.length);
    });
  }, [onChange]);

  // --- inline formatting ---
  const execInline = useCallback((prefix: string, suffix: string) => {
    if (mode === "rt") {
      document.execCommand("styleWithCSS", false, "false");
      if (prefix === "**") document.execCommand("bold");
      else if (prefix === "*") document.execCommand("italic");
      else if (prefix === "~~") document.execCommand("strikeThrough");
      else if (prefix === "`") {
        const sel = window.getSelection();
        if (sel && !sel.isCollapsed) {
          const range = sel.getRangeAt(0);
          const code = document.createElement("code");
          code.textContent = range.toString();
          range.deleteContents();
          range.insertNode(code);
        }
      } else if (prefix === "||") {
        const sel = window.getSelection();
        if (sel && !sel.isCollapsed) {
          const range = sel.getRangeAt(0);
          const span = document.createElement("span");
          span.className = "spoiler";
          span.textContent = range.toString();
          range.deleteContents();
          range.insertNode(span);
        }
      }
      richRef.current?.focus();
    } else {
      wrapTextarea(prefix, suffix);
    }
  }, [mode, wrapTextarea]);

  // --- block-level formatting (rich text uses execCommand, markdown wraps) ---
  const execBlock = useCallback((cmd: string) => {
    if (mode === "rt") {
      if (cmd === "ul") document.execCommand("insertUnorderedList");
      else if (cmd === "ol") document.execCommand("insertOrderedList");
      else if (cmd === "blockquote") {
        // execCommand quote is finicky; wrap selected blocks manually
        const sel = window.getSelection();
        if (sel && !sel.isCollapsed) {
          document.execCommand("formatBlock", false, "<blockquote>");
        } else {
          // insert empty blockquote
          const bq = document.createElement("blockquote");
          bq.innerHTML = "<br>";
          richRef.current?.appendChild(bq);
        }
      }
      else if (cmd === "h1") document.execCommand("formatBlock", false, "<h1>");
      else if (cmd === "h2") document.execCommand("formatBlock", false, "<h2>");
      else if (cmd === "h3") document.execCommand("formatBlock", false, "<h3>");
      richRef.current?.focus();
    } else {
      switch (cmd) {
        case "ul": wrapTextarea("- ", ""); break;
        case "ol": wrapTextarea("1. ", ""); break;
        case "blockquote": {
          const ta = textareaRef.current;
          if (!ta) return;
          const start = ta.selectionStart ?? 0;
          const end = ta.selectionEnd ?? 0;
          const text = ta.value.slice(start, end) || ta.value;
          const lines = text.split("\n").map((l) => "> " + l).join("\n");
          onChange(lines);
          requestAnimationFrame(() => ta.focus());
          break;
        }
        case "h1": wrapTextarea("# ", "", "Heading 1"); break;
        case "h2": wrapTextarea("## ", "", "Heading 2"); break;
        case "h3": wrapTextarea("### ", "", "Heading 3"); break;
      }
    }
  }, [mode, onChange, wrapTextarea]);

  // --- link insertion ---
  const execLink = useCallback(() => {
    const url = prompt("Enter URL:", "https://");
    if (!url) return;
    if (mode === "rt") {
      const sel = window.getSelection();
      if (sel && !sel.isCollapsed) {
        document.execCommand("createLink", false, url);
      } else {
        const a = document.createElement("a");
        a.href = url;
        a.target = "_blank";
        a.rel = "noreferrer";
        a.textContent = url;
        richRef.current?.appendChild(a);
      }
      richRef.current?.focus();
    } else {
      const ta = textareaRef.current;
      if (!ta) return;
      const start = ta.selectionStart ?? 0;
      const end = ta.selectionEnd ?? 0;
      const selected = ta.value.slice(start, end) || url;
      const md = `[${selected}](${url})`;
      const result = ta.value.slice(0, start) + md + ta.value.slice(end);
      onChange(result);
      requestAnimationFrame(() => {
        ta.focus();
        ta.setSelectionRange(start + md.length, start + md.length);
      });
    }
  }, [mode, onChange]);

  // --- insert text/emoji at cursor ---
  const insert = useCallback((text: string) => {
    if (mode === "rt" && richRef.current) {
      richRef.current.focus();
      document.execCommand("insertText", false, text);
    } else {
      onChange(value + text);
      textareaRef.current?.focus();
    }
  }, [mode, value, onChange]);

  const insertMarkdown = useCallback((md: string) => {
    if (mode === "rt" && richRef.current) {
      richRef.current.focus();
      const html = markdownToHtml(md);
      const sel = window.getSelection();
      if (sel && sel.rangeCount > 0) {
        const range = sel.getRangeAt(0);
        range.deleteContents();
        const frag = range.createContextualFragment(html);
        range.insertNode(frag);
        range.collapse(false);
        sel.removeAllRanges();
        sel.addRange(range);
      }
    } else {
      onChange(value + (value && !value.endsWith(" ") ? " " : "") + md);
      textareaRef.current?.focus();
    }
  }, [mode, value, onChange]);

  // --- file upload ---
  const validateFile = useCallback((file: File): string | null => {
    const ext = file.name.slice(file.name.lastIndexOf(".")).toLowerCase();
    if (!SAFE_EXTS.has(ext)) return `File type "${ext}" not allowed.`;
    if (file.size > 1024 * 1024 * 1024) return "File too large (max 1 GB).";
    return null;
  }, []);

  const uploadFile = useCallback((file: File) => {
    const err = validateFile(file);
    if (err) { alert(err); return; }
    setUploading(true);
    setUploadProgress(0);
    const formData = new FormData();
    formData.append("file", file);
    const xhr = new XMLHttpRequest();
    xhr.upload.onprogress = (e) => {
      if (e.lengthComputable) setUploadProgress(Math.round((e.loaded / e.total) * 100));
    };
    xhr.onload = () => {
      setUploading(false);
      if (xhr.status >= 200 && xhr.status < 300) {
        const data = JSON.parse(xhr.responseText);
        const isImage = /\.(jpg|jpeg|png|gif|webp)$/i.test(file.name);
        const md = isImage ? `![${file.name}](${data.url})` : `[${file.name}](${data.url})`;
        insertMarkdown(md);
      } else {
        const msg = (() => { try { return JSON.parse(xhr.responseText).error; } catch { return "Upload failed"; } })();
        alert(msg);
      }
    };
    xhr.onerror = () => { setUploading(false); alert("Upload failed"); };
    xhr.open("POST", "/api/v1/upload");
    xhr.withCredentials = true;
    xhr.send(formData);
  }, [validateFile, insertMarkdown]);

  const handleFileInput = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (file) uploadFile(file);
    e.target.value = "";
  }, [uploadFile]);

  // --- drag-and-drop ---
  useEffect(() => {
    const el = mode === "rt" ? richRef.current : textareaRef.current;
    if (!el) return;
    const onDragEnter = (e: Event) => {
      e.preventDefault(); e.stopPropagation();
      dragCounterRef.current++;
      if (dragCounterRef.current === 1) setIsDragOver(true);
    };
    const onDragOver = (e: Event) => { e.preventDefault(); };
    const onDragLeave = (e: Event) => {
      e.preventDefault(); e.stopPropagation();
      dragCounterRef.current--;
      if (dragCounterRef.current === 0) setIsDragOver(false);
    };
    const onDrop = (e: Event) => {
      e.preventDefault(); e.stopPropagation();
      setIsDragOver(false);
      dragCounterRef.current = 0;
      const file = (e as DragEvent).dataTransfer?.files?.[0];
      if (file) uploadFile(file);
    };
    el.addEventListener("dragenter", onDragEnter);
    el.addEventListener("dragover", onDragOver);
    el.addEventListener("dragleave", onDragLeave);
    el.addEventListener("drop", onDrop);
    return () => {
      el.removeEventListener("dragenter", onDragEnter);
      el.removeEventListener("dragover", onDragOver);
      el.removeEventListener("dragleave", onDragLeave);
      el.removeEventListener("drop", onDrop);
    };
  }, [uploadFile, mode]);

  // --- Ctrl+E kaomoji ---
  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      if (e.ctrlKey && e.key === "e" && !disabled) {
        e.preventDefault();
        setShowKaomoji((p) => !p);
        setShowEmoji(false);
        setShowGif(false);
      }
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [disabled]);

  const handleSubmit = useCallback(
    (e: React.FormEvent) => {
      e.preventDefault();
      let finalValue = value;
      if (mode === "rt" && richRef.current) {
        finalValue = htmlToMarkdown(richRef.current.innerHTML);
      }
      if (!disabled && finalValue.trim()) {
        if (mode === "rt") onChange(finalValue);
        onSubmit();
      }
    },
    [disabled, value, mode, onChange, onSubmit],
  );

  const handleRichInput = useCallback(() => {
    if (!richRef.current) return;
    richSyncing.current = true;
    const md = htmlToMarkdown(richRef.current.innerHTML);
    onChange(md);
    requestAnimationFrame(() => { richSyncing.current = false; });
  }, [onChange]);

  const toggleMode = useCallback(() => {
    setMode((prev) => {
      const next: EditorMode = prev === "md" ? "rt" : "md";
      if (next === "rt") {
        requestAnimationFrame(() => {
          if (richRef.current) {
            richRef.current.innerHTML = markdownToHtml(value || "");
          }
        });
      }
      return next;
    });
  }, [value]);

  const toggleGif = () => { setShowGif((p) => !p); setShowKaomoji(false); setShowEmoji(false); };
  const toggleEmoji = () => { setShowEmoji((p) => !p); setShowKaomoji(false); setShowGif(false); };
  const toggleKaomoji = () => { setShowKaomoji((p) => !p); setShowEmoji(false); setShowGif(false); };

  const handleGif = (gif: Gif) => {
    if (onGifSelect) {
      onGifSelect(gif);
    } else {
      const md = `![${gif.title || "GIF"}](${gif.images.fixed_height.url})`;
      insertMarkdown(md);
    }
    setShowGif(false);
  };

  return (
    <div className="relative">
      {/* picker overlays */}
      {showGif && (
        <div className="mb-1">
          <GiphyPicker onSelect={handleGif} onClose={() => setShowGif(false)} />
        </div>
      )}
      {showKaomoji && (
        <KaomojiPicker onSelect={insert} onClose={() => setShowKaomoji(false)} />
      )}
      {showEmoji && (
        <div className="absolute bottom-full right-0 mb-2 z-50">
          <Picker
            theme={Theme.DARK}
            onEmojiClick={(e) => { insert(e.emoji); setShowEmoji(false); }}
          />
        </div>
      )}

      <form onSubmit={handleSubmit}>
        <div className="bg-gb-bg-s terminal-border px-2 py-1">
          {mode === "md" ? (
            <textarea
              ref={textareaRef}
              value={value}
              onChange={(e) => onChange(e.target.value)}
              onPaste={onPaste}
              onKeyDown={(e) => {
                if (e.key === "Enter" && !e.shiftKey) {
                  e.preventDefault();
                  onSubmit();
                }
              }}
              placeholder={isDragOver ? "Drop file here..." : placeholder}
              rows={1}
              disabled={disabled}
              className={`w-full bg-transparent outline-none border-none resize-none overflow-y-auto max-h-32 text-[14px] leading-snug py-1 font-mono ${isDragOver ? "bg-gb-bg-t" : ""}`}
            />
          ) : (
            <div
              ref={richRef}
              contentEditable={!disabled}
              suppressContentEditableWarning
              onInput={handleRichInput}
              onKeyDown={(e) => {
                if (e.key === "Enter" && !e.shiftKey) {
                  e.preventDefault();
                  onSubmit();
                }
              }}
              data-placeholder={isDragOver ? "Drop file here..." : placeholder}
              className={`w-full outline-none resize-none overflow-y-auto max-h-32 text-[14px] leading-snug py-1 font-mono
                before:content-[attr(data-placeholder)] before:text-gb-fg-f before:opacity-60
                empty:before:block before:hidden
                ${isDragOver ? "bg-gb-bg-t" : ""}
                [&_b]:text-gb-fg [&_b]:font-bold [&_strong]:font-bold [&_i]:text-gb-fg [&_i]:italic [&_em]:italic [&_s]:text-gb-fg [&_s]:line-through [&_del]:line-through [&_strike]:line-through
                [&_code]:bg-gb-bg-t [&_code]:px-1 [&_code]:rounded
                [&_.spoiler]:bg-gb-bg-t [&_.spoiler]:px-1 [&_.spoiler]:rounded
                [&_a]:text-gb-aqua [&_a]:underline
                [&_h1]:text-lg [&_h1]:font-bold [&_h2]:text-base [&_h2]:font-bold
                [&_h3]:text-sm [&_h3]:font-bold
                [&_blockquote]:border-l-2 [&_blockquote]:border-gb-orange [&_blockquote]:pl-2 [&_blockquote]:text-gb-fg-s
                [&_ul]:list-disc [&_ul]:ml-4 [&_ol]:list-decimal [&_ol]:ml-4
                [&_pre]:bg-gb-bg-t [&_pre]:p-2 [&_pre]:my-1 [&_pre]:overflow-x-auto
                [&_pre_code]:bg-transparent [&_pre_code]:px-0`}
            />
          )}

          {/* upload progress bar */}
          {uploading && (
            <div className="flex items-center gap-2 px-1 pb-1">
              <span className="text-gb-fg-f text-xs font-mono shrink-0">uploading</span>
              <div className="flex-1 h-1.5 bg-gb-bg-t rounded-full overflow-hidden">
                <div
                  className="h-full bg-gb-aqua transition-all duration-150 rounded-full"
                  style={{ width: `${uploadProgress || 5}%` }}
                />
              </div>
              <span className="text-gb-fg-f text-xs font-mono shrink-0 w-8 text-right">{uploadProgress}%</span>
            </div>
          )}

          {/* toolbar row */}
          <div className="flex items-center gap-0.5 pt-0.5 border-t border-gb-bg-t">
            {/* + file upload */}
            <input
              ref={fileInputRef}
              type="file"
              className="hidden"
              onChange={handleFileInput}
              accept=".jpg,.jpeg,.png,.gif,.webp,.mp3,.wav,.ogg,.flac,.mp4,.webm,.mov,.pdf,.txt,.md,.json,.csv,.zip,.tar,.gz"
            />
            <button type="button" disabled={disabled || uploading}
              onClick={() => fileInputRef.current?.click()}
              title="Upload file"
              className="text-gb-fg-f hover:text-gb-orange p-1 disabled:opacity-50">
              <FontAwesomeIcon icon={faPlus} className={`w-3.5 h-3.5 ${uploading ? "animate-pulse" : ""}`} />
            </button>

            <span className="text-gb-bg-t mx-1">│</span>

            {/* block formatting */}
            <button type="button" disabled={disabled}
              onClick={() => execBlock("ul")} title="Unordered list"
              className="text-gb-fg-f hover:text-gb-orange p-1 disabled:opacity-50">
              <FontAwesomeIcon icon={faListUl} className="w-3 h-3" />
            </button>
            <button type="button" disabled={disabled}
              onClick={() => execBlock("ol")} title="Ordered list"
              className="text-gb-fg-f hover:text-gb-orange p-1 disabled:opacity-50">
              <FontAwesomeIcon icon={faListOl} className="w-3 h-3" />
            </button>
            <button type="button" disabled={disabled}
              onClick={() => execBlock("blockquote")} title="Blockquote"
              className="text-gb-fg-f hover:text-gb-orange p-1 disabled:opacity-50">
              <FontAwesomeIcon icon={faQuoteRight} className="w-3 h-3" />
            </button>
            <button type="button" disabled={disabled}
              onClick={execLink} title="Insert link"
              className="text-gb-fg-f hover:text-gb-orange p-1 disabled:opacity-50">
              <FontAwesomeIcon icon={faLink} className="w-3 h-3" />
            </button>
            <button type="button" disabled={disabled}
              onClick={() => execBlock("h2")} title="Heading"
              className="text-gb-fg-f hover:text-gb-orange p-1 disabled:opacity-50">
              <FontAwesomeIcon icon={faHeading} className="w-3 h-3" />
            </button>

            <span className="text-gb-bg-t mx-1">│</span>

            {/* inline formatting */}
            <button type="button" disabled={disabled}
              onClick={() => execInline("**", "**")} title="Bold"
              className="text-gb-fg-f hover:text-gb-orange p-1 disabled:opacity-50">
              <FontAwesomeIcon icon={faBold} className="w-3 h-3" />
            </button>
            <button type="button" disabled={disabled}
              onClick={() => execInline("*", "*")} title="Italic"
              className="text-gb-fg-f hover:text-gb-orange p-1 disabled:opacity-50">
              <FontAwesomeIcon icon={faItalic} className="w-3 h-3" />
            </button>
            <button type="button" disabled={disabled}
              onClick={() => execInline("~~", "~~")} title="Strikethrough"
              className="text-gb-fg-f hover:text-gb-orange p-1 disabled:opacity-50">
              <FontAwesomeIcon icon={faStrikethrough} className="w-3 h-3" />
            </button>
            <button type="button" disabled={disabled}
              onClick={() => execInline("`", "`")} title="Code"
              className="text-gb-fg-f hover:text-gb-orange p-1 disabled:opacity-50">
              <FontAwesomeIcon icon={faCode} className="w-3 h-3" />
            </button>
            <button type="button" disabled={disabled}
              onClick={() => execInline("||", "||")} title="Spoiler"
              className="text-gb-fg-f hover:text-gb-orange p-1 disabled:opacity-50">
              <FontAwesomeIcon icon={faEyeSlash} className="w-3 h-3" />
            </button>

            <span className="text-gb-bg-t mx-1">│</span>

            {/* emoji / kaomoji / gif */}
            <button type="button" disabled={disabled} onClick={toggleEmoji} title="Emoji"
              className={`p-1 disabled:opacity-50 ${showEmoji ? "text-gb-orange" : "text-gb-fg-f hover:text-gb-orange"}`}>
              <FontAwesomeIcon icon={faSmile} className="w-4 h-4" />
            </button>
            <button type="button" disabled={disabled} onClick={toggleKaomoji} title="Kaomoji"
              className={`p-1 disabled:opacity-50 ${showKaomoji ? "text-gb-orange" : "text-gb-fg-f hover:text-gb-orange"}`}>
              <FontAwesomeIcon icon={faGrin} className="w-4 h-4" />
            </button>
            <button type="button" disabled={disabled} onClick={toggleGif} title="GIF"
              className={`p-1 disabled:opacity-50 ${showGif ? "text-gb-orange" : "text-gb-fg-f hover:text-gb-orange"}`}>
              <FontAwesomeIcon icon={faFilm} className="w-4 h-4" />
            </button>

            <span className="flex-1" />

            {/* mode toggle */}
            <button type="button" disabled={disabled} onClick={toggleMode}
              title={mode === "md" ? "Switch to rich text" : "Switch to markdown"}
              className={`p-1 disabled:opacity-50 ${mode === "rt" ? "text-gb-orange" : "text-gb-fg-f hover:text-gb-orange"}`}>
              <FontAwesomeIcon icon={mode === "md" ? faParagraph : faAlignLeft} className="w-3.5 h-3.5" />
            </button>

            {/* send */}
            <button type="submit"
              disabled={disabled || !value.trim()}
              className="text-gb-aqua hover:text-gb-orange disabled:text-gb-fg-f p-1 disabled:opacity-40 transition-colors"
              title="Send (Enter)">
              <FontAwesomeIcon icon={faPaperPlane} className="w-4 h-4" />
            </button>
          </div>
        </div>
      </form>
    </div>
  );
});
