import Feather from '@expo/vector-icons/Feather'
import {
  BottomSheetBackdrop,
  type BottomSheetModal,
  BottomSheetView,
} from '@gorhom/bottom-sheet'
import { Paths } from 'expo-file-system'
import { type Ref, useCallback, useEffect, useRef, useState } from 'react'
import { Platform, Pressable } from 'react-native'
import { PixelfedBottomSheetModal } from 'src/components/common/BottomSheets'
import { ScreenScrollContainer } from 'src/components/layout'
import {
  BodyText,
  Card,
  FootnoteText,
  SectionHeader,
  SettingRow,
  SubheadText,
} from 'src/components/ui'
import { useConfirmDialog } from 'src/hooks/useConfirmDialog'
import { haptic } from 'src/lib/haptics'
import {
  type AutoEvictPolicy,
  clearAllMediaCaches,
  clearImageCache,
  clearVideoCache,
  getAutoEvictPolicy,
  getCurrentVideoCacheBytes,
  getImageCacheCap,
  getVideoCacheCap,
  IMAGE_CACHE_PRESETS,
  setAutoEvictPolicy,
  setImageCacheCap,
  setVideoCacheCap,
  VIDEO_CACHE_PRESETS,
} from 'src/lib/media/cacheConfig'
import { getAllDraftsBytes } from 'src/lib/media/draft'
import { Separator, Text, useTheme, View, XStack, YStack } from 'tamagui'

// Settings → Storage. Surfaces what Halflight is using on disk and lets the
// user clear caches or change the per-category caps. Drafts are listed but
// never get a clear button — they're user data and disappear on a successful
// post via `useComposer`'s `reset()` → `clearDraft()` path.

