import Feather from '@expo/vector-icons/Feather'
import { useMemo } from 'react'
import { Linking, Platform, Pressable } from 'react-native'
import { BodyText, FootnoteText } from 'src/components/ui'
import { ICON } from 'src/lib/icons'
import {
  type Classification,
  hasBlockingItem as hasBlockingItemFn,
} from 'src/lib/media/classify'
import { Button, ScrollView, Sheet, Text, useTheme, XStack, YStack } from 'tamagui'

// Compose Phase 5 — pre-process consent gate. Renders one row per picked
// asset showing exactly what's about to happen ("resize from 12 MB", "too
// big — trim it") with the destination instance named in the title. Sits
// between `pickMedia()` and `processMediaBatch()` in the composer hook;
// dismissing the sheet rolls back to no-op (matches the existing "user
// changed their mind" path).

type Props = {
  open: boolean
  onOpenChange: (open: boolean) => void
  /** Instance hostname pre-formatted by `formatHostForLimitMsg()` — passed
   *  pre-formatted so the sheet stays purely presentational. */
  hostLabel: string
  items: Classification[]
  /** User accepted — proceed with `processMediaBatch`. Only enabled when
   *  no item is blocking (too-big-* / unsupported). */
  onConfirm: () => void
  /** User cancelled or swipe-dismissed. Equivalent to "drop the pick". */
  onCancel: () => void
  /** User asked to drop the blocking item from the batch so the sheet's
   *  remaining items can proceed. Identified by the asset URI. */
  onRemoveItem: (uri: string) => void
}

export function ResizeConsentSheet({
  open,
  onOpenChange,
  hostLabel,
  items,
  onConfirm,
  onCancel,
  onRemoveItem,
}: Props) {
  const theme = useTheme()
  const blocking = useMemo(() => hasBlockingItemFn(items), [items])
  const photoCount = items.filter((c) => c.raw.kind === 'photo').length
  const videoCount = items.filter((c) => c.raw.kind === 'video').length

  return (
    <Sheet
      modal
      open={open}
      onOpenChange={(next: boolean) => {
        onOpenChange(next)
        if (!next) onCancel()
      }}
      snapPointsMode="fit"
      dismissOnSnapToBottom
      zIndex={100_000}
    >
      <Sheet.Overlay
        backgroundColor="rgba(0,0,0,0.5)"
        enterStyle={{ opacity: 0 }}
        exitStyle={{ opacity: 0 }}
      />
      <Sheet.Handle backgroundColor={theme.backgroundTertiary.val} />
      <Sheet.Frame
        backgroundColor={theme.background.val}
        paddingHorizontal="$4"
        paddingTop="$3"
        paddingBottom="$5"
        gap="$3"
      >
        <YStack gap="$1">
          <Text fontSize="$7" fontWeight="700" color={theme.color.val}>
            Prepare media for {hostLabel}?
          </Text>
          <FootnoteText tone="secondary">
            {summaryLine(photoCount, videoCount)}
          </FootnoteText>
        </YStack>

        <ScrollView maxHeight={360}>
          <YStack gap="$3">
            {items.map((c) => (
              <ItemRow
                key={c.raw.uri}
                classification={c}
                hostLabel={hostLabel}
                onRemove={() => onRemoveItem(c.raw.uri)}
              />
            ))}
          </YStack>
        </ScrollView>

        <XStack gap="$2" paddingTop="$1">
          {/* Buttons resolve the consent promise via onCancel / onConfirm
              only — the parent closes the sheet by clearing
              `pendingConsent` after the awaited promise settles. Calling
              `onOpenChange(false)` here in addition would race ahead of
              the resolver: PostComposer treats every onOpenChange(false)
              as a cancel (so swipe-to-dismiss counts as cancel), and an
              extra synchronous call fires before onConfirm — silently
              cancelling the accept. */}
          <Button
            flex={1}
            size="$4"
            variant="outlined"
            onPress={onCancel}
            accessibilityLabel="Cancel media upload"
          >
            <Button.Text>Cancel</Button.Text>
          </Button>
          <Button
            flex={2}
            size="$4"
            bg={theme.interactiveActive.val}
            disabled={blocking}
            opacity={blocking ? 0.5 : 1}
            onPress={onConfirm}
            accessibilityLabel={confirmLabel(items)}
          >
            <Button.Text color={theme.background.val} fontWeight="700">
              {confirmLabel(items)}
            </Button.Text>
          </Button>
        </XStack>

        {blocking ? (
          <FootnoteText tone="secondary">
            Remove the items above the limit (or trim them in your gallery and pick again)
            to enable upload.
          </FootnoteText>
        ) : null}
      </Sheet.Frame>
    </Sheet>
  )
}

