import * as Linking from 'expo-linking';
import { Link, Stack, useLocalSearchParams } from 'expo-router';
import { useEffect, useMemo, useRef, useState } from 'react';
import {
  ActivityIndicator,
  Platform,
  Pressable,
  ScrollView,
  StyleSheet,
  TextInput,
  View,
} from 'react-native';

import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';
import { MaxContentWidth, Spacing } from '@/constants/theme';
import { useTheme } from '@/hooks/use-theme';
import { getRepo, type Transcript } from '@/lib/db';
import { downloadText } from '@/lib/download';
import {
  detectCitations,
  detectEvents,
  lookupLinksFor,
  resolveCitation,
  toIcs,
  type CandidateEvent,
  type Citation,
  type LookupLinks,
  type RefMetadata,
} from '@/lib/enrich';
import { EXPORT_META, formatTranscript, type ExportFormat } from '@/lib/export';
import { formatClock } from '@/lib/format';
import {
  cardsFromGlossary,
  glossary,
  summarize,
  type GlossaryEntry,
  type SourcedSentence,
} from '@/lib/learn';
import type { Segment } from '@/lib/types';
import { useTranscribe } from '@/stores/transcribeStore';

const ACCENT = '#3c87f7';

export default function TranscriptScreen() {
  const theme = useTheme();
  const { id, t } = useLocalSearchParams<{ id: string; t?: string }>();
  // Deep-link target time (seconds) from a search hit, if any.
  const jumpTo = useMemo(() => {
    const n = Number(t);
    return Number.isFinite(n) && n >= 0 ? n : null;
  }, [t]);

  const [transcript, setTranscript] = useState<Transcript | null | undefined>(undefined);
  const [title, setTitle] = useState('');
  const [segments, setSegments] = useState<Segment[]>([]);
  const [dirty, setDirty] = useState(false);
  const [saving, setSaving] = useState(false);
  const [currentTime, setCurrentTime] = useState(0);

  // Study aids (deterministic, computed on demand — no storage, no model).
  const [aids, setAids] = useState<{
    summary: SourcedSentence[];
    glossary: GlossaryEntry[];
  } | null>(null);
  const [creatingCards, setCreatingCards] = useState(false);
  const [cardsAdded, setCardsAdded] = useState<number | null>(null);

  // Dates & references (Phase 4) — detected on demand, nothing persisted.
  // `enrichment === null` means "not scanned yet".
  const [enrichment, setEnrichment] = useState<{
    events: CandidateEvent[];
    citations: Citation[];
  } | null>(null);
  // Per-citation resolved metadata + in-flight flags, keyed by list index.
  const [refMeta, setRefMeta] = useState<Record<number, RefMetadata>>({});
  const [refLoading, setRefLoading] = useState<Record<number, boolean>>({});

  const scrollRef = useRef<ScrollView | null>(null);
  // Y offset of each rendered segment row, captured via onLayout, for scroll-to.
  const segmentYs = useRef<Record<number, number>>({});
  // Ensure we only auto-jump once per (id, t) deep-link.
  const jumpedRef = useRef(false);

  // Prefer the just-transcribed in-session audio; otherwise load the persisted
  // source media so playback/scrub works after a reload (ROADMAP Phase 0).
  const sessionAudioUrl = useTranscribe((s) => (s.lastTranscriptId === id ? s.audioUrl : undefined));
  const [persistedUrl, setPersistedUrl] = useState<string | undefined>(undefined);
  const audioUrl = sessionAudioUrl ?? persistedUrl;
  const audioRef = useRef<HTMLAudioElement | null>(null);

  useEffect(() => {
    if (sessionAudioUrl) return; // already have in-session audio
    let url: string | undefined;
    let alive = true;
    void getRepo()
      .getMediaUrl(id)
      .then((u) => {
        if (!alive) return;
        url = u;
        setPersistedUrl(u);
      });
    return () => {
      alive = false;
      if (url && Platform.OS === 'web' && url.startsWith('blob:')) URL.revokeObjectURL(url);
    };
  }, [id, sessionAudioUrl]);

  useEffect(() => {
    let alive = true;
    void getRepo()
      .get(id)
      .then((t) => {
        if (!alive) return;
        setTranscript(t ?? null);
        setTitle(t?.title ?? '');
        setSegments(t?.segments ?? []);
      });
    return () => {
      alive = false;
    };
  }, [id]);

  // Web-only <audio> element for click-to-seek playback.
  useEffect(() => {
    if (Platform.OS !== 'web' || !audioUrl) return;
    const el = new Audio(audioUrl);
    audioRef.current = el;
    const onTime = () => setCurrentTime(el.currentTime);
    el.addEventListener('timeupdate', onTime);
    return () => {
      el.pause();
      el.removeEventListener('timeupdate', onTime);
      audioRef.current = null;
    };
  }, [audioUrl]);

  const activeIndex = useMemo(
    () => segments.findIndex((s) => currentTime >= s.start && currentTime < s.end),
    [segments, currentTime],
  );

  const seek = (time: number) => {
    const el = audioRef.current;
    if (!el) return;
    el.currentTime = time;
    void el.play();
  };

  // Reset the one-shot jump guard whenever the deep-link target changes.
  useEffect(() => {
    jumpedRef.current = false;
  }, [id, jumpTo]);

  // Deep-link jump: once the transcript is loaded and we have a finite `t`,
  // seek the audio (if present), highlight the matching segment, and scroll it
  // into view. Works even without audio (scroll/highlight by time only).
  useEffect(() => {
    if (jumpTo === null || jumpedRef.current) return;
    if (!transcript || segments.length === 0) return;

    const target = segments.findIndex((s) => jumpTo >= s.start && jumpTo < s.end);
    const idx = target >= 0 ? target : nearestIndex(segments, jumpTo);
    if (idx < 0) return;

    jumpedRef.current = true;
    // Drive the highlight by time even if audio isn't available.
    setCurrentTime(jumpTo);
    if (audioRef.current) seek(jumpTo);

    // Scroll once the row's Y offset has been measured (next frames).
    const scrollToIdx = () => {
      const y = segmentYs.current[idx];
      if (y === undefined) return false;
      scrollRef.current?.scrollTo({ y: Math.max(0, y - Spacing.four), animated: true });
      return true;
    };
    if (!scrollToIdx()) {
      // Layout may not be measured yet; retry on the next ticks.
      const timers = [requestAnimationFrame(() => { if (!scrollToIdx()) setTimeout(scrollToIdx, 120); })];
      return () => timers.forEach(cancelAnimationFrame);
    }
  }, [jumpTo, transcript, segments, audioUrl]);

  const editSegment = (i: number, text: string) => {
    setSegments((prev) => prev.map((s, idx) => (idx === i ? { ...s, text } : s)));
    setDirty(true);
  };

  const save = async () => {
    setSaving(true);
    try {
      await getRepo().update(id, { title, segments });
      setDirty(false);
    } finally {
      setSaving(false);
    }
  };

  const onExport = (fmt: ExportFormat) => {
    const meta = EXPORT_META[fmt];
    const content = formatTranscript(segments, fmt, { title });
    const safeName = (title || 'transcript').replace(/[^\w.-]+/g, '_');
    downloadText(`${safeName}.${meta.ext}`, meta.mime, content);
  };

  // Compute summary + glossary on demand (pure helpers, nothing persisted).
  const generateAids = () => {
    setAids({ summary: summarize(segments), glossary: glossary(segments) });
    setCardsAdded(null);
  };

  // Seeds derived from the current glossary — drives the "Create N flashcards" label.
  const cardSeeds = useMemo(
    () => (aids ? cardsFromGlossary(aids.glossary) : []),
    [aids],
  );

  const createCards = async () => {
    if (cardSeeds.length === 0 || creatingCards) return;
    setCreatingCards(true);
    try {
      const created = await getRepo().createFlashcards(
        cardSeeds.map((s) => ({
          transcriptId: id,
          courseId: transcript?.courseId ?? null,
          segmentId: s.segmentId,
          start: s.start,
          front: s.front,
          back: s.back,
        })),
      );
      setCardsAdded(created.length);
    } finally {
      setCreatingCards(false);
    }
  };

  // Scan segments for deadlines + citations. Dates are anchored to the lecture
  // date when known, else the transcript's creation time. Pure & local.
  const scanEnrichment = () => {
    const anchorMs = transcript?.lectureDate ?? transcript?.createdAt ?? Date.now();
    setEnrichment({
      events: detectEvents(segments, anchorMs),
      citations: detectCitations(segments),
    });
    setRefMeta({});
    setRefLoading({});
  };

  // Download a calendar file for one or more events.
  const exportIcs = (events: CandidateEvent[]) => {
    if (events.length === 0) return;
    const ics = toIcs(
      events.map((e) => ({ title: e.title, dateMs: e.dateMs, allDay: e.allDay })),
    );
    downloadText('event.ics', 'text/calendar', ics);
  };

  // Network lookup for a single citation (sends only the id string, never text).
  const lookupRef = async (i: number, c: Citation) => {
    if (refLoading[i]) return;
    setRefLoading((prev) => ({ ...prev, [i]: true }));
    try {
      const meta = await resolveCitation(c);
      if (meta) setRefMeta((prev) => ({ ...prev, [i]: meta }));
    } finally {
      setRefLoading((prev) => ({ ...prev, [i]: false }));
    }
  };

  const openLink = (url: string) => {
    void Linking.openURL(url);
  };

  if (transcript === undefined) return <Centered><ActivityIndicator /></Centered>;
  if (transcript === null)
    return (
      <Centered>
        <ThemedText type="small" themeColor="textSecondary">Transcript not found.</ThemedText>
      </Centered>
    );

  return (
    <ThemedView style={styles.fill}>
      <Stack.Screen options={{ title: title || 'Transcript' }} />
      <ScrollView ref={scrollRef} contentContainerStyle={styles.content}>
        <TextInput
          value={title}
          onChangeText={(next) => {
            setTitle(next);
            setDirty(true);
          }}
          style={[styles.title, { color: theme.text }]}
          placeholder="Title"
          placeholderTextColor={theme.textSecondary}
        />

        {!audioUrl && (
          <ThemedText type="small" themeColor="textSecondary">
            No source audio stored for this transcript — playback unavailable.
          </ThemedText>
        )}

        <View style={styles.exportRow}>
          {(Object.keys(EXPORT_META) as ExportFormat[]).map((fmt) => (
            <Pressable
              key={fmt}
              onPress={() => onExport(fmt)}
              style={({ pressed }) => [styles.chip, { backgroundColor: theme.backgroundElement, opacity: pressed ? 0.7 : 1 }]}>
              <ThemedText type="small">{EXPORT_META[fmt].label}</ThemedText>
            </Pressable>
          ))}
        </View>

        {/* Study aids — deterministic helpers, computed on demand. */}
        <ThemedView type="backgroundElement" style={styles.aidsCard}>
          <ThemedText type="smallBold">Study aids</ThemedText>
          <View style={styles.aidsRow}>
            <Pressable
              onPress={generateAids}
              disabled={segments.length === 0}
              style={({ pressed }) => [
                styles.aidBtn,
                { backgroundColor: ACCENT, opacity: segments.length === 0 ? 0.5 : pressed ? 0.85 : 1 },
              ]}>
              <ThemedText style={styles.aidBtnText}>
                {aids ? 'Regenerate summary & glossary' : 'Generate summary & glossary'}
              </ThemedText>
            </Pressable>
            <Link href={{ pathname: '/quiz', params: { id } }} asChild>
              <Pressable
                style={({ pressed }) => [
                  styles.aidBtn,
                  { backgroundColor: theme.backgroundSelected, opacity: pressed ? 0.85 : 1 },
                ]}>
                <ThemedText style={styles.aidBtnTextAlt}>Quiz this lecture</ThemedText>
              </Pressable>
            </Link>
          </View>

          {aids && (
            <>
              {aids.summary.length > 0 ? (
                <View style={styles.aidSection}>
                  <ThemedText type="smallBold" themeColor="textSecondary">Summary</ThemedText>
                  {aids.summary.map((s, i) => (
                    <Pressable key={`sum-${i}`} onPress={() => seek(s.start)} hitSlop={4}>
                      <View style={styles.aidLine}>
                        <ThemedText type="code" themeColor="textSecondary" style={styles.ts}>
                          {formatClock(s.start)}
                        </ThemedText>
                        <ThemedText type="small" style={styles.flex}>{s.text}</ThemedText>
                      </View>
                    </Pressable>
                  ))}
                </View>
              ) : (
                <ThemedText type="small" themeColor="textSecondary">No summary could be derived.</ThemedText>
              )}

              {aids.glossary.length > 0 ? (
                <View style={styles.aidSection}>
                  <ThemedText type="smallBold" themeColor="textSecondary">Glossary</ThemedText>
                  {aids.glossary.map((g, i) => (
                    <Pressable key={`glo-${i}`} onPress={() => seek(g.start)} hitSlop={4}>
                      <View style={styles.aidLine}>
                        <ThemedText type="code" themeColor="textSecondary" style={styles.ts}>
                          {formatClock(g.start)}
                        </ThemedText>
                        <ThemedText type="small" style={styles.flex}>
                          <ThemedText type="smallBold">{g.term}</ThemedText>
                          {` — ${g.definition}`}
                        </ThemedText>
                      </View>
                    </Pressable>
                  ))}
                </View>
              ) : (
                <ThemedText type="small" themeColor="textSecondary">No glossary terms found.</ThemedText>
              )}

              {cardSeeds.length > 0 && (
                <Pressable
                  onPress={() => void createCards()}
                  disabled={creatingCards}
                  style={({ pressed }) => [
                    styles.aidBtn,
                    { backgroundColor: ACCENT, opacity: creatingCards ? 0.6 : pressed ? 0.85 : 1 },
                  ]}>
                  <ThemedText style={styles.aidBtnText}>
                    {creatingCards ? 'Adding…' : `Create ${cardSeeds.length} flashcards`}
                  </ThemedText>
                </Pressable>
              )}

              {cardsAdded !== null && (
                <ThemedText type="small" themeColor="textSecondary">
                  Added {cardsAdded} {cardsAdded === 1 ? 'card' : 'cards'}.
                </ThemedText>
              )}
            </>
          )}
        </ThemedView>

        {/* Dates & references — detected on demand (pure/local detection). */}
        <ThemedView type="backgroundElement" style={styles.aidsCard}>
          <ThemedText type="smallBold">Dates & references</ThemedText>
          <Pressable
            onPress={scanEnrichment}
            disabled={segments.length === 0}
            style={({ pressed }) => [
              styles.aidBtn,
              { backgroundColor: ACCENT, opacity: segments.length === 0 ? 0.5 : pressed ? 0.85 : 1 },
            ]}>
            <ThemedText style={styles.aidBtnText}>
              {enrichment ? 'Re-scan for dates & references' : 'Scan for dates & references'}
            </ThemedText>
          </Pressable>

          {enrichment && (
            <>
              {/* --- Dates --- */}
              <View style={styles.aidSection}>
                <View style={styles.refHeader}>
                  <ThemedText type="smallBold" themeColor="textSecondary">Dates</ThemedText>
                  {enrichment.events.length > 0 && (
                    <Pressable onPress={() => exportIcs(enrichment.events)} hitSlop={6}>
                      <ThemedText type="linkPrimary">Add all ({enrichment.events.length})</ThemedText>
                    </Pressable>
                  )}
                </View>
                {enrichment.events.length === 0 ? (
                  <ThemedText type="small" themeColor="textSecondary">No dates found.</ThemedText>
                ) : (
                  enrichment.events.map((e, i) => (
                    <View key={`evt-${i}`} style={styles.refRow}>
                      <Pressable
                        onPress={() => e.start !== undefined && seek(e.start)}
                        disabled={e.start === undefined}
                        hitSlop={4}
                        style={styles.flex}>
                        <ThemedText type="small">{e.title}</ThemedText>
                        <ThemedText type="small" themeColor="textSecondary">
                          {formatEventDate(e)}
                          {e.start !== undefined ? ` · ${formatClock(e.start)}` : ''}
                        </ThemedText>
                      </Pressable>
                      <Pressable onPress={() => exportIcs([e])} hitSlop={6}>
                        <ThemedText type="link" themeColor="textSecondary">Add to calendar</ThemedText>
                      </Pressable>
                    </View>
                  ))
                )}
              </View>

              {/* --- References --- */}
              <View style={styles.aidSection}>
                <ThemedText type="smallBold" themeColor="textSecondary">References</ThemedText>
                {enrichment.citations.length === 0 ? (
                  <ThemedText type="small" themeColor="textSecondary">No references found.</ThemedText>
                ) : (
                  enrichment.citations.map((c, i) => {
                    const meta = refMeta[i];
                    const links = lookupLinksFor(c, meta);
                    return (
                      <View key={`cit-${i}`} style={styles.citCard}>
                        <View style={styles.refRow}>
                          <Pressable
                            onPress={() => c.start !== undefined && seek(c.start)}
                            disabled={c.start === undefined}
                            hitSlop={4}
                            style={styles.flex}>
                            <ThemedText type="small">{c.raw}</ThemedText>
                            {c.start !== undefined && (
                              <ThemedText type="code" themeColor="textSecondary">
                                {formatClock(c.start)}
                              </ThemedText>
                            )}
                          </Pressable>
                          <Pressable
                            onPress={() => void lookupRef(i, c)}
                            disabled={refLoading[i]}
                            hitSlop={6}>
                            {refLoading[i] ? (
                              <ActivityIndicator size="small" />
                            ) : (
                              <ThemedText type="link" themeColor="textSecondary">
                                {meta ? 'Looked up' : 'Look up'}
                              </ThemedText>
                            )}
                          </Pressable>
                        </View>

                        {meta && (
                          <View style={styles.metaBox}>
                            {meta.title && <ThemedText type="smallBold">{meta.title}</ThemedText>}
                            {(meta.authors?.length || meta.year) && (
                              <ThemedText type="small" themeColor="textSecondary">
                                {[meta.authors?.join(', '), meta.year].filter(Boolean).join(' · ')}
                              </ThemedText>
                            )}
                            <ThemedText type="small" themeColor="textSecondary">{meta.source}</ThemedText>
                          </View>
                        )}

                        <View style={styles.linkChips}>
                          {linkEntries(links).map(([label, url]) => (
                            <Pressable
                              key={label}
                              onPress={() => openLink(url)}
                              style={({ pressed }) => [
                                styles.linkChip,
                                { backgroundColor: theme.backgroundSelected, opacity: pressed ? 0.7 : 1 },
                              ]}>
                              <ThemedText type="small">{label}</ThemedText>
                            </Pressable>
                          ))}
                        </View>
                      </View>
                    );
                  })
                )}
              </View>
            </>
          )}
        </ThemedView>

        {segments.map((s, i) => (
          <View
            key={i}
            onLayout={(e) => {
              segmentYs.current[i] = e.nativeEvent.layout.y;
            }}
            style={[styles.segRow, i === activeIndex && { backgroundColor: theme.backgroundSelected }]}>
            <Pressable onPress={() => seek(s.start)} hitSlop={6}>
              <ThemedText type="code" themeColor="textSecondary" style={styles.ts}>
                {formatClock(s.start)}
              </ThemedText>
            </Pressable>
            <TextInput
              value={s.text}
              onChangeText={(next) => editSegment(i, next)}
              multiline
              style={[styles.segText, { color: theme.text }]}
            />
          </View>
        ))}

        {segments.length === 0 && (
          <ThemedText type="small" themeColor="textSecondary">This transcript has no segments.</ThemedText>
        )}
      </ScrollView>

      {dirty && (
        <Pressable
          onPress={() => void save()}
          disabled={saving}
          style={({ pressed }) => [styles.saveButton, { opacity: saving ? 0.6 : pressed ? 0.85 : 1 }]}>
          <ThemedText style={styles.saveText}>{saving ? 'Saving…' : 'Save changes'}</ThemedText>
        </Pressable>
      )}
    </ThemedView>
  );
}

