import { useCallback, useEffect, useRef, useState, type KeyboardEvent } from 'react';
import { Send } from 'lucide-react';
import { toast } from 'sonner';
import { Textarea } from '@/components/ui/textarea';
import { Button } from '@/components/ui/button';
import { flattenToMessage, inferLanguage, type Attachment } from '@/lib/attachments';
import { AttachmentChip } from '@/components/AttachmentChip';
import { AttachmentPreviewModal } from '@/components/AttachmentPreviewModal';
import { FileMentionPopover } from '@/components/FileMentionPopover';
import { api } from '@/api/client';
import { sessionEvents } from '@/hooks/sessionEvents';
import { useViewport } from '@/hooks/useViewport';

interface Props {
  disabled?: boolean;
  projectId: string;
  onSend: (content: string) => void | Promise<void>;
  onForceSend?: (content: string) => void | Promise<void>;
}

export function ChatInput({ disabled, projectId, onSend, onForceSend }: Props) {
  const { isMobile } = useViewport();
  const [value, setValue] = useState('');
  const [busy, setBusy] = useState(false);
  const [attachments, setAttachments] = useState<Attachment[]>([]);
  const [previewAttachment, setPreviewAttachment] = useState<Attachment | null>(null);
  const [mentionState, setMentionState] = useState<{
    open: boolean;
    query: string;
    atIdx: number;
    anchorRect: { top: number; left: number };
  } | null>(null);
  const [fileIndex, setFileIndex] = useState<string[] | null>(null);
  const textareaRef = useRef<HTMLTextAreaElement | null>(null);

  function addAttachment(a: Attachment) {
    setAttachments(prev => {
      if (prev.length >= 10) {
        toast.error('Max 10 attachments per message');
        return prev;
      }
      return [...prev, a];
    });
  }

  const addAttachmentRef = useRef(addAttachment);
  addAttachmentRef.current = addAttachment;

  useEffect(() => {
    return sessionEvents.subscribe((event) => {
      if (event.type !== 'attach_chat_file') return;
      addAttachmentRef.current({
        id: crypto.randomUUID(),
        ...event.attachment,
      });
    });
  }, []);

  function removeAttachment(id: string) {
    setAttachments(prev => prev.filter(a => a.id !== id));
  }

  async function submit() {
    const text = value.trim();
    if (!text && attachments.length === 0) return;
    if (disabled || busy) return;
    setBusy(true);
    try {
      const body = flattenToMessage(attachments, text);
      await onSend(body);
      setValue('');
      setAttachments([]);
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'failed to send');
    } finally {
      setBusy(false);
    }
  }

  function getCaretCoords(textarea: HTMLTextAreaElement): { top: number; left: number } {
    const mirror = document.createElement('div');
    const style = window.getComputedStyle(textarea);

    const properties = [
      'fontFamily', 'fontSize', 'fontWeight', 'fontStyle',
      'letterSpacing', 'lineHeight', 'textTransform', 'wordSpacing',
      'textIndent', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
      'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth',
      'boxSizing', 'whiteSpace', 'overflowWrap',
    ] as const;

    mirror.style.position = 'absolute';
    mirror.style.visibility = 'hidden';
    mirror.style.overflow = 'hidden';
    mirror.style.width = style.width;
    for (const prop of properties) {
      mirror.style[prop] = style[prop];
    }
    mirror.style.whiteSpace = 'pre-wrap';
    mirror.style.overflowWrap = 'break-word';

    const textBefore = textarea.value.slice(0, textarea.selectionStart);
    mirror.textContent = textBefore;

    const span = document.createElement('span');
    span.textContent = '​'; // zero-width space
    mirror.appendChild(span);

    document.body.appendChild(mirror);

    const taRect = textarea.getBoundingClientRect();
    const spanRect = span.getBoundingClientRect();
    const mirrorRect = mirror.getBoundingClientRect();

    const top = taRect.top + (spanRect.top - mirrorRect.top) - textarea.scrollTop + span.offsetHeight;
    const left = taRect.left + (spanRect.left - mirrorRect.left);

    document.body.removeChild(mirror);

    return { top, left };
  }

  function handleChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
    const newValue = e.target.value;
    setValue(newValue);

    const ta = e.target;
    const pos = ta.selectionStart;

    // Check for @ trigger
    if (pos > 0 && newValue[pos - 1] === '@') {
      const charBefore = pos >= 2 ? newValue[pos - 2] : null;
      if (charBefore === null || charBefore === ' ' || charBefore === '\n') {
        const coords = getCaretCoords(ta);
        setMentionState({ open: true, query: '', atIdx: pos - 1, anchorRect: coords });
        if (!fileIndex) {
          api.projects.files(projectId).then(r => setFileIndex(r.files)).catch(() => {});
        }
        return;
      }
    }

    // Update query if popover is open — use stored atIdx
    if (mentionState?.open) {
      const { atIdx } = mentionState;
      if (atIdx < pos && newValue[atIdx] === '@') {
        const query = newValue.slice(atIdx + 1, pos);
        setMentionState(prev => prev ? { ...prev, query } : null);
      } else {
        setMentionState(null);
      }
    }
  }

  async function handleMentionSelect(path: string) {
    const atIdx = mentionState?.atIdx ?? -1;
    const ta = textareaRef.current;
    const caretPos = ta?.selectionStart ?? value.length;
    setMentionState(null);

    try {
      const result = await api.projects.viewFile(projectId, path);
      if (atIdx >= 0) {
        const cleaned = value.slice(0, atIdx) + value.slice(caretPos);
        setValue(cleaned);
        if (ta) {
          requestAnimationFrame(() => {
            ta.selectionStart = ta.selectionEnd = atIdx;
            ta.focus();
          });
        }
      }
      addAttachment({
        id: crypto.randomUUID(),
        kind: 'file',
        filename: path,
        language: inferLanguage(path),
        content: result.content,
        source: '@',
      });
    } catch {
      toast.error('Failed to load file');
    }
  }

  const closeMention = useCallback(() => setMentionState(null), []);

  function onKeyDown(e: KeyboardEvent<HTMLTextAreaElement>) {
    if (mentionState?.open) return;
    // IME safety: never act on Enter while an IME composition is in flight
    // (CJK input methods commit composition via Enter). Without this, the
    // first Enter of a Japanese/Chinese/Korean composition would submit
    // instead of finalizing the candidate.
    if (e.nativeEvent.isComposing) return;
    if (e.key === 'Enter' && e.shiftKey && (e.metaKey || e.ctrlKey) && onForceSend) {
      e.preventDefault();
      void forceSubmit();
      return;
    }
    if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
      e.preventDefault();
      void submit();
      return;
    }
    // Bare Enter: sends on desktop, inserts a newline on mobile (per spec —
    // send is via the dedicated button on touch devices).
    if (e.key === 'Enter' && !e.shiftKey && !isMobile) {
      e.preventDefault();
      void submit();
    }
  }

  async function forceSubmit() {
    const text = value.trim();
    if (!text || !onForceSend) return;
    if (busy) return;
    setBusy(true);
    try {
      const body = flattenToMessage(attachments, text);
      await onForceSend(body);
      setValue('');
      setAttachments([]);
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'force send failed');
    } finally {
      setBusy(false);
    }
  }

  return (
    <div className="border-t" style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}>
      <div className="max-w-[1000px] mx-auto w-full">
      {attachments.length > 0 && (
        <div className="flex flex-wrap gap-1.5 px-4 pt-3">
          {attachments.map(a => (
            <AttachmentChip
              key={a.id}
              attachment={a}
              onRemove={removeAttachment}
              onPreview={setPreviewAttachment}
            />
          ))}
        </div>
      )}
      <div className="px-4 py-3 flex items-end gap-2">
        <Textarea
          ref={textareaRef}
          value={value}
          onChange={handleChange}
          onKeyDown={onKeyDown}
          placeholder={
            isMobile
              ? 'Ask about this project. Tap send to submit.'
              : 'Ask about this project. Enter to send · Shift+Enter for newline.'
          }
          disabled={disabled || busy}
          rows={3}
          className="resize-none min-h-[68px] max-h-[240px]"
        />
        <Button
          onClick={() => void submit()}
          disabled={disabled || busy || (!value.trim() && attachments.length === 0)}
          size="icon-lg"
          aria-label="Send"
        >
          <Send />
        </Button>
      </div>
      </div>
      <AttachmentPreviewModal
        attachment={previewAttachment}
        onClose={() => setPreviewAttachment(null)}
      />
      {mentionState?.open && (
        <FileMentionPopover
          query={mentionState.query}
          files={fileIndex ?? []}
          anchorRect={mentionState.anchorRect}
          onSelect={handleMentionSelect}
          onClose={closeMention}
        />
      )}
    </div>
  );
}
