import { useEffect, useRef, type ReactNode } from 'react';
import { Platform, ScrollView, Text, View, type ViewStyle } from 'react-native';
import { Renderer, useMarkdown, type MarkedStyles } from 'react-native-marked';
import type { Particle } from '@/api/types';
import { cn } from '@/lib/utils';
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,
  );
}

// react-native-marked doesn't render GFM task-list checkboxes (marked strips
// the `[ ]`/`[x]` into token flags the parser ignores), so a write/read drift
// shows up as bullets with no box. Swap the marker for a checkbox glyph before
// parsing — read-only, matching desktop's bullet-free checkboxes.
const TASK_ITEM_RE = /^(\s*)[-*+] \[([ xX])\] /gm;

function withTaskCheckboxes(markdown: string): string {
  return markdown.replace(
    TASK_ITEM_RE,
    (_match, indent: string, mark: string) =>
      `${indent}${mark === ' ' ? '☐' : '☑'} `,
  );
}

// Mirror the desktop Crepe palette (markdown-editor.css `--crepe-*`) so a
// message reads the same on both surfaces: white-on-transparent text, a blue
// accent, pink inline code, and a near-opaque dark surface behind code blocks
// and tables. Defined at module scope so the references stay stable —
// `useMarkdown` re-parses only when these or the content change.
//
// Known gap vs desktop: fenced code blocks aren't syntax-highlighted (Crepe
// uses CodeMirror; react-native-marked only exposes the language tag). They
// render as plain monospace on the dark surface, which is acceptable for v1.
const TEXT_COLOR = 'rgba(255,255,255,0.92)';
const ACCENT = '#60a5fa';
const SURFACE = 'rgba(24,24,28,0.96)';
const OUTLINE = 'rgba(255,255,255,0.2)';
const MONO = Platform.OS === 'ios' ? 'Menlo' : 'monospace';

const MARKDOWN_THEME = {
  colors: {
    text: TEXT_COLOR,
    link: ACCENT,
    code: SURFACE,
    border: OUTLINE,
  },
};

const MARKDOWN_STYLES: MarkedStyles = {
  text: { color: TEXT_COLOR, fontSize: 18, lineHeight: 28 },
  li: { color: TEXT_COLOR, fontSize: 18, lineHeight: 28 },
  strong: { fontWeight: '700' },
  em: { fontStyle: 'italic' },
  strikethrough: {
    textDecorationLine: 'line-through',
    color: 'rgba(255,255,255,0.6)',
  },
  // fontStyle "normal" cancels react-native-marked's italic-by-default for
  // links and inline code (desktop renders neither italic).
  link: { color: ACCENT, fontStyle: 'normal' },
  // borderBottomWidth 0 removes the library's default heading underline rule,
  // which desktop's headings don't have.
  h1: {
    color: '#ffffff',
    fontSize: 28,
    lineHeight: 34,
    fontWeight: '700',
    marginTop: 8,
    marginBottom: 8,
    borderBottomWidth: 0,
  },
  h2: {
    color: '#ffffff',
    fontSize: 24,
    lineHeight: 30,
    fontWeight: '700',
    marginTop: 8,
    marginBottom: 6,
    borderBottomWidth: 0,
  },
  h3: {
    color: '#ffffff',
    fontSize: 20,
    lineHeight: 26,
    fontWeight: '600',
    marginTop: 6,
    marginBottom: 4,
  },
  h4: {
    color: '#ffffff',
    fontSize: 18,
    lineHeight: 24,
    fontWeight: '600',
    marginTop: 6,
    marginBottom: 4,
  },
  h5: {
    color: '#ffffff',
    fontSize: 16,
    lineHeight: 22,
    fontWeight: '600',
    marginTop: 4,
    marginBottom: 2,
  },
  h6: {
    color: 'rgba(255,255,255,0.7)',
    fontSize: 15,
    lineHeight: 20,
    fontWeight: '600',
    marginTop: 4,
    marginBottom: 2,
  },
  codespan: {
    color: '#fca5a5',
    fontFamily: MONO,
    fontStyle: 'normal',
    backgroundColor: 'rgba(255,255,255,0.1)',
  },
  code: {
    backgroundColor: SURFACE,
    borderColor: OUTLINE,
    borderWidth: 1,
    borderRadius: 8,
    padding: 12,
    marginVertical: 6,
  },
  blockquote: {
    borderLeftWidth: 3,
    borderLeftColor: OUTLINE,
    paddingLeft: 12,
    marginVertical: 6,
    opacity: 0.85,
  },
  // hr is left to the library default, which already draws a 1px rule in the
  // themed border color (OUTLINE).
  table: { borderWidth: 1, borderColor: OUTLINE, marginVertical: 6 },
  tableRow: { borderColor: OUTLINE },
  tableCell: { borderColor: OUTLINE, padding: 8 },
};

// react-native-marked feeds fenced code blocks the `em` (italic, proportional)
// text style, so out of the box code renders italic in the body font. Override
// `code` to apply a monospace, non-italic style instead — matching desktop's
// code blocks. Instantiated once at module scope to keep the reference stable
// for `useMarkdown`'s memoization.
const CODE_TEXT_STYLE = {
  color: TEXT_COLOR,
  fontFamily: MONO,
  fontSize: 15,
  lineHeight: 22,
};

class MarkdownRenderer extends Renderer {
  code(text: string, language?: string, containerStyle?: ViewStyle): ReactNode {
    return super.code(text, language, containerStyle, CODE_TEXT_STYLE);
  }
}

const MARKDOWN_RENDERER = new MarkdownRenderer();

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 markdownNodes = useMarkdown(withTaskCheckboxes(content), {
    renderer: MARKDOWN_RENDERER,
    theme: MARKDOWN_THEME,
    styles: MARKDOWN_STYLES,
  });

  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"
      >
        {markdownNodes}
        {editedLabel}
      </ScrollView>
    </View>
  );
}
