import { useLayoutEffect, useRef, useState } from "preact/hooks";
import { Paperclip, Search, SendHorizontal } from "lucide-preact";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";

type MutableValueRef = {
  current: string;
};

type Props = {
  draftRef: MutableValueRef;
  draftRevision: number;
  error: string | null;
  isSearchMode: boolean;
  isSending: boolean;
  pendingAttachmentCount: number;
  attachmentButtonDisabled: boolean;
  onOpenAttachmentPicker: () => void;
  onPaste: (event: ClipboardEvent) => void;
  onSend: (draft: string) => void | Promise<void>;
};

const MIRROR_SENTINEL = "\u200b";
const HAS_NON_WHITESPACE = /\S/;

export function ChatComposer({
  draftRef,
  draftRevision,
  error,
  isSearchMode,
  isSending,
  pendingAttachmentCount,
  attachmentButtonDisabled,
  onOpenAttachmentPicker,
  onPaste,
  onSend,
}: Props) {
  // The draft stays in the DOM/ref so typing never schedules a render of the workspace transcript.
  const textareaContainerRef = useRef<HTMLDivElement>(null);
  const mirrorRef = useRef<HTMLDivElement>(null);
  const [hasDraft, setHasDraft] = useState(() => HAS_NON_WHITESPACE.test(draftRef.current));
  const hasDraftRef = useRef(hasDraft);

  const getTextarea = () => textareaContainerRef.current?.querySelector("textarea") ?? null;

  const updateMirror = (value: string) => {
    // The overlapping mirror lets normal layout size the textarea without synchronous scrollHeight reads.
    if (mirrorRef.current) mirrorRef.current.textContent = `${value}${MIRROR_SENTINEL}`;
  };

  const updateHasDraft = (value: string) => {
    const nextHasDraft = HAS_NON_WHITESPACE.test(value);
    if (nextHasDraft !== hasDraftRef.current) {
      hasDraftRef.current = nextHasDraft;
      setHasDraft(nextHasDraft);
    }
  };

  useLayoutEffect(() => {
    const value = draftRef.current;
    const textarea = getTextarea();
    if (textarea && textarea.value !== value) {
      textarea.value = value;
    }
    updateMirror(value);
    updateHasDraft(value);
  }, [draftRevision]);

  const submit = () => {
    const textarea = getTextarea();
    const draft = textarea?.value ?? draftRef.current;
    const canSend = HAS_NON_WHITESPACE.test(draft) || (!isSearchMode && pendingAttachmentCount > 0);
    if (isSending || !canSend) return;

    draftRef.current = "";
    if (textarea) textarea.value = "";
    updateMirror("");
    updateHasDraft("");
    void onSend(draft);
  };

  return (
    <>
      <div ref={textareaContainerRef} className="grid max-h-40 min-h-0 overflow-hidden">
        <div
          ref={mirrorRef}
          className="pointer-events-none invisible col-start-1 row-start-1 max-h-40 min-h-0 overflow-x-hidden overflow-y-auto whitespace-pre-wrap break-words px-3 py-3 text-base"
          aria-hidden="true"
        />
        <Textarea
          id="composer-input"
          rows={1}
          onInput={(event) => {
            const value = event.currentTarget.value;
            draftRef.current = value;
            updateMirror(value);
            updateHasDraft(value);
          }}
          onPaste={(event) => {
            onPaste(event);
          }}
          onKeyDown={(event) => {
            if (event.key === "Enter" && !event.shiftKey && !event.isComposing) {
              event.preventDefault();
              submit();
            }
          }}
          placeholder={isSearchMode ? "Search the web" : "Enter prompt..."}
          className="col-start-1 row-start-1 h-full max-h-40 min-h-0 resize-none overflow-y-auto border-0 bg-transparent px-3 py-3 text-base text-violet-50 shadow-none placeholder:text-violet-200/45 focus-visible:ring-0"
          disabled={isSending}
        />
      </div>
      <div className={cn("flex items-center gap-3 px-2 pb-1", error ? "justify-between" : "justify-end")}>
        {error ? <p className="min-w-0 truncate text-xs text-rose-300">{error}</p> : null}
        {!isSearchMode ? (
          <Button
            className="h-10 w-10 rounded-lg"
            onClick={onOpenAttachmentPicker}
            size="icon"
            variant="secondary"
            disabled={attachmentButtonDisabled}
            aria-label="Attach files"
          >
            <Paperclip className="h-4 w-4" />
          </Button>
        ) : null}
        <Button
          className="h-10 w-10 rounded-lg"
          onClick={submit}
          size="icon"
          disabled={isSending || (!hasDraft && (isSearchMode || pendingAttachmentCount === 0))}
          aria-label={isSearchMode ? "Search" : "Send message"}
        >
          {isSearchMode ? <Search className="h-4 w-4" /> : <SendHorizontal className="h-4 w-4" />}
        </Button>
      </div>
    </>
  );
}