/** Index of the segment whose start is closest to `time` (fallback for jumps
 *  that don't land inside any segment's [start, end) window). */
function nearestIndex(segments: Segment[], time: number): number {
  if (segments.length === 0) return -1;
  let best = 0;
  let bestDist = Infinity;
  for (let i = 0; i < segments.length; i++) {
    const seg = segments[i];
    if (!seg) continue;
    const dist = Math.abs(seg.start - time);
    if (dist < bestDist) {
      bestDist = dist;
      best = i;
    }
  }
  return best;
}

function Centered({ children }: { children: React.ReactNode }) {
  return <ThemedView style={[styles.fill, styles.centered]}>{children}</ThemedView>;
}

/** Localized date for a candidate event — date-only when it's an all-day item. */
function formatEventDate(e: CandidateEvent): string {
  const d = new Date(e.dateMs);
  return e.allDay
    ? d.toLocaleDateString()
    : d.toLocaleString([], { dateStyle: 'medium', timeStyle: 'short' });
}

/** Ordered, labeled, defined link entries from a LookupLinks bag. */
function linkEntries(links: LookupLinks): [string, string][] {
  const order: [keyof LookupLinks, string][] = [
    ['doi', 'DOI'],
    ['arxiv', 'arXiv'],
    ['openAlex', 'OpenAlex'],
    ['openLibrary', 'Open Library'],
    ['googleBooks', 'Google Books'],
    ['wikipedia', 'Wikipedia'],
    ['annasArchive', "Anna's Archive"],
    ['libgen', 'LibGen'],
  ];
  return order
    .map(([key, label]) => [label, links[key]] as const)
    .filter((pair): pair is [string, string] => typeof pair[1] === 'string' && pair[1].length > 0)
    .map(([label, url]) => [label, url]);
}