function formatBytes(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`
  if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(0)} MB`
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`
}

type Sizes = {
  videos: number
  drafts: number
  available: number
  total: number
}

function readSizes(): Sizes {
  return {
    videos: getCurrentVideoCacheBytes(),
    drafts: getAllDraftsBytes(),
    available: Paths.availableDiskSpace,
    total: Paths.totalDiskSpace,
  }
}

type Policy = AutoEvictPolicy

const POLICY_OPTIONS: { value: Policy; label: string; description: string }[] = [
  { value: 'never', label: 'Never', description: 'Caches stay until you clear them.' },
  {
    value: 'weekly',
    label: 'Weekly',
    description: 'Wipe image and video caches once a week at launch.',
  },
  {
    value: 'monthly',
    label: 'Monthly',
    description: 'Wipe image and video caches once a month at launch.',
  },
]

export default function StorageScreen() {
  const theme = useTheme()
  const { confirm, dialog: confirmDialog } = useConfirmDialog()
  const [sizes, setSizes] = useState<Sizes>(() => readSizes())
  const [videoCap, setVideoCap] = useState<number>(() => getVideoCacheCap())
  const [imageCap, setImageCap] = useState<number>(() => getImageCacheCap())
  const [policy, setPolicy] = useState<Policy>(() => getAutoEvictPolicy())
  const videoSheetRef = useRef<BottomSheetModal>(null)
  const imageSheetRef = useRef<BottomSheetModal>(null)

  // Refresh on mount and after every clear; expo-video's
  // getCurrentVideoCacheSize is synchronous so the read is cheap.
  const refresh = useCallback(() => {
    setSizes(readSizes())
  }, [])

  useEffect(() => {
    refresh()
  }, [refresh])

  const onClearImages = useCallback(async () => {
    haptic.selection()
    await clearImageCache()
    refresh()
  }, [refresh])

  const onClearVideos = useCallback(async () => {
    haptic.selection()
    await clearVideoCache()
    refresh()
  }, [refresh])

  const onClearAll = useCallback(async () => {
    const ok = await confirm({
      title: 'Clear all caches?',
      message:
        'Removes cached images and videos. Your drafts and signed-in accounts are unaffected.',
      confirmLabel: 'Clear caches',
      destructive: true,
    })
    if (!ok) return
    await clearAllMediaCaches()
    refresh()
  }, [confirm, refresh])

  const onPickPolicy = useCallback((next: Policy) => {
    haptic.selection()
    setAutoEvictPolicy(next)
    setPolicy(next)
  }, [])

  const onPickVideoCap = useCallback(async (bytes: number) => {
    haptic.selection()
    await setVideoCacheCap(bytes)
    setVideoCap(bytes)
    videoSheetRef.current?.dismiss()
  }, [])

  const onPickImageCap = useCallback((bytes: number) => {
    haptic.selection()
    setImageCacheCap(bytes)
    setImageCap(bytes)
    imageSheetRef.current?.dismiss()
  }, [])

  const total = sizes.videos + sizes.drafts
  const isIOS = Platform.OS === 'ios'

  return (
    <ScreenScrollContainer variant="scroll" header="Storage">
      <YStack px="$5" pt="$4" pb="$2" gap="$1">
        <BodyText tone="secondary">
          Halflight caches images and videos so the feed stays fast and works offline. The
          OS may also clear these under storage pressure.
        </BodyText>
      </YStack>

      <YStack px="$5" pt="$5" pb="$3" gap="$2">
        <FootnoteText tone="tertiary" letterSpacing={0.4} textTransform="uppercase">
          Halflight is using
        </FootnoteText>
        <SubheadText emphasis="strong" fontSize={28}>
          {formatBytes(total)}
        </SubheadText>
        <FootnoteText tone="secondary">
          {formatBytes(sizes.available)} of {formatBytes(sizes.total)} free on this device
        </FootnoteText>
      </YStack>

      <SectionHeader>Breakdown</SectionHeader>
      <YStack gap={1}>
        {/*
          On Android we don't have a programmatic API to read the live image
          cache size from Coil, and on iOS expo-image exposes no public
          aggregate-size getter either — surfacing a fake number would be
          worse than honest absence. Show the row as "Manage" so the clear
          button stays reachable, and keep videos/drafts on the precise path.
        */}
        <BreakdownRow
          icon="image"
          label="Images"
          subtitle={
            isIOS
              ? `Capped at ${formatBytes(imageCap)}`
              : 'Managed by the system (Android)'
          }
          onClear={onClearImages}
          clearLabel="Clear"
        />
        <Separator borderColor="$borderColor" />
        <BreakdownRow
          icon="video"
          label="Videos"
          value={formatBytes(sizes.videos)}
          onClear={onClearVideos}
          clearLabel="Clear"
        />
        <Separator borderColor="$borderColor" />
        <BreakdownRow
          icon="file-text"
          label="Drafts"
          value={formatBytes(sizes.drafts)}
          subtitle="In-progress posts. Open the composer to manage."
        />
      </YStack>

      <SectionHeader spacing="loose">Limits</SectionHeader>
      <SettingRow
        title="Video cache"
        description="Maximum disk space for cached videos. Older videos drop first."
        onPress={() => videoSheetRef.current?.present()}
        trailing={
          <XStack gap="$2" alignItems="center">
            <FootnoteText tone="secondary">
              {videoCap === 0 ? 'Off' : formatBytes(videoCap)}
            </FootnoteText>
            <Feather name="chevron-right" size={16} color={theme.colorTertiary.val} />
          </XStack>
        }
      />
      {isIOS ? (
        <>
          <Separator borderColor="$borderColor" />
          <SettingRow
            title="Image cache (iOS)"
            description="Upper bound for the image cache. Android manages its own limit."
            onPress={() => imageSheetRef.current?.present()}
            trailing={
              <XStack gap="$2" alignItems="center">
                <FootnoteText tone="secondary">{formatBytes(imageCap)}</FootnoteText>
                <Feather name="chevron-right" size={16} color={theme.colorTertiary.val} />
              </XStack>
            }
          />
        </>
      ) : null}

      <SectionHeader spacing="loose">Auto-clear</SectionHeader>
      {POLICY_OPTIONS.map((opt, idx) => {
        const selected = policy === opt.value
        return (
          <YStack key={opt.value}>
            <SettingRow
              title={opt.label}
              description={opt.description}
              onPress={() => onPickPolicy(opt.value)}
              accessibility={{
                role: 'radio',
                label: opt.label,
                state: { selected },
              }}
              trailing={
                selected ? (
                  <Feather name="check" size={20} color={theme.interactiveActive.val} />
                ) : null
              }
            />
            {idx < POLICY_OPTIONS.length - 1 ? (
              <Separator borderColor="$borderColor" />
            ) : null}
          </YStack>
        )
      })}

      <YStack px="$5" pt="$6" pb="$8" gap="$3">
        <Pressable onPress={onClearAll}>
          <Card
            tone="bordered"
            flexDirection="row"
            justifyContent="center"
            alignItems="center"
            borderColor={theme.colorDanger.val}
          >
            <Text color={theme.colorDanger.val} fontWeight="700">
              Clear all caches
            </Text>
          </Card>
        </Pressable>
        <FootnoteText tone="tertiary">
          Drafts are not part of the cache and are kept until you post or discard them in
          the composer.
        </FootnoteText>
      </YStack>

      <CapPickerSheet
        ref={videoSheetRef}
        title="Video cache"
        subtitle="Pick the maximum disk space for cached videos."
        presets={VIDEO_CACHE_PRESETS as readonly { label: string; bytes: number }[]}
        selected={videoCap}
        onPick={onPickVideoCap}
      />
      <CapPickerSheet
        ref={imageSheetRef}
        title="Image cache"
        subtitle="Pick the maximum disk space for cached images."
        presets={IMAGE_CACHE_PRESETS as readonly { label: string; bytes: number }[]}
        selected={imageCap}
        onPick={onPickImageCap}
      />

      {confirmDialog}
    </ScreenScrollContainer>
  )
}

function BreakdownRow({
  icon,
  label,
  value,
  subtitle,
  onClear,
  clearLabel,
}: {
  icon: 'image' | 'video' | 'file-text'
  label: string
  value?: string
  subtitle?: string
  onClear?: () => void
  clearLabel?: string
}) {
  const theme = useTheme()
  return (
    <Card flexDirection="row" alignItems="center" gap="$3">
      <Feather name={icon} size={20} color={theme.color.val as string} />
      <YStack flex={1} gap={2}>
        <SubheadText emphasis="strong">{label}</SubheadText>
        {subtitle ? <FootnoteText tone="secondary">{subtitle}</FootnoteText> : null}
      </YStack>
      {value ? <FootnoteText tone="secondary">{value}</FootnoteText> : null}
      {onClear ? (
        <Pressable
          onPress={onClear}
          accessibilityRole="button"
          accessibilityLabel={`${clearLabel ?? 'Clear'} ${label.toLowerCase()}`}
        >
          <View paddingHorizontal="$3" paddingVertical="$2">
            <FootnoteText tone="danger">{clearLabel ?? 'Clear'}</FootnoteText>
          </View>
        </Pressable>
      ) : null}
    </Card>
  )
}

function CapPickerSheet({
  title,
  subtitle,
  presets,
  selected,
  onPick,
  ref,
}: {
  title: string
  subtitle: string
  presets: readonly { label: string; bytes: number }[]
  selected: number
  onPick: (bytes: number) => void
  ref: Ref<BottomSheetModal | null>
}) {
  const theme = useTheme()
  const renderBackdrop = useCallback(
    (props: any) => (
      <BottomSheetBackdrop {...props} disappearsOnIndex={-1} appearsOnIndex={0} />
    ),
    []
  )
  return (
    <PixelfedBottomSheetModal
      ref={ref}
      enableDynamicSizing
      backdropComponent={renderBackdrop}
      backgroundStyle={{ backgroundColor: theme.background.val }}
      handleIndicatorStyle={{ backgroundColor: theme.backgroundTertiary.val }}
    >
      <BottomSheetView
        style={{ paddingHorizontal: 16, paddingTop: 8, paddingBottom: 28 }}
      >
        <YStack gap="$3" pb="$3">
          <SubheadText emphasis="strong" fontSize={20}>
            {title}
          </SubheadText>
          <FootnoteText tone="secondary">{subtitle}</FootnoteText>
        </YStack>
        <YStack gap="$1">
          {presets.map((preset) => {
            const isSelected = preset.bytes === selected
            return (
              <Pressable
                key={preset.label}
                onPress={() => onPick(preset.bytes)}
                accessibilityRole="radio"
                accessibilityState={{ selected: isSelected }}
              >
                <XStack
                  paddingHorizontal="$3"
                  paddingVertical="$3"
                  alignItems="center"
                  justifyContent="space-between"
                >
                  <SubheadText>{preset.label}</SubheadText>
                  {isSelected ? (
                    <Feather name="check" size={20} color={theme.interactiveActive.val} />
                  ) : null}
                </XStack>
              </Pressable>
            )
          })}
        </YStack>
      </BottomSheetView>
    </PixelfedBottomSheetModal>
  )
}
