{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "predictive-text",
  "type": "registry:component",
  "title": "Predictive Text",
  "description": "Suggests next words in real time while users type naturally.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "files": [
    {
      "path": "components/watermelon/predictive-text.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';\nimport { motion, AnimatePresence } from 'motion/react';\nimport { ArrowUp, ImageIcon, Mic } from 'lucide-react';\n\ninterface PredictiveInputProps {\n  dictionary?: string[];\n  placeholder?: string;\n  onSend?: (text: string) => void;\n  className?: string;\n}\n\nconst DEFAULT_WORDS = [\n  \"what\", \"whatever\", \"what's\", \"bright\", \"brighter\", \"brigade\",\n  \"sunny\", \"sunset\", \"sun\", \"day\", \"dance\", \"data\", \"a\", \"an\", \"any\"\n];\n\nexport const PredictiveText: React.FC<PredictiveInputProps> = ({\n  dictionary = DEFAULT_WORDS,\n  placeholder = \"Write a message\",\n  onSend,\n  className = \"\"\n}) => {\n  const [text, setText] = useState(\"\");\n\n  const [activeSuggestionIndex, setActiveSuggestionIndex] = useState<number>(-1);\n  const [wordFrequency, setWordFrequency] = useState<Record<string, number>>({});\n  const inputRef = useRef<HTMLInputElement>(null);\n\n  // Build a combined dictionary from the provided list + frequently used words\n  const enrichedDictionary = useCallback(() => {\n    const freqWords = Object.keys(wordFrequency).sort(\n      (a, b) => wordFrequency[b] - wordFrequency[a]\n    );\n    // Merge freq words first (for priority), then dedupe with base dictionary\n    return Array.from(new Set([...freqWords, ...dictionary]));\n  }, [dictionary, wordFrequency]);\n\n  // Derive lastWord and suggestions during render\n  const lastWord = useMemo(() => {\n    const words = text.split(/\\s+/);\n    return words[words.length - 1].toLowerCase();\n  }, [text]);\n\n  const suggestions = useMemo(() => {\n    if (lastWord.length > 0) {\n      const dict = enrichedDictionary();\n      return dict\n        .filter(word => word.toLowerCase().startsWith(lastWord) && word.toLowerCase() !== lastWord)\n        .slice(0, 3);\n    }\n    return [];\n  }, [lastWord, enrichedDictionary]);\n\n  // Reset active suggestion whenever text changes via typing\n  useEffect(() => {\n    requestAnimationFrame(() => setActiveSuggestionIndex(-1));\n  }, [text]);\n\n  const applySuggestion = useCallback((suggestion: string) => {\n    const words = text.split(/\\s+/);\n    words[words.length - 1] = suggestion;\n    const newText = words.join(\" \") + \" \";\n    setText(newText);\n    setActiveSuggestionIndex(-1);\n    inputRef.current?.focus();\n  }, [text]);\n\n  const handleSend = useCallback(() => {\n    if (!text.trim()) return;\n\n    // Track word frequency for smarter future suggestions\n    const usedWords = text.trim().toLowerCase().split(/\\s+/);\n    setWordFrequency(prev => {\n      const updated = { ...prev };\n      usedWords.forEach(w => {\n        updated[w] = (updated[w] ?? 0) + 1;\n      });\n      return updated;\n    });\n\n    onSend?.(text);\n    setText(\"\");\n  }, [text, onSend]);\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n    switch (e.key) {\n      case \"Enter\": {\n        if (activeSuggestionIndex >= 0 && suggestions[activeSuggestionIndex]) {\n          e.preventDefault();\n          applySuggestion(suggestions[activeSuggestionIndex]);\n        } else {\n          e.preventDefault();\n          handleSend();\n        }\n        break;\n      }\n\n      case \"Tab\": {\n        if (suggestions.length > 0) {\n          e.preventDefault();\n          // Cycle through suggestions, -1 means none selected\n          const next = (activeSuggestionIndex + 1) % suggestions.length;\n          setActiveSuggestionIndex(next);\n        }\n        break;\n      }\n\n      case \"ArrowRight\": {\n        // Accept first suggestion on ArrowRight when cursor is at end\n        const input = inputRef.current;\n        if (\n          suggestions.length > 0 &&\n          input &&\n          input.selectionStart === text.length\n        ) {\n          e.preventDefault();\n          applySuggestion(suggestions[0]);\n        }\n        break;\n      }\n\n      case \"Escape\": {\n        if (text.length > 0) {\n          e.preventDefault();\n          setText(\"\");\n        }\n        break;\n      }\n\n      default:\n        break;\n    }\n  };\n\n  return (\n    <div className={`w-full flex flex-col items-center justify-center p-4 sm:p-6 antialiased select-none ${className}`}>\n      <div className=\"relative w-full max-w-[95%] sm:max-w-md flex flex-col items-start mb-10 sm:mb-20\">\n\n        <div className=\"h-10 sm:h-12 w-full flex justify-start items-center mb-3\">\n          <AnimatePresence>\n            {suggestions.length > 0 && (\n              <motion.div\n                initial={{ opacity: 0, y: 10, scale: 0.95 }}\n                animate={{ opacity: 1, y: 0, scale: 1 }}\n                exit={{ opacity: 0, y: 10, scale: 0.95 }}\n                transition={{ type: \"spring\", stiffness: 500, damping: 30 }}\n                className=\"flex items-center gap-0.5 border-2 px-1 py-1 rounded-full shadow-sm transition-colors bg-white border-neutral-100 dark:bg-neutral-900 dark:border-neutral-800\"\n              >\n                {suggestions.map((word, i) => (\n                  <button\n                    key={word}\n                    onClick={() => applySuggestion(word)}\n                    className={`px-3 sm:px-4 py-1 text-xs sm:text-sm font-bold transition-colors whitespace-nowrap\n                      ${i === activeSuggestionIndex\n                        ? 'text-blue-500 dark:text-blue-400'\n                        : 'text-neutral-400 hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300'}\n                      ${i !== 0 ? 'border-l-2 pl-3 sm:pl-4 border-neutral-100 dark:border-neutral-800' : ''}\n                    `}\n                  >\n                    {word}\n                  </button>\n                ))}\n              </motion.div>\n            )}\n          </AnimatePresence>\n        </div>\n\n        <div className=\"relative w-full group\">\n          <input\n            ref={inputRef}\n            type=\"text\"\n            value={text}\n            onChange={(e) => setText(e.target.value)}\n            onKeyDown={handleKeyDown}\n            placeholder={placeholder}\n            className=\"w-full border-none rounded-4xl sm:rounded-[22px] shadow-sm py-3.5 sm:py-4 px-5 sm:px-6 pr-20 sm:pr-24 text-sm sm:text-base outline-none transition-all font-bold tracking-wide \n              bg-neutral-100 text-black placeholder:text-neutral-400 focus:ring-1 focus:ring-neutral-200\n              dark:bg-neutral-900 dark:text-white dark:placeholder:text-neutral-600 dark:focus:ring-neutral-800\"\n          />\n\n          <div className=\"absolute right-2 sm:right-3 top-1/2 -translate-y-1/2 flex items-center gap-2 sm:gap-3\">\n            <AnimatePresence mode=\"wait\">\n              {text.length > 0 ? (\n                <motion.button\n                  key=\"send-btn\"\n                  initial={{ opacity: 0, scale: 0.8 }}\n                  animate={{ opacity: 1, scale: 1 }}\n                  exit={{ opacity: 0, scale: 0.8 }}\n                  onClick={handleSend}\n                  className=\"w-8 h-8 sm:w-10 sm:h-10 rounded-full flex items-center justify-center transition-all active:scale-90 bg-neutral-900 text-white dark:bg-white dark:text-black shadow-md\"\n                >\n                  <ArrowUp size={18} strokeWidth={3} />\n                </motion.button>\n              ) : (\n                <motion.div\n                  key=\"placeholder-icons\"\n                  initial={{ opacity: 0 }}\n                  animate={{ opacity: 1 }}\n                  exit={{ opacity: 0 }}\n                  className=\"flex items-center gap-3 sm:gap-4 pr-1 sm:pr-2 text-neutral-400 dark:text-neutral-600\"\n                >\n                  <ImageIcon size={20} className=\"cursor-pointer hover:text-neutral-600 dark:hover:text-neutral-400 transition-colors\" />\n                  <Mic size={20} className=\"cursor-pointer hover:text-neutral-600 dark:hover:text-neutral-400 transition-colors\" />\n                </motion.div>\n              )}\n            </AnimatePresence>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};"
    }
  ]
}
