import * as Linking from 'expo-linking';
import { Stack, useLocalSearchParams } from 'expo-router';
import { useCallback, useState } from 'react';
import { ActivityIndicator, Pressable, ScrollView, StyleSheet, 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 } from '@/lib/db';
import { downloadText } from '@/lib/download';
import {
  detectCitations,
  lookupLinksFor,
  resolveCitation,
  toBibtex,
  toRis,
  type Citation,
  type LookupLinks,
  type RefMetadata,
} from '@/lib/enrich';

const ACCENT = '#3c87f7';

/** A detected citation paired with its (best-effort) resolved metadata. */
interface BibEntry {
  citation: Citation;
  meta?: RefMetadata;
}

export default function BibliographyScreen() {
  const theme = useTheme();
  const { courseId } = useLocalSearchParams<{ courseId?: string }>();

  const [entries, setEntries] = useState<BibEntry[] | null>(null);
  const [building, setBuilding] = useState(false);

  // Collect citations across the course's (or all) transcripts, then resolve
  // each via the metadata APIs. Detection is local; lookups send only ids.
  const build = useCallback(async () => {
    setBuilding(true);
    setEntries(null);
    try {
      const repo = getRepo();
      const metas = courseId ? await repo.listByCourse(courseId) : await repo.list();

      // Detect citations across every transcript body. Dedupe by kind+value so
      // a reference cited in several lectures shows once.
      const byKey = new Map<string, Citation>();
      for (const m of metas) {
        const full = await repo.get(m.id);
        if (!full) continue;
        for (const c of detectCitations(full.segments)) {
          const key = `${c.kind}:${c.value}`;
          if (!byKey.has(key)) byKey.set(key, c);
        }
      }
      const citations = [...byKey.values()];
      // Seed the list immediately (links work without lookups), then resolve.
      setEntries(citations.map((citation) => ({ citation })));

      const resolved = await Promise.all(
        citations.map(async (citation) => {
          try {
            return { citation, meta: await resolveCitation(citation) };
          } catch {
            return { citation };
          }
        }),
      );
      setEntries(resolved);
    } finally {
      setBuilding(false);
    }
  }, [courseId]);

  // Resolved metadata feeds the exporters; fall back to the raw citation text
  // for entries that couldn't be looked up so nothing is silently dropped.
  const exportable = (entries ?? [])
    .map((e) => e.meta ?? fallbackMeta(e.citation))
    .filter((m): m is RefMetadata => m !== undefined);

  const exportBibtex = () => {
    if (exportable.length === 0) return;
    downloadText('bibliography.bib', 'application/x-bibtex', toBibtex(exportable));
  };

  const exportRis = () => {
    if (exportable.length === 0) return;
    downloadText('bibliography.ris', 'application/x-research-info-systems', toRis(exportable));
  };

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

  return (
    <ThemedView style={styles.fill}>
      <Stack.Screen options={{ title: 'Bibliography' }} />
      <ScrollView contentContainerStyle={styles.content}>
        <ThemedText type="small" themeColor="textSecondary">
          {courseId
            ? 'References detected across this course’s lectures.'
            : 'References detected across all lectures.'}
        </ThemedText>

        <View style={styles.row}>
          <Pressable
            onPress={() => void build()}
            disabled={building}
            style={({ pressed }) => [
              styles.btn,
              { backgroundColor: ACCENT, opacity: building ? 0.6 : pressed ? 0.85 : 1 },
            ]}>
            <ThemedText style={styles.btnText}>
              {building ? 'Scanning…' : entries ? 'Re-scan' : 'Build bibliography'}
            </ThemedText>
          </Pressable>
          {entries && entries.length > 0 && (
            <>
              <Pressable
                onPress={exportBibtex}
                style={({ pressed }) => [
                  styles.btn,
                  { backgroundColor: theme.backgroundElement, opacity: pressed ? 0.7 : 1 },
                ]}>
                <ThemedText type="smallBold">Export BibTeX</ThemedText>
              </Pressable>
              <Pressable
                onPress={exportRis}
                style={({ pressed }) => [
                  styles.btn,
                  { backgroundColor: theme.backgroundElement, opacity: pressed ? 0.7 : 1 },
                ]}>
                <ThemedText type="smallBold">Export RIS</ThemedText>
              </Pressable>
            </>
          )}
        </View>

        {building && entries === null && <ActivityIndicator style={styles.pad} />}

        {entries && entries.length === 0 && (
          <ThemedText type="small" themeColor="textSecondary" style={styles.pad}>
            No references found.
          </ThemedText>
        )}

        {entries?.map((e, i) => {
          const links = lookupLinksFor(e.citation, e.meta);
          return (
            <ThemedView key={`${e.citation.kind}:${e.citation.value}:${i}`} type="backgroundElement" style={styles.card}>
              {e.meta?.title ? (
                <ThemedText type="smallBold">{e.meta.title}</ThemedText>
              ) : (
                <ThemedText type="smallBold">{e.citation.raw}</ThemedText>
              )}
              {(e.meta?.authors?.length || e.meta?.year) && (
                <ThemedText type="small" themeColor="textSecondary">
                  {[e.meta?.authors?.join(', '), e.meta?.year].filter(Boolean).join(' · ')}
                </ThemedText>
              )}
              <ThemedText type="small" themeColor="textSecondary">
                {e.meta?.source ?? e.citation.kind}
              </ThemedText>
              <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>
            </ThemedView>
          );
        })}
      </ScrollView>
    </ThemedView>
  );
}

/** Minimal metadata so an unresolved citation still exports something useful. */
function fallbackMeta(c: Citation): RefMetadata {
  return { title: c.raw, source: c.kind };
}

/** 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 },
  content: { padding: Spacing.three, gap: Spacing.two, maxWidth: MaxContentWidth, width: '100%', alignSelf: 'center' },
  row: { flexDirection: 'row', flexWrap: 'wrap', gap: Spacing.two, alignItems: 'center' },
  btn: { paddingHorizontal: Spacing.three, paddingVertical: Spacing.two, borderRadius: Spacing.two, alignItems: 'center' },
  btnText: { color: '#fff', fontWeight: '700', fontSize: 14 },
  card: { padding: Spacing.three, borderRadius: Spacing.three, gap: 2 },
  linkChips: { flexDirection: 'row', flexWrap: 'wrap', gap: Spacing.one, marginTop: Spacing.one },
  linkChip: { paddingHorizontal: Spacing.two, paddingVertical: 2, borderRadius: 999 },
  pad: { paddingVertical: Spacing.four, textAlign: 'center' },
});
