import { useEffect, useRef } from 'react';
import { ScrollView, Text, View } from 'react-native';
import type { Particle } from '@/api/types';
import { cn } from '@/lib/utils';
import { MarkdownBody } from '@/components/MarkdownBody';
import { RelativeTimestamp } from '@/components/RelativeTimestamp';
import { useStreamSafeArea } from './stream-safe-area';

type TextParticle = Extract<Particle, { type: 'text' }>;

interface TextParticleViewProps {
  particle: TextParticle;
  paused: boolean;
  onEnded: () => void;
  onProgress: (ratio: number) => void;
}

// Mirrors desktop's read-duration math (chars/min ≈ 1000, plus +2s per
// link/attachment, clamped 3–15s). Mobile v1 has no attachments and we
// don't extract link previews mid-render, so the formula collapses to
// a length-only base.
const CHARS_PER_MINUTE = 1000;
const MIN_DURATION_S = 3;
const MAX_DURATION_S = 15;
const TICK_MS = 100;
const IMMERSIVE_CHAR_LIMIT = 120;

function computeReadDuration(text: string): number {
  const base = (text.length / CHARS_PER_MINUTE) * 60;
  return Math.min(Math.max(base, MIN_DURATION_S), MAX_DURATION_S);
}

function getImmersiveStyle(length: number) {
  if (length < 30) return { className: 'text-5xl font-semibold leading-tight' };
  if (length < 70) return { className: 'text-3xl font-semibold leading-snug' };
  return { className: 'text-2xl font-normal leading-snug' };
}

// Mirrors desktop's text-particle-view: short plain notes get the immersive
// centered treatment; anything with markdown syntax renders formatted instead
// of showing raw `**asterisks**`. Covers the full GFM set desktop's Crepe
// engine handles — including ~~strikethrough~~ and tables — so short formatted
// messages drop to the rendered card rather than showing raw syntax.
function hasMarkdownFormatting(content: string): boolean {
  return /^#{1,6} |^\s*[-*+] |^\s*\d+\. |^```|`[^`]+`|\*\*|__|\*[^*]|_[^_]|~~|^>|\|.*\|/m.test(
    content,
  );
}

export function TextParticleView({
  particle,
  paused,
  onEnded,
  onProgress,
}: TextParticleViewProps) {
  const content = particle.properties.content;
  const editedAt = particle.properties.edited_at;
  const durationS = computeReadDuration(content);
  const elapsedRef = useRef(0);
  const safe = useStreamSafeArea();

  const editedLabel = editedAt ? (
    <View className="mt-3 items-center">
      <Text className="text-white/40 text-xs">
        edited <RelativeTimestamp date={editedAt} />
      </Text>
    </View>
  ) : null;

  // Reset when the particle changes.
  useEffect(() => {
    elapsedRef.current = 0;
    onProgress(0);
  }, [particle.id, onProgress]);

  useEffect(() => {
    if (paused) return;

    const interval = setInterval(() => {
      elapsedRef.current += TICK_MS / 1000;
      const ratio = Math.min(elapsedRef.current / durationS, 1);
      onProgress(ratio);
      if (ratio >= 1) {
        clearInterval(interval);
        onEnded();
      }
    }, TICK_MS);

    return () => clearInterval(interval);
  }, [paused, durationS, onEnded, onProgress, particle.id]);

  // Immersive (short, plain): centered, large type — feels like a lock-screen
  // note. Short messages that contain markdown fall through to the rendered
  // card so formatting isn't shown as raw syntax.
  if (
    content.length < IMMERSIVE_CHAR_LIMIT &&
    !hasMarkdownFormatting(content)
  ) {
    const style = getImmersiveStyle(content.length);
    return (
      <View
        className="flex-1 items-center justify-center px-8"
        style={{
          paddingTop: safe.top + 16,
          paddingBottom: safe.bottom + 16,
        }}
      >
        <Text
          className={cn('text-white text-center max-w-xl', style.className)}
        >
          {content}
        </Text>
        {editedLabel}
      </View>
    );
  }

  // Long text or markdown: scrollable card so the reader can pace themselves;
  // the duration timer keeps ticking either way, which is intentional — long
  // messages should still auto-advance at the 15s cap. Markdown is rendered
  // via the useMarkdown hook (not the FlatList-based component) so its blocks
  // nest cleanly inside this ScrollView. Padding is pulled from the
  // StreamSafeArea so the card never slips under chrome.
  return (
    <View
      className="flex-1 items-center justify-center px-6"
      style={{
        paddingTop: safe.top + 16,
        paddingBottom: safe.bottom + 16,
      }}
    >
      <ScrollView
        className="max-h-full w-full max-w-xl rounded-2xl bg-white/10"
        contentContainerClassName="px-5 py-5"
        showsVerticalScrollIndicator
        indicatorStyle="white"
      >
        <MarkdownBody content={content} />
        {editedLabel}
      </ScrollView>
    </View>
  );
}
