import { useMemo, useRef, useEffect, useState } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';

/**
 * Streaming text with word-by-word reveal animation.
 *
 * Inspired by DeerFlow's Streamdown library:
 * - Splits streaming text into "words" at whitespace and CJK boundaries
 * - Each word gets a CSS fade-in animation
 * - Historical messages render statically (no animation overhead)
 *
 * For non-streaming content, falls back to react-markdown for full
 * markdown rendering including GFM tables, strikethrough, etc.
 */

interface StreamingTextProps {
  content: string;
  isStreaming: boolean;
  className?: string;
  /** Render as markdown for completed messages */
  asMarkdown?: boolean;
}

// Split text into words at whitespace and CJK character boundaries
function splitIntoTokens(text: string): string[] {
  const tokens: string[] = [];
  let current = '';

  for (const char of text) {
    const code = char.codePointAt(0);
    const isCJK = code && (
      (code >= 0x4E00 && code <= 0x9FFF) ||   // CJK Unified Ideographs
      (code >= 0x3400 && code <= 0x4DBF) ||   // CJK Extension A
      (code >= 0x3000 && code <= 0x303F) ||   // CJK Symbols and Punctuation
      (code >= 0xFF00 && code <= 0xFFEF) ||   // Fullwidth Forms
      (code >= 0x2E80 && code <= 0x2EFF) ||   // CJK Radicals Supplement
      (code >= 0xF900 && code <= 0xFAFF)       // CJK Compatibility Ideographs
    );
    const isWhitespace = /\s/.test(char);

    if (isCJK) {
      // CJK chars are individual tokens
      if (current) {
        tokens.push(current);
        current = '';
      }
      tokens.push(char);
    } else if (isWhitespace) {
      current += char;
    } else {
      current += char;
    }
  }

  if (current) {
    tokens.push(current);
  }

  return tokens;
}

export function StreamingText({
  content,
  isStreaming,
  className = '',
  asMarkdown = true,
}: StreamingTextProps) {
  // For completed messages, use full markdown rendering
  if (!isStreaming && asMarkdown) {
    return (
      <div className={`prose-sm prose-gray dark:prose-invert max-w-none ${className}`}>
        <ReactMarkdown remarkPlugins={[remarkGfm]}>
          {content}
        </ReactMarkdown>
      </div>
    );
  }

  // For streaming messages, use token-by-token animation
  if (isStreaming && content) {
    return (
      <StreamingTokenText content={content} className={className} />
    );
  }

  // Empty streaming - show nothing
  return null;
}

/**
 * Token-by-token streaming text with CSS animation.
 * Each token (word/CJK char) fades in sequentially.
 */
function StreamingTokenText({ content, className }: { content: string; className: string }) {
  const tokens = useMemo(() => splitIntoTokens(content), [content]);
  const containerRef = useRef<HTMLSpanElement>(null);
  const [visibleCount, setVisibleCount] = useState(0);

  // Animate tokens appearing
  useEffect(() => {
    if (visibleCount >= tokens.length) return;

    const remaining = tokens.length - visibleCount;
    // Batch reveal: show multiple tokens per frame for fast streaming
    const batchSize = Math.min(remaining, 3);
    const timer = requestAnimationFrame(() => {
      setVisibleCount(prev => Math.min(prev + batchSize, tokens.length));
    });

    return () => cancelAnimationFrame(timer);
  }, [tokens.length, visibleCount]);

  // Reset visible count when content changes significantly
  useEffect(() => {
    setVisibleCount(tokens.length);
  }, [tokens.length]);

  return (
    <span ref={containerRef} className={`whitespace-pre-wrap ${className}`}>
      {tokens.map((token, i) => (
        <span
          key={i}
          className="streaming-token"
          style={{
            opacity: i < visibleCount ? 1 : 0,
            transition: 'opacity 0.15s ease-in',
          }}
        >
          {token}
        </span>
      ))}
      <span className="inline-block w-1.5 h-4 bg-orange-500 animate-pulse ml-0.5 align-text-bottom rounded-sm" />
    </span>
  );
}
