// Sceny + filmy dla wybranego performera. Tab "Scenes" / "Movies".
// Backend filtruje przez `performer_ids=<id>` w /scenes i /movies — oba endpointy
// to przyjmują (movies dodane 2026-05-16 bug-report "aktorzy powinni mieć też movies").
//
// has_playback ZAWSZE on dla obu — TPDB/StashDB stubs bez linków są niewidoczne.
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import * as Sentry from '@sentry/react-native';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import React from 'react';
import {
  ActivityIndicator,
  Alert,
  FlatList,
  Pressable,
  ScrollView,
  StyleSheet,
  Text,
  View,
} from 'react-native';
import { useClient } from '../ClientContext';
import { FavoriteSceneRow } from '../components/FavoriteSceneRow';
import { MoviePosterCard } from '../components/MoviePosterCard';
import { SceneTile, sceneGridProps } from '../components/SceneTile';
import { usePreferences } from '../PreferencesContext';
import { ErrorBoundary } from '../ErrorBoundary';
import type { RootStackParamList } from '../navigation';
import { theme } from '../theme';
import type { SceneOut } from '../types';

type Tab = 'scenes' | 'movies';

const MOVIE_COLS = 2;

export function PerformerScenesScreen() {
  const client = useClient();
  const { gridColumns } = usePreferences();
  const queryClient = useQueryClient();
  const navigation =
    useNavigation<NativeStackNavigationProp<RootStackParamList, 'PerformerScenes'>>();
  const route = useRoute<RouteProp<RootStackParamList, 'PerformerScenes'>>();
  const { id, name, seenSince } = route.params;

  const [tab, setTab] = React.useState<Tab>('scenes');

  const favoritesQuery = useQuery({
    queryKey: ['favorites'],
    queryFn: () => client.listFavorites(),
    staleTime: 30_000,
  });
  const isFavorite = !!favoritesQuery.data?.items.find((f) => f.performer_id === id);

  const addMutation = useMutation({
    mutationFn: () => client.addFavorite(id),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['favorites'] }),
  });
  const removeMutation = useMutation({
    mutationFn: () => client.removeFavorite(id),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['favorites'] }),
  });

  const blacklistMutation = useMutation({
    mutationFn: () => client.addBlacklist('performer', id),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['scenes'] });
      queryClient.invalidateQueries({ queryKey: ['performer-scenes', id] });
      queryClient.invalidateQueries({ queryKey: ['performer-movies', id] });
      navigation.goBack();
    },
  });

  const refreshMutation = useMutation({
    mutationFn: () => client.refreshPerformer(id),
    onSuccess: (data) => {
      queryClient.invalidateQueries({ queryKey: ['performer-scenes', id] });
      Alert.alert(
        'Refresh complete',
        `${data.new_scenes} new scenes pulled across tubes.`,
      );
    },
    onError: (e: any) => {
      const msg = e?.message || 'unknown error';
      Alert.alert('Refresh failed', msg);
    },
  });

  // Top tagi/kategorie performera (chipsy w headerze, bug-report 1a4bf258 — zastąpiły
  // dev-only przycisk Re-scrape). Backend agreguje scene_tags po scenach z żywym
  // playbackiem. Tap → TagScenes. Rescrape (bulk enrich miniaturek/tagów) został
  // przeniesiony do flow per-scene (SceneDetail) — na liście performera był devowy szum.
  const tagsQuery = useQuery({
    queryKey: ['performer-tags', id],
    queryFn: () => client.performerTags(id, 18),
    staleTime: 5 * 60_000,
  });

  const onHide = () => {
    Alert.alert(
      'Hide performer',
      `Hide all scenes featuring ${name}? You can undo this from Settings → Blacklist.`,
      [
        { text: 'Cancel', style: 'cancel' },
        { text: 'Hide', style: 'destructive', onPress: () => blacklistMutation.mutate() },
      ],
    );
  };

  // Mark seen przy wejściu (jeśli już ulubiony) — zerujemy badge nowych scen
  React.useEffect(() => {
    if (isFavorite) {
      client.markFavoriteSeen(id).then(() => {
        queryClient.invalidateQueries({ queryKey: ['favorites'] });
      }).catch(() => {});
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isFavorite, id]);

  React.useLayoutEffect(() => {
    navigation.setOptions({
      title: name,
      headerRight: () => (
        <View style={{ flexDirection: 'row', gap: 16, alignItems: 'center' }}>
          <Pressable onPress={onHide} hitSlop={10}>
            <Text style={{ color: theme.mutedDim, fontSize: 18 }}>🚫</Text>
          </Pressable>
          <Pressable
            onPress={() => (isFavorite ? removeMutation.mutate() : addMutation.mutate())}
            hitSlop={12}
            disabled={addMutation.isPending || removeMutation.isPending}
          >
            <Text style={{ color: isFavorite ? theme.accent : theme.muted, fontSize: 22 }}>
              {isFavorite ? '★' : '☆'}
            </Text>
          </Pressable>
        </View>
      ),
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [navigation, name, isFavorite, addMutation.isPending, removeMutation.isPending]);

  // Filtr tagu IN-PLACE (bug-report 0264a3ff: wybór tagu ma filtrować sceny tej
  // aktorki w tym widoku, nie nawigować do osobnego ekranu). null = bez filtra.
  const [selectedTag, setSelectedTag] = React.useState<{ slug: string; name: string } | null>(null);

  const scenesQuery = useQuery({
    queryKey: ['performer-scenes', id, selectedTag?.slug ?? null],
    queryFn: () =>
      client.listScenes({
        performer_ids: [id],
        tags: selectedTag ? [selectedTag.slug] : undefined,
        sort: 'release_date',
        per_page: 200,
        // Sceny TPDB/StashDB bez tube-linków nie są usable — zawsze filtrujemy.
        has_playback: true,
      }),
  });

  const moviesQuery = useQuery({
    queryKey: ['performer-movies', id],
    queryFn: () =>
      client.listMovies({
        performer_ids: [id],
        sort: 'release_date',
        per_page: 100,
        has_playback: true,
      }),
    // Lazy: fetcha dopiero przy switch na Movies tab. ~16k pandamovies refów
    // → query nie powinna lecieć dla każdego performera od razu.
    enabled: tab === 'movies',
  });

  // Sortowanie: NEW (created_at > seenSince) na górze; reszta po release_date desc
  // jak zwracane z backendu. Bez `seenSince` (entry spoza Favorites) — kolejność nie zmieniana.
  const sortedScenes = React.useMemo<SceneOut[]>(() => {
    const items = scenesQuery.data?.items ?? [];
    if (!seenSince) return items;
    const newOnes: SceneOut[] = [];
    const rest: SceneOut[] = [];
    for (const s of items) {
      if (s.created_at && s.created_at > seenSince) {
        newOnes.push(s);
      } else {
        rest.push(s);
      }
    }
    return [...newOnes, ...rest];
  }, [scenesQuery.data?.items, seenSince]);

  const movies = moviesQuery.data?.items ?? [];
  const scenesTotal = scenesQuery.data?.total ?? 0;
  const scenesSuffix = scenesQuery.data?.total_capped ? '+' : '';
  const moviesTotal = moviesQuery.data?.total ?? 0;

  // Bug-report 2026-05-17 (562cf95c): "Przy przełączaniu na movies app crashed".
  // Breadcrumb na każdy switch żeby przy crash dostać context co user kliknął
  // ostatnie ~30s przed crashem (Sentry domyślnie trzyma ostatnie 100 breadcrumbs).
  const handleTabSwitch = React.useCallback((next: Tab) => {
    Sentry.addBreadcrumb({
      category: 'ui.tab',
      message: `PerformerScenes tab switch → ${next}`,
      level: 'info',
      data: { performerId: id, performerName: name, from: tab, to: next },
    });
    setTab(next);
  }, [id, name, tab]);

  const tabRow = (
    <View style={styles.tabRow}>
      <TabButton
        label="Scenes"
        count={scenesTotal}
        active={tab === 'scenes'}
        onPress={() => handleTabSwitch('scenes')}
      />
      <TabButton
        label="Movies"
        count={moviesTotal}
        active={tab === 'movies'}
        onPress={() => handleTabSwitch('movies')}
      />
    </View>
  );

  return (
    <View style={styles.container}>
      {/* Tab row ZAWSZE widoczny, nawet podczas loading. Wcześniej tabRow
          renderował się tylko jako ListHeaderComponent — pierwsze przejście
          na Movies tab pokazywało blank screen bez sposobu wrotu do Scenes. */}
      {tabRow}
      {tab === 'scenes' ? (
        <>
          {scenesQuery.isLoading && <ActivityIndicator color={theme.fg} />}
          {scenesQuery.error instanceof Error && (
            <Text style={styles.error}>{scenesQuery.error.message}</Text>
          )}
          <FlatList
            {...sceneGridProps(gridColumns)}
            key={`scenes-list-${gridColumns}`}
            data={sortedScenes}
            keyExtractor={(s) => s.id}
            // Android default removeClippedSubviews=true odpina miniaturki poza
            // viewportem i expo-image często nie re-renderuje ich po powrocie →
            // "miniaturki znikają przy scrollu" (bug-report f181d382 2026-06-07).
            removeClippedSubviews={false}
            renderItem={({ item }) => (
              <SceneTile scene={item} seenSince={seenSince} secondLine="studio" />
            )}
            refreshing={scenesQuery.isRefetching}
            onRefresh={scenesQuery.refetch}
            ListHeaderComponent={
              <View>
                <View style={styles.header}>
                  <Text style={styles.subtitle}>
                    {scenesQuery.data
                      ? `${scenesTotal}${scenesSuffix} ${scenesTotal === 1 ? 'scene' : 'scenes'}`
                      : ' '}
                  </Text>
                </View>
                {/* Bug-report 2026-05-17 (f3f019d0): "elementy obsługowe zajmują za
                    dużo ekranu" + 1a4bf258: "Re-scrape mógłby zniknąć, za to tagi/
                    kategorie by mogły". Re-scrape (dev-only bulk enrich) usunięty;
                    Search zostaje, pod nim chipsy top-tagów performera (tap → TagScenes). */}
                <View style={styles.actionRow}>
                  <Pressable
                    style={[
                      styles.actionBtn,
                      refreshMutation.isPending && styles.refreshBtnLoading,
                    ]}
                    onPress={() => refreshMutation.mutate()}
                    disabled={refreshMutation.isPending}
                  >
                    {refreshMutation.isPending ? (
                      <ActivityIndicator color={theme.accent} size="small" />
                    ) : (
                      <Text style={styles.actionBtnTextPrimary}>↻ Search more scenes</Text>
                    )}
                  </Pressable>
                </View>
                {tab === 'scenes' && !!tagsQuery.data?.items.length && (
                  <ScrollView
                    horizontal
                    showsHorizontalScrollIndicator={false}
                    contentContainerStyle={styles.tagRow}
                  >
                    {tagsQuery.data.items.map((t) => {
                      const active = selectedTag?.slug === t.slug;
                      return (
                        <Pressable
                          key={t.id}
                          style={[styles.tagChip, active && styles.tagChipActive]}
                          onPress={() =>
                            setSelectedTag(active ? null : { slug: t.slug, name: t.name })
                          }
                        >
                          <Text
                            style={[styles.tagChipText, active && styles.tagChipTextActive]}
                            numberOfLines={1}
                          >
                            {active ? `${t.name}  ✕` : t.name}
                          </Text>
                        </Pressable>
                      );
                    })}
                  </ScrollView>
                )}
              </View>
            }
            ListEmptyComponent={
              !scenesQuery.isLoading ? <Text style={styles.muted}>no scenes</Text> : null
            }
            contentContainerStyle={{ paddingBottom: 24 }}
          />
        </>
      ) : (
        // Bug-report 2026-05-17 (562cf95c): app crashed przy switch na Movies.
        // Lokalny boundary łapie React errors w drzewie Movies (np. native crash
        // w expo-image jeśli poster_url malformed, lub VirtualizedList z dwu-kolumnowym
        // numColumns). Crash idzie do Sentry z tagiem `boundary:perfscenes-movies`.
        <ErrorBoundary scope="perfscenes-movies">
          {moviesQuery.isLoading && <ActivityIndicator color={theme.fg} />}
          {moviesQuery.error instanceof Error && (
            <Text style={styles.error}>{moviesQuery.error.message}</Text>
          )}
          <FlatList
            // Explicit key wymusza clean remount przy tab switch — bez tego React
            // próbuje reuse'ować VirtualizedList z poprzedniej tab (numColumns=1
            // dla scenes vs numColumns=2 dla movies), co na Androidzie czasem
            // wywala native ViewGroup lifecycle.
            key="movies-list"
            data={movies}
            numColumns={MOVIE_COLS}
            removeClippedSubviews={false}
            keyExtractor={(m) => m.id}
            renderItem={({ item }) => {
              // Defensive: jeśli backend kiedyś wyśle pojedynczy malformed movie,
              // jeden bad item nie wywala całej listy (FlatList catchuje renderItem
              // throws, ale wolimy fallback).
              try {
                return (
                  <MoviePosterCard
                    movie={item}
                    onPress={() => navigation.navigate('MovieDetail', { id: item.id })}
                  />
                );
              } catch (e) {
                Sentry.captureException(e, {
                  tags: { component: 'MoviePosterCard' },
                  contexts: { movie: { id: item?.id, title: item?.title } },
                });
                return null;
              }
            }}
            refreshing={moviesQuery.isRefetching}
            onRefresh={moviesQuery.refetch}
            ListHeaderComponent={
              <View style={styles.header}>
                <Text style={styles.subtitle}>
                  {moviesQuery.data
                    ? `${moviesTotal} ${moviesTotal === 1 ? 'film' : 'films'}`
                    : ' '}
                </Text>
              </View>
            }
            ListEmptyComponent={
              !moviesQuery.isLoading ? (
                <Text style={styles.muted}>no movies for this performer</Text>
              ) : null
            }
            // columnWrapperStyle musi być w obecności numColumns>1 — pusta `data=[]`
            // z columnWrapperStyle ALE bez gap krzyczy w niektórych RN; trzymamy gap.
            columnWrapperStyle={movies.length > 0 ? { gap: 8 } : undefined}
            contentContainerStyle={{ paddingBottom: 24, paddingHorizontal: 6 }}
          />
        </ErrorBoundary>
      )}
    </View>
  );
}

function TabButton({
  label,
  count,
  active,
  onPress,
}: {
  label: string;
  count: number;
  active: boolean;
  onPress: () => void;
}) {
  return (
    <Pressable style={[styles.tabBtn, active && styles.tabBtnActive]} onPress={onPress}>
      <Text style={[styles.tabBtnText, active && styles.tabBtnTextActive]}>
        {label}
      </Text>
      {count > 0 ? (
        <View style={styles.tabCount}>
          <Text style={styles.tabCountText}>{count}</Text>
        </View>
      ) : null}
    </Pressable>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: theme.bg, paddingHorizontal: 12, paddingTop: 8 },
  gridRow: { gap: 10, marginBottom: 14 },
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 8,
    paddingHorizontal: 4,
  },
  subtitle: { color: theme.muted },
  muted: { color: theme.muted, textAlign: 'center', marginTop: 24 },
  error: { color: theme.bad, padding: 12 },
  refreshBtnLoading: { opacity: 0.6 },
  actionRow: {
    flexDirection: 'row',
    gap: 6,
    marginHorizontal: 4,
    marginBottom: 8,
  },
  actionBtn: {
    flex: 1,
    backgroundColor: theme.card,
    borderColor: theme.border,
    borderWidth: 1,
    borderRadius: 8,
    paddingVertical: 6,
    alignItems: 'center',
  },
  actionBtnText: { color: theme.muted, fontWeight: '600', fontSize: 12 },
  actionBtnTextPrimary: { color: theme.accent, fontWeight: '700', fontSize: 12 },
  tagRow: {
    flexDirection: 'row',
    gap: 6,
    paddingHorizontal: 4,
    paddingBottom: 10,
    alignItems: 'center',
  },
  tagChip: {
    backgroundColor: theme.card,
    borderColor: theme.border,
    borderWidth: 1,
    borderRadius: 14,
    paddingVertical: 4,
    paddingHorizontal: 10,
    maxWidth: 200,
  },
  tagChipActive: { backgroundColor: theme.accent, borderColor: theme.accent },
  tagChipText: { color: theme.muted, fontSize: 12, fontWeight: '600' },
  tagChipTextActive: { color: theme.fg, fontWeight: '700' },
  tabRow: { flexDirection: 'row', gap: 8, marginBottom: 8 },
  tabBtn: {
    flex: 1,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    gap: 6,
    paddingVertical: 8,
    borderRadius: 8,
    backgroundColor: theme.card,
    borderColor: theme.border,
    borderWidth: 1,
  },
  tabBtnActive: { borderColor: theme.accent, backgroundColor: theme.bgElevated },
  tabBtnText: { color: theme.muted, fontWeight: '600', fontSize: 14 },
  tabBtnTextActive: { color: theme.fg },
  tabCount: {
    backgroundColor: theme.border,
    paddingHorizontal: 6,
    paddingVertical: 1,
    borderRadius: 4,
  },
  tabCountText: { color: theme.muted, fontWeight: '700', fontSize: 11 },
});