const styles = StyleSheet.create({
  fill: { flex: 1 },
  centered: { alignItems: 'center', justifyContent: 'center' },
  content: { padding: Spacing.three, gap: Spacing.two, maxWidth: MaxContentWidth, width: '100%', alignSelf: 'center', paddingBottom: Spacing.six },
  title: { fontSize: 22, fontWeight: '700', paddingVertical: Spacing.two },
  exportRow: { flexDirection: 'row', flexWrap: 'wrap', gap: Spacing.two, marginBottom: Spacing.two },
  chip: { paddingHorizontal: Spacing.three, paddingVertical: Spacing.one, borderRadius: 999 },
  aidsCard: { padding: Spacing.three, borderRadius: Spacing.three, gap: Spacing.two, marginBottom: Spacing.two },
  aidsRow: { flexDirection: 'row', flexWrap: 'wrap', gap: Spacing.two },
  aidBtn: { paddingHorizontal: Spacing.three, paddingVertical: Spacing.two, borderRadius: Spacing.two, alignItems: 'center' },
  aidBtnText: { color: '#fff', fontWeight: '700', fontSize: 14 },
  aidBtnTextAlt: { fontWeight: '700', fontSize: 14 },
  aidSection: { gap: Spacing.one, marginTop: Spacing.one },
  aidLine: { flexDirection: 'row', gap: Spacing.two, alignItems: 'flex-start', paddingVertical: 2 },
  flex: { flex: 1 },
  refHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: Spacing.two },
  refRow: { flexDirection: 'row', gap: Spacing.two, alignItems: 'flex-start', paddingVertical: 2 },
  citCard: { gap: Spacing.one, paddingVertical: Spacing.one },
  metaBox: { gap: 2, paddingLeft: Spacing.two },
  linkChips: { flexDirection: 'row', flexWrap: 'wrap', gap: Spacing.one },
  linkChip: { paddingHorizontal: Spacing.two, paddingVertical: 2, borderRadius: 999 },
  segRow: { flexDirection: 'row', gap: Spacing.two, paddingVertical: Spacing.one, paddingHorizontal: Spacing.two, borderRadius: Spacing.two, alignItems: 'flex-start' },
  ts: { paddingTop: 4, minWidth: 52 },
  segText: { flex: 1, fontSize: 16, lineHeight: 24, padding: 0 },
  saveButton: { position: 'absolute', bottom: Spacing.three, right: Spacing.three, left: Spacing.three, maxWidth: MaxContentWidth, alignSelf: 'center', backgroundColor: '#3c87f7', paddingVertical: Spacing.three, borderRadius: Spacing.three, alignItems: 'center' },
  saveText: { color: '#fff', fontWeight: '700' },
});
