import { useEffect, useMemo, useRef, useState, type ChangeEvent, type FormEvent, type KeyboardEvent } from 'react';
import {
  SPECIAL_MENTIONS,
  insertMention,
  resolveMentions,
  trailingMentionQuery,
} from '../mentions';
import type { Attachment, Person, SendOpts } from '../types';

interface Suggestion {
  key: string;
  label: string;
  insert: string;
}

/** Keyboard shortcut → the marker it wraps the selection in. */
const SHORTCUTS: Record<string, string> = { b: '*', i: '_', e: '`' };

/** Toolbar affordances for the same markers (strikethrough is button-only — no common shortcut). */
const FORMAT_BUTTONS: Array<{ marker: string; label: string; title: string }> = [
  { marker: '*', label: 'B', title: 'Bold (⌘B)' },
  { marker: '_', label: 'I', title: 'Italic (⌘I)' },
  { marker: '~', label: 'S', title: 'Strikethrough' },
  { marker: '`', label: '‹›', title: 'Code (⌘E)' },
];

/**
 * The message input: draft, @mention autocomplete, and attachment staging. Shared by the main
 * Thread and the ThreadPane (which passes a parentInteractionId so a reply lands in the thread).
 */
export function Composer({
  members,
  canUpload,
  upload,
  onSend,
  onTyping,
  parentInteractionId,
  placeholder = 'Type a message… @ to mention, *bold*',
}: {
  members: Person[];
  canUpload: boolean;
  upload: (file: File) => Promise<Attachment>;
  onSend: (text: string, opts?: SendOpts) => Promise<void>;
  onTyping?: () => void;
  parentInteractionId?: string;
  placeholder?: string;
}) {
  const [draft, setDraft] = useState('');
  const [sending, setSending] = useState(false);
  const [staged, setStaged] = useState<Attachment | null>(null);
  const [uploading, setUploading] = useState(false);
  const [uploadingName, setUploadingName] = useState<string | null>(null);
  const fileRef = useRef<HTMLInputElement>(null);
  const inputRef = useRef<HTMLTextAreaElement>(null);

  // Grow with the content up to the CSS max-height, then scroll — a chat box, not a fixed field.
  // The box is border-box, but scrollHeight excludes the border, so add it back or every measure
  // lands a couple of pixels short and the textarea shows a scrollbar it doesn't need.
  useEffect(() => {
    const el = inputRef.current;
    if (!el) return;
    el.style.height = 'auto';
    const border = el.offsetHeight - el.clientHeight;
    el.style.height = `${el.scrollHeight + border}px`;
  }, [draft]);

  /**
   * Wrap the current selection in a marker (or, with nothing selected, drop in an empty pair and
   * park the caret inside). Text stays plain — the marker characters ARE the format, so this never
   * needs a rich-text model and the native spellchecker keeps working on the raw string.
   */
  function wrapSelection(marker: string): void {
    const el = inputRef.current;
    if (!el) return;
    const start = el.selectionStart ?? draft.length;
    const end = el.selectionEnd ?? start;
    const selected = draft.slice(start, end);
    const next = `${draft.slice(0, start)}${marker}${selected}${marker}${draft.slice(end)}`;
    setDraft(next);
    // Restore a sensible selection after React re-renders the value.
    const caret = selected ? start + marker.length + selected.length + marker.length : start + marker.length;
    requestAnimationFrame(() => {
      el.focus();
      el.setSelectionRange(selected ? start + marker.length : caret, selected ? caret - marker.length : caret);
    });
  }

  const query = trailingMentionQuery(draft);
  const suggestions = useMemo<Suggestion[]>(() => {
    if (query === null) return [];
    const q = query.toLowerCase();
    const specials = SPECIAL_MENTIONS.filter((s) => s.startsWith(q)).map((s) => ({ key: `@${s}`, label: `@${s}`, insert: s }));
    const people = members.filter((m) => m.name.toLowerCase().includes(q)).map((m) => ({ key: m.id, label: m.name, insert: m.name }));
    return [...specials, ...people].slice(0, 6);
  }, [query, members]);
  const showSuggest = query !== null && suggestions.length > 0;

  function pick(insert: string): void {
    setDraft((d) => insertMention(d, insert));
  }

  async function onPickFile(e: ChangeEvent<HTMLInputElement>): Promise<void> {
    const file = e.target.files?.[0];
    e.target.value = '';
    if (!file) return;
    setUploading(true);
    setUploadingName(file.name);
    try {
      setStaged(await upload(file));
    } catch {
      /* host surfaces upload errors */
    } finally {
      setUploading(false);
      setUploadingName(null);
    }
  }

  async function submit(e?: FormEvent): Promise<void> {
    e?.preventDefault();
    const text = draft.trim();
    if ((!text && !staged) || sending) return;
    const mentions = resolveMentions(text, members);
    const att = staged;
    setDraft('');
    setStaged(null);
    setSending(true);
    try {
      await onSend(text, {
        ...(mentions.length ? { mentions } : {}),
        ...(att ? { attachment: att } : {}),
        ...(parentInteractionId ? { parentInteractionId } : {}),
      });
    } catch {
      setStaged(att);
    } finally {
      setSending(false);
    }
  }

  function onKeyDown(e: KeyboardEvent<HTMLTextAreaElement>): void {
    // Formatting shortcuts, matching the toolbar. Cmd on macOS, Ctrl elsewhere.
    if (e.metaKey || e.ctrlKey) {
      const marker = SHORTCUTS[e.key.toLowerCase()];
      if (marker) {
        e.preventDefault();
        wrapSelection(marker);
        return;
      }
    }
    if (e.key !== 'Enter') return;
    if (showSuggest) {
      e.preventDefault();
      pick(suggestions[0]!.insert);
      return;
    }
    // Enter sends; Shift+Enter (and IME composition) inserts a newline.
    if (!e.shiftKey && !e.nativeEvent.isComposing) {
      e.preventDefault();
      void submit();
    }
  }

  return (
    <form className="miu-composer" onSubmit={submit}>
      {showSuggest ? (
        <ul className="miu-suggest" role="listbox" aria-label="Mention suggestions">
          {suggestions.map((s) => (
            <li key={s.key}>
              <button type="button" role="option" aria-selected="false" className="miu-suggest-item" onClick={() => pick(s.insert)}>
                {s.label}
              </button>
            </li>
          ))}
        </ul>
      ) : null}
      {uploading && uploadingName ? (
        <div className="miu-staged is-uploading">
          <span className="miu-spinner" aria-hidden="true" /> {uploadingName} · uploading…
        </div>
      ) : staged ? (
        <div className="miu-staged">
          📎 {staged.name}
          <button type="button" className="miu-staged-x" onClick={() => setStaged(null)} aria-label="Remove attachment">
            ✕
          </button>
        </div>
      ) : null}
      <div className="miu-format-bar" role="group" aria-label="Formatting">
        {FORMAT_BUTTONS.map((b) => (
          <button key={b.marker} type="button" className="miu-format-btn" title={b.title} aria-label={b.title} onClick={() => wrapSelection(b.marker)}>
            {b.label}
          </button>
        ))}
      </div>
      <div className="miu-composer-row">
        {canUpload ? (
          <>
            <input ref={fileRef} type="file" className="miu-file-input" onChange={onPickFile} aria-label="Attach a file" />
            <button type="button" className="miu-attach-btn" title="Attach a file" disabled={uploading} onClick={() => fileRef.current?.click()}>
              {uploading ? '…' : '📎'}
            </button>
          </>
        ) : null}
        <textarea
          ref={inputRef}
          className="miu-input miu-composer-box"
          value={draft}
          placeholder={placeholder}
          aria-label="Message"
          rows={1}
          // Native platform text services: the browser/OS supplies the red squiggle, the right-click
          // suggestions and "Add to dictionary", and mobile keyboards add autocorrect. Nothing to ship.
          spellCheck
          autoCorrect="on"
          autoCapitalize="sentences"
          onChange={(e) => {
            setDraft(e.target.value);
            onTyping?.();
          }}
          onKeyDown={onKeyDown}
        />
        <button type="submit" className="miu-send" disabled={(!draft.trim() && !staged) || sending}>
          Send
        </button>
      </div>
    </form>
  );
}