function ItemRow({
  classification,
  hostLabel,
  onRemove,
}: {
  classification: Classification
  hostLabel: string
  onRemove: () => void
}) {
  const theme = useTheme()
  const c = classification
  const fileLabel = friendlyFileName(c.raw.uri, c.raw.kind)
  const sizeLabel = c.sizeBytes != null ? formatBytes(c.sizeBytes) : 'unknown size'
  const tone = rowTone(c.tag)
  const explanation = explainClassification(c, hostLabel)
  const showRemove =
    c.tag === 'too_big_to_resize' ||
    c.tag === 'too_big_to_compress' ||
    c.tag === 'unsupported_mime'
  const showOpenGallery = c.tag === 'too_big_to_compress'

  return (
    <YStack
      gap="$1"
      paddingVertical="$2"
      paddingHorizontal="$3"
      borderRadius="$3"
      backgroundColor={theme.backgroundTertiary.val}
      borderLeftWidth={3}
      borderLeftColor={tone === 'danger' ? theme.colorDanger.val : theme.borderColor.val}
    >
      <XStack alignItems="center" gap="$2">
        <Feather
          name={c.raw.kind === 'video' ? 'video' : 'image'}
          size={ICON.md}
          color={theme.colorSecondary.val}
        />
        <BodyText emphasis="strong" flex={1} numberOfLines={1}>
          {fileLabel}
        </BodyText>
        <FootnoteText tone="tertiary">{sizeLabel}</FootnoteText>
      </XStack>
      <FootnoteText tone={tone === 'danger' ? 'danger' : 'secondary'}>
        {explanation}
      </FootnoteText>
      {showRemove ? (
        <XStack gap="$2" paddingTop="$1" flexWrap="wrap">
          {showOpenGallery ? <OpenGalleryButton /> : null}
          {showRemove ? (
            <Pressable
              onPress={onRemove}
              accessibilityRole="button"
              accessibilityLabel={`Remove ${fileLabel} from this post`}
              hitSlop={8}
            >
              <FootnoteText tone="link" emphasis="strong">
                Remove from post
              </FootnoteText>
            </Pressable>
          ) : null}
        </XStack>
      ) : null}
    </YStack>
  )
}

function OpenGalleryButton() {
  // Best-effort link to the OS gallery so the user can trim the clip.
  // iOS: `photos-redirect://` opens Photos directly. Android: open the
  // Pictures collection via a generic image MIME intent. Both degrade
  // gracefully if the URL can't be opened (no native crash).
  const onPress = async () => {
    const url =
      Platform.OS === 'ios'
        ? 'photos-redirect://'
        : 'content://media/external/images/media'
    try {
      const supported = await Linking.canOpenURL(url)
      if (supported) await Linking.openURL(url)
    } catch {
      /* ignore — the explanation already tells the user where to go */
    }
  }
  return (
    <Pressable
      onPress={onPress}
      accessibilityRole="button"
      accessibilityLabel="Open the gallery to trim this video"
      hitSlop={8}
    >
      <FootnoteText tone="link" emphasis="strong">
        Open gallery to trim
      </FootnoteText>
    </Pressable>
  )
}

function summaryLine(photos: number, videos: number): string {
  if (photos > 0 && videos > 0) {
    return `${pluralise(photos, 'photo', 'photos')} and ${pluralise(videos, 'video', 'videos')} selected.`
  }
  if (photos > 0) return `${pluralise(photos, 'photo', 'photos')} selected.`
  if (videos > 0) return `${pluralise(videos, 'video', 'videos')} selected.`
  return ''
}

function pluralise(n: number, singular: string, plural: string): string {
  return `${n} ${n === 1 ? singular : plural}`
}

function confirmLabel(items: Classification[]): string {
  const willResize = items.some(
    (c) => c.tag === 'will_resize' || c.tag === 'will_compress'
  )
  if (willResize) return 'Resize & upload'
  return 'Upload'
}

function rowTone(tag: Classification['tag']): 'default' | 'danger' {
  if (
    tag === 'too_big_to_resize' ||
    tag === 'too_big_to_compress' ||
    tag === 'unsupported_mime'
  ) {
    return 'danger'
  }
  return 'default'
}

export function explainClassification(c: Classification, hostLabel: string): string {
  const targetMb = (bytes: number) => formatBytes(bytes)
  switch (c.tag) {
    case 'within_limits':
      return 'Within limits.'
    case 'will_transcode_only':
      return c.raw.kind === 'photo'
        ? 'Will be converted to a server-friendly format.'
        : 'Will be remuxed to MP4.'
    case 'will_resize':
      return `Will resize to fit ${hostLabel}'s ${targetMb(c.targetBytes)} photo limit.`
    case 'will_compress':
      return `Will compress to fit ${hostLabel}'s ${targetMb(c.targetBytes)} video limit.`
    case 'too_big_to_resize':
      if (c.isAnimatedGif) {
        return `${hostLabel} caps photos at ${targetMb(c.targetBytes)}. Animated GIFs can't be safely shrunk — convert to MP4 or pick a smaller file.`
      }
      return `${hostLabel} caps photos at ${targetMb(c.targetBytes)}. Re-export at lower quality and try again.`
    case 'too_big_to_compress': {
      const ratioLabel = `~${Math.round(c.ratio)}× the limit`
      return `${hostLabel} caps videos at ${targetMb(c.targetBytes)} (this is ${ratioLabel}). Trim it in your gallery and try again.`
    }
    case 'unsupported_mime':
      return `${hostLabel} doesn't accept this file format.`
  }
}

function friendlyFileName(uri: string, kind: 'photo' | 'video'): string {
  // expo-image-picker URIs end in `.jpg`/`.mov`/etc. Pull the last segment
  // for a recognisable label; fall back to a generic kind word so the row
  // never renders an empty string.
  try {
    const tail = uri.split('?')[0].split('/').pop()
    if (tail) return decodeURIComponent(tail)
  } catch {
    /* ignore */
  }
  return kind === 'video' ? 'Video' : 'Photo'
}

function formatBytes(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`
  const mb = bytes / (1024 * 1024)
  return mb >= 100 ? `${mb.toFixed(0)} MB` : `${mb.toFixed(1)} MB`
}
