import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useState, useEffect, useRef, useMemo, useCallback } from 'react'
import { useSearchParams, Link as RouterLink } from 'react-router-dom'
import { useBreadcrumb } from '../../hooks/useBreadcrumb'
import { breadcrumbConfig } from '../../config/breadcrumbConfig'
import {
  ScrollText,
  Download,
  Calendar,
  User,
  Image as ImageIcon,
  Video,
  File,
  ChevronLeft,
  X,
  Heart,
  Loader2,
  Edit,
  RefreshCw,
  Save,
  Link,
  AlertTriangle,
  LayoutGrid,
  Tag,
  Check,
  CheckSquare,
  Square,
  Music,
  Trash2,
  Plus,
  Columns,
  Pin,
  ChevronDown,
  ImagePlus,
  Play,
  Lock,
  ExternalLink,
  MessageSquare,
  Eye,
  EyeOff,
  Clock,
} from 'lucide-react'
import { FilterBar, FilterSection, ActiveFilter } from '../../components/FilterPopover'
import { api, wsClient, PaidContentPost, PaidContentAttachment, PaidContentCreator, PaidContentCreatorGroup } from '../../lib/api'
import { formatRelativeTime, formatPlatformName, decodeHtmlEntities, cleanCaption, THUMB_CACHE_V } from '../../lib/utils'
import PostDetailView from '../../components/paid-content/PostDetailView'
import BundleLightbox from '../../components/paid-content/BundleLightbox'
import { CopyToGalleryModal } from '../../components/private-gallery/CopyToGalleryModal'
import { useEnabledFeatures } from '../../hooks/useEnabledFeatures'
import TagSearchSelector from '../../components/private-gallery/TagSearchSelector'

// Alias for backwards compatibility in this file
const getServiceDisplayName = formatPlatformName

interface FeedFilters {
  service?: string
  platform?: string
  creator_id?: number
  creator_ids?: number[]
  creator_group_id?: number
  content_type?: string  // 'image', 'video', 'audio'
  min_resolution?: string  // '720p', '1080p', '1440p', '4k'
  tag_id?: number
  tagged_user?: string
  favorites_only: boolean
  unviewed_only: boolean
  downloaded_only: boolean
  has_missing: boolean
  missing_description: boolean
  hide_empty: boolean
  date_from?: string
  date_to?: string
  sort_by: string
  sort_order: 'asc' | 'desc'
  search?: string
}

// Format file size helper
function formatFileSize(bytes: number | null): string {
  if (!bytes) return ''
  if (bytes < 1024) return `${bytes} B`
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
  if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`
}

// Format duration helper
function formatDuration(seconds: number | null): string {
  if (!seconds) return ''
  const mins = Math.floor(seconds / 60)
  const secs = Math.floor(seconds % 60)
  if (mins > 0) return `${mins}:${secs.toString().padStart(2, '0')}`
  return `0:${secs.toString().padStart(2, '0')}`
}

// Generate display title for posts without titles
function getPostDisplayTitle(post: PaidContentPost): string {
  // Use actual title if available
  if (post.title && post.title.trim()) {
    return post.title.trim()
  }

  // Build a descriptive title from available data
  const parts: string[] = []

  // Add date if available
  if (post.published_at) {
    const date = new Date(post.published_at)
    parts.push(date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }))
  }

  // Add attachment info
  if (post.attachment_count && post.attachment_count > 0) {
    const imageCount = post.attachments?.filter(a => a.file_type === 'image').length || 0
    const videoCount = post.attachments?.filter(a => a.file_type === 'video').length || 0

    if (videoCount > 0 && imageCount > 0) {
      parts.push(`${imageCount} images, ${videoCount} videos`)
    } else if (videoCount > 0) {
      parts.push(`${videoCount} ${videoCount === 1 ? 'video' : 'videos'}`)
    } else if (imageCount > 0) {
      parts.push(`${imageCount} ${imageCount === 1 ? 'image' : 'images'}`)
    } else {
      parts.push(`${post.attachment_count} ${post.attachment_count === 1 ? 'file' : 'files'}`)
    }
  }

  // If we have parts, join them
  if (parts.length > 0) {
    return parts.join(' — ')
  }

  // Last resort: use post_id
  return `Post ${post.post_id?.slice(-8) || post.id}`
}

function AttachmentThumbnail({
  attachment,
  onClick,
  onImport
}: {
  attachment: PaidContentAttachment
  onClick?: () => void
  onImport?: (attachment: PaidContentAttachment) => void
}) {
  const [thumbnailError, setThumbnailError] = useState(false)
  // Preview frames are DRM video thumbnails (jpg) — treat as images, not videos
  const pvImageExts = ['jpg', 'jpeg', 'png', 'webp', 'gif']
  const isPF = attachment.file_type === 'video' && pvImageExts.includes(attachment.extension?.toLowerCase() || '')
  const isImage = attachment.file_type === 'image' || isPF
  const isVideo = attachment.file_type === 'video' && !isPF
  const fileUrl = attachment.local_path
    ? attachment.file_token ? `/api/paid-content/files/serve?t=${encodeURIComponent(attachment.file_token)}` : `/api/paid-content/files/serve?path=${encodeURIComponent(attachment.local_path)}`
    : null
  const isMissing = attachment.status === 'failed' || attachment.status === 'pending'

  const handleClick = () => {
    if (onClick) {
      onClick()
    } else if (fileUrl && attachment.status === 'completed') {
      window.open(fileUrl, '_blank')
    }
  }

  // Build metadata string for tooltip
  const metadata: string[] = []
  if (attachment.name) metadata.push(attachment.name)
  if (attachment.file_size) metadata.push(formatFileSize(attachment.file_size))
  if (attachment.width && attachment.height) metadata.push(`${attachment.width}×${attachment.height}`)
  if (attachment.duration) metadata.push(formatDuration(attachment.duration))

  const canShowThumbnail = (isImage || isVideo) && attachment.status === 'completed' && attachment.id && !thumbnailError
  // Use file_hash as cache buster so thumbnails refresh when a file is re-uploaded
  const thumbCacheBuster = attachment.file_hash ? `v=${attachment.file_hash.slice(0, 8)}` : THUMB_CACHE_V

  return (
    <div
      className={`${isVideo ? 'aspect-video' : 'aspect-square'} bg-secondary rounded-lg overflow-hidden relative group ${
        attachment.status === 'completed' ? 'cursor-pointer hover:ring-2 hover:ring-primary' : ''
      }`}
      onClick={handleClick}
      title={metadata.join(' • ')}
    >
      {canShowThumbnail ? (
        // Use cached thumbnail for both images and videos
        <img
          src={`/api/paid-content/files/thumbnail/${attachment.id}?size=large&${thumbCacheBuster}`}
          alt={attachment.name}
          className="w-full h-full object-cover"
          loading="lazy"
          onError={() => setThumbnailError(true)}
        />
      ) : (
        <div className="w-full h-full flex items-center justify-center">
          {isImage && <ImageIcon className="w-8 h-8 text-muted-foreground" />}
          {isVideo && <Video className="w-8 h-8 text-muted-foreground" />}
          {!isImage && !isVideo && <File className="w-8 h-8 text-muted-foreground" />}
        </div>
      )}

      {/* Metadata overlay on hover */}
      <div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/80 to-transparent p-2 opacity-0 group-hover:opacity-100 transition-opacity">
        <div className="text-white text-xs truncate">
          {attachment.name || `File ${attachment.attachment_index + 1}`}
        </div>
        <div className="text-white/70 text-xs flex items-center space-x-2">
          {attachment.file_size && <span>{formatFileSize(attachment.file_size)}</span>}
          {attachment.width && attachment.height && <span>{attachment.width}×{attachment.height}</span>}
          {isVideo && attachment.duration && <span>{formatDuration(attachment.duration)}</span>}
        </div>
      </div>

      {/* Status indicators */}
      {attachment.status === 'completed' && (
        <div className="absolute top-1 right-1 bg-emerald-500 text-white rounded-full p-1">
          <Download className="w-3 h-3" />
        </div>
      )}
      {attachment.status === 'pending' && (
        <div className="absolute inset-0 bg-black/50 flex items-center justify-center">
          <span className="text-xs text-white">Pending</span>
        </div>
      )}
      {attachment.status === 'failed' && (
        <div className="absolute inset-0 bg-red-500/50 flex items-center justify-center">
          <span className="text-xs text-white">Failed</span>
        </div>
      )}
      {isVideo && attachment.status === 'completed' && (
        <div className="absolute top-1 left-1 bg-black/60 text-white rounded px-1.5 py-0.5 text-xs">
          <Video className="w-3 h-3 inline mr-1" />
          {attachment.duration ? formatDuration(attachment.duration) : 'Video'}
        </div>
      )}
      {isPF && attachment.status === 'completed' && (
        <div className="absolute inset-0 bg-black/20 flex items-center justify-center">
          <Lock className="w-6 h-6 text-white/70" />
        </div>
      )}

      {/* Import button for failed/pending attachments */}
      {isMissing && onImport && (
        <button
          onClick={(e) => {
            e.stopPropagation()
            onImport(attachment)
          }}
          className="absolute top-1 right-1 bg-blue-500 hover:bg-blue-600 text-white rounded-full p-1.5 opacity-0 group-hover:opacity-100 transition-opacity z-10"
          title="Import from URL"
        >
          <Link className="w-3 h-3" />
        </button>
      )}
    </div>
  )
}

// Import Modal for importing files from URLs or uploading directly
function ImportUrlModal({
  attachment,
  onClose,
  onSuccess
}: {
  attachment: PaidContentAttachment
  onClose: () => void
  onSuccess: () => void | Promise<void>
}) {
  const [mode, setMode] = useState<'url' | 'file'>('url')
  const [url, setUrl] = useState('')
  const [selectedFile, setSelectedFile] = useState<File | null>(null)
  const [isLoading, setIsLoading] = useState(false)
  const [isDownloading, setIsDownloading] = useState(false)
  const [progress, setProgress] = useState<string | null>(null)
  const [error, setError] = useState<string | null>(null)
  const [isDragging, setIsDragging] = useState(false)
  const fileInputRef = useRef<HTMLInputElement>(null)

  // Real-time WebSocket progress updates for URL downloads
  useEffect(() => {
    if (!isDownloading) return

    const handleProgress = (data: any) => {
      if (data.attachment_id !== attachment.id) return

      if (data.status === 'completed') {
        setIsDownloading(false)
        setProgress(null)
        onSuccess()
        onClose()
      } else if (data.status === 'failed') {
        setIsDownloading(false)
        setIsLoading(false)
        setError(data.message || 'Download failed')
      } else if (data.status === 'downloading') {
        setProgress(data.message || `Downloading: ${data.progress}%`)
      }
    }

    const unsubscribe = wsClient.on('attachment_download_progress', handleProgress)

    const pollInterval = setInterval(async () => {
      try {
        const att = await api.paidContent.getAttachment(attachment.id)
        if (att.status === 'completed') {
          setIsDownloading(false)
          setProgress('Complete! Refreshing...')
          await onSuccess()
          onClose()
        } else if (att.status === 'failed') {
          setIsDownloading(false)
          setIsLoading(false)
          setError(att.error_message || 'Download failed')
        } else if (att.status === 'downloading' && att.error_message) {
          setProgress(att.error_message)
        }
      } catch (err) {
        // Ignore polling errors
      }
    }, 2000)

    return () => {
      unsubscribe()
      clearInterval(pollInterval)
    }
  }, [isDownloading, attachment.id, onSuccess, onClose])

  const handleImportUrl = async () => {
    if (!url.trim()) {
      setError('Please enter a URL')
      return
    }

    setIsLoading(true)
    setError(null)
    setProgress(null)

    try {
      const result = await api.paidContent.importUrlToAttachment(attachment.id, url.trim()) as {
        status: string
        error?: string
        message?: string
      }
      if (result.status === 'success') {
        await onSuccess()
        onClose()
      } else if (result.status === 'downloading') {
        setIsDownloading(true)
        setProgress('Starting download...')
      } else {
        setError(result.error || 'Import failed')
        setIsLoading(false)
      }
    } catch (err: any) {
      setError(err.message || 'Failed to import file')
      setIsLoading(false)
    }
  }

  const handleUploadFile = async () => {
    if (!selectedFile) {
      setError('Please select a file')
      return
    }

    setIsLoading(true)
    setError(null)
    setProgress('Uploading: 0%')

    try {
      await api.paidContent.uploadFileToAttachment(
        attachment.id,
        selectedFile,
        (loaded, total) => {
          const pct = Math.round((loaded / total) * 100)
          const loadedMB = (loaded / (1024 * 1024)).toFixed(1)
          const totalMB = (total / (1024 * 1024)).toFixed(1)
          setProgress(`Uploading: ${pct}% (${loadedMB}MB / ${totalMB}MB)`)
        }
      )
      setProgress('Complete! Refreshing...')
      await onSuccess()
      onClose()
    } catch (err: any) {
      setError(err.message || 'Failed to upload file')
      setIsLoading(false)
      setProgress(null)
    }
  }

  const handleDrop = (e: React.DragEvent) => {
    e.preventDefault()
    setIsDragging(false)
    const file = e.dataTransfer.files[0]
    if (file) {
      setSelectedFile(file)
      setError(null)
    }
  }

  const handleDragOver = (e: React.DragEvent) => {
    e.preventDefault()
    setIsDragging(true)
  }

  const handleDragLeave = () => {
    setIsDragging(false)
  }

  const formatFileSize = (bytes: number) => {
    if (bytes < 1024) return `${bytes} B`
    if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
    if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
    return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`
  }

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60" onClick={isDownloading || isLoading ? undefined : onClose}>
      <div
        className="bg-card border border-border rounded-xl shadow-xl w-full max-w-md mx-4 p-6"
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex items-center justify-between mb-4">
          <h3 className="text-lg font-semibold text-foreground">
            Import Attachment
          </h3>
          {!isDownloading && !isLoading && (
            <button onClick={onClose} className="p-1 rounded-lg hover:bg-secondary transition-colors">
              <X className="w-5 h-5 text-muted-foreground" />
            </button>
          )}
        </div>

        <div className="space-y-4">
          <p className="text-sm text-muted-foreground">
            Import file for: <span className="font-medium text-foreground">{attachment.name || `Attachment ${attachment.attachment_index + 1}`}</span>
          </p>

          {/* Mode tabs */}
          <div className="flex border border-border rounded-lg overflow-hidden">
            <button
              onClick={() => { setMode('url'); setError(null) }}
              disabled={isLoading || isDownloading}
              className={`flex-1 px-4 py-2 text-sm font-medium transition-colors ${
                mode === 'url'
                  ? 'bg-blue-500 text-white'
                  : 'bg-secondary text-muted-foreground hover:text-foreground'
              }`}
            >
              <Link className="w-4 h-4 inline mr-2" />
              From URL
            </button>
            <button
              onClick={() => { setMode('file'); setError(null) }}
              disabled={isLoading || isDownloading}
              className={`flex-1 px-4 py-2 text-sm font-medium transition-colors ${
                mode === 'file'
                  ? 'bg-blue-500 text-white'
                  : 'bg-secondary text-muted-foreground hover:text-foreground'
              }`}
            >
              <Plus className="w-4 h-4 inline mr-2" />
              Upload File
            </button>
          </div>

          {/* URL input */}
          {mode === 'url' && (
            <div className="space-y-2">
              <p className="text-xs text-muted-foreground">
                Supported: Bunkr, Pixeldrain, Gofile, Cyberdrop, or direct file URLs
              </p>
              <input
                type="url"
                value={url}
                onChange={(e) => setUrl(e.target.value)}
                placeholder="https://..."
                className="w-full px-4 py-2 rounded-lg border border-border bg-secondary text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-blue-500"
                autoFocus
                disabled={isDownloading || isLoading}
                onKeyDown={(e) => {
                  if (e.key === 'Enter' && !isLoading && !isDownloading) {
                    handleImportUrl()
                  }
                }}
              />
            </div>
          )}

          {/* File upload */}
          {mode === 'file' && (
            <div className="space-y-2">
              <div
                onDrop={handleDrop}
                onDragOver={handleDragOver}
                onDragLeave={handleDragLeave}
                onClick={() => !isLoading && fileInputRef.current?.click()}
                className={`border-2 border-dashed rounded-lg p-6 text-center cursor-pointer transition-colors ${
                  isDragging
                    ? 'border-blue-500 bg-blue-500/10'
                    : selectedFile
                    ? 'border-green-500 bg-green-500/10'
                    : 'border-border hover:border-blue-500 hover:bg-secondary'
                }`}
              >
                {selectedFile ? (
                  <div className="space-y-1">
                    <p className="text-sm font-medium text-foreground">{selectedFile.name}</p>
                    <p className="text-xs text-muted-foreground">{formatFileSize(selectedFile.size)}</p>
                    <p className="text-xs text-green-500">Click to change file</p>
                  </div>
                ) : (
                  <div className="space-y-1">
                    <Plus className="w-8 h-8 mx-auto text-muted-foreground" />
                    <p className="text-sm text-muted-foreground">
                      Drop file here or click to browse
                    </p>
                  </div>
                )}
              </div>
              <input
                ref={fileInputRef}
                type="file"
                className="hidden"
                onChange={(e) => {
                  const file = e.target.files?.[0]
                  if (file) {
                    setSelectedFile(file)
                    setError(null)
                  }
                }}
              />
            </div>
          )}

          {/* Progress display */}
          {(isDownloading || (isLoading && mode === 'file')) && progress && (
            <div className="space-y-2">
              <div className="flex items-center gap-2 text-sm text-blue-500">
                <Loader2 className="w-4 h-4 animate-spin" />
                {progress}
              </div>
              {progress.includes('%') && (
                <div className="w-full bg-secondary rounded-full h-2">
                  <div
                    className="bg-blue-500 h-2 rounded-full transition-all duration-300"
                    style={{ width: progress.match(/(\d+)%/)?.[1] + '%' || '0%' }}
                  />
                </div>
              )}
            </div>
          )}

          {error && (
            <div className="flex items-center gap-2 text-sm text-red-500">
              <AlertTriangle className="w-4 h-4" />
              {error}
            </div>
          )}

          <div className="flex justify-end gap-2">
            <button
              onClick={onClose}
              className="px-4 py-2 rounded-lg border border-border text-foreground hover:bg-secondary transition-colors"
              disabled={isLoading || isDownloading}
            >
              {isDownloading ? 'Downloading...' : 'Cancel'}
            </button>
            <button
              onClick={mode === 'url' ? handleImportUrl : handleUploadFile}
              disabled={isLoading || isDownloading || (mode === 'url' ? !url.trim() : !selectedFile)}
              className="px-4 py-2 rounded-lg bg-blue-500 text-white hover:bg-blue-600 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
            >
              {isLoading ? (
                <>
                  <Loader2 className="w-4 h-4 animate-spin" />
                  {mode === 'url' ? 'Starting...' : 'Uploading...'}
                </>
              ) : (
                <>
                  <Download className="w-4 h-4" />
                  {mode === 'url' ? 'Import' : 'Upload'}
                </>
              )}
            </button>
          </div>
        </div>
      </div>
    </div>
  )
}

function PostCard({
  post,
  isSelected,
  onClick,
  onOpenLightbox,
  onCopyToGallery,
}: {
  post: PaidContentPost
  isSelected: boolean
  onClick: () => void
  onOpenLightbox?: (attachmentIndex: number) => void
  onCopyToGallery?: (post: PaidContentPost) => void
}) {
  const queryClient = useQueryClient()
  const touchStartRef = useRef<{ x: number; y: number; time: number } | null>(null)

  const favoriteMutation = useMutation({
    mutationFn: () => api.paidContent.toggleFavorite(post.id),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
    },
  })

  const viewedMutation = useMutation({
    mutationFn: () => api.paidContent.toggleViewed(post.id),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
      queryClient.invalidateQueries({ queryKey: ['paid-content-unviewed-count'] })
    },
  })

  const { data: watchLaterIds = [] } = useQuery({
    queryKey: ['paid-content-watch-later-ids'],
    queryFn: () => api.paidContent.getWatchLaterAttachmentIds(),
    staleTime: 30000,
  })

  const watchLaterSet = new Set(watchLaterIds)
  const hasWatchLaterItems = post.attachments?.some(a => a.status === 'completed' && watchLaterSet.has(a.id))
  const firstCompletedAttachment = post.attachments?.find(a => a.status === 'completed')

  const addWatchLaterMutation = useMutation({
    mutationFn: (attachmentId: number) => api.paidContent.addToWatchLater(attachmentId),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['paid-content-watch-later-ids'] }),
  })

  const removeWatchLaterMutation = useMutation({
    mutationFn: (attachmentId: number) => api.paidContent.removeFromWatchLater(attachmentId),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['paid-content-watch-later-ids'] })
      queryClient.invalidateQueries({ queryKey: ['paid-content-watch-later'] })
    },
  })

  const toggleWatchLater = () => {
    if (!firstCompletedAttachment) return
    if (hasWatchLaterItems) {
      // Remove all completed attachments from watch later
      post.attachments?.filter(a => a.status === 'completed' && watchLaterSet.has(a.id))
        .forEach(a => removeWatchLaterMutation.mutate(a.id))
    } else {
      // Add first completed attachment
      addWatchLaterMutation.mutate(firstCompletedAttachment.id)
    }
  }

  // Get completed attachments for display
  const completedAttachments = post.attachments?.filter(a => a.status === 'completed') || []
  const visualAttachments = completedAttachments.filter(a => a.file_type !== 'audio')
  const unavailableAttachments = post.attachments?.filter(a => a.status === 'unavailable') || []
  const imageExts = ['jpg', 'jpeg', 'png', 'webp', 'gif']
  const previewFrameIds = new Set(
    completedAttachments
      .filter(a => a.file_type === 'video' && imageExts.includes(a.extension?.toLowerCase() || ''))
      .map(a => a.id)
  )
  const hasMoreAttachments = (post.attachment_count || 0) > 4

  // Handle touch to distinguish between tap and scroll
  const handleTouchStart = (e: React.TouchEvent) => {
    touchStartRef.current = { x: e.touches[0].clientX, y: e.touches[0].clientY, time: Date.now() }
  }

  const handleTouchEnd = (e: React.TouchEvent) => {
    if (!touchStartRef.current) return
    const touch = e.changedTouches[0]
    const dx = Math.abs(touch.clientX - touchStartRef.current.x)
    const dy = Math.abs(touch.clientY - touchStartRef.current.y)
    const duration = Date.now() - touchStartRef.current.time
    // Only trigger click if movement was less than 20px and touch lasted at least 50ms (deliberate tap)
    if (dx < 20 && dy < 20 && duration >= 50) {
      onClick()
    }
    touchStartRef.current = null
  }

  return (
    <div
      data-post-id={post.id}
      className={`p-4 border-b border-border cursor-pointer hover:bg-muted/50 transition-colors ${isSelected && post.is_pinned === 1 ? 'bg-amber-100/80 dark:bg-amber-900/30' : isSelected ? 'bg-primary/10' : post.is_pinned === 1 ? 'bg-amber-50/60 dark:bg-amber-900/15' : ''}`}
      onClick={() => {
        // Only handle click on desktop (no touch events)
        if (!('ontouchstart' in window)) {
          onClick()
        }
      }}
      onTouchStart={handleTouchStart}
      onTouchEnd={handleTouchEnd}
    >
      {/* Header row */}
      <div className="flex items-start justify-between">
        <div className="flex items-center space-x-3">
          <div className="w-10 h-10 rounded-full bg-gradient-to-br from-pink-500 to-violet-500 p-0.5 flex-shrink-0">
            <div className="w-full h-full rounded-full overflow-hidden bg-card">
              {post.profile_image_url ? (
                <img src={post.profile_image_url} alt="" className="w-full h-full object-cover" />
              ) : (
                <div className="w-full h-full flex items-center justify-center bg-secondary">
                  <User className="w-5 h-5 text-muted-foreground" />
                </div>
              )}
            </div>
          </div>
          <div>
            <p className="font-medium text-foreground">{post.display_name || post.username}</p>
            <p className="text-xs text-muted-foreground">@{post.username}</p>
          </div>
        </div>
        <div className="flex items-center space-x-1 sm:space-x-2 flex-shrink-0">
          {post.is_pinned === 1 && (
            <span className="text-amber-500" title="Pinned">
              <Pin className="w-4 h-4" />
            </span>
          )}
          {onCopyToGallery && post.attachments?.some(a => a.status === 'completed' && a.local_path) && (
            <button
              onClick={(e) => {
                e.stopPropagation()
                onCopyToGallery(post)
              }}
              className="hidden sm:block p-1.5 rounded-lg transition-colors text-muted-foreground hover:text-violet-500"
              title="Copy to Private Gallery"
            >
              <ImagePlus className="w-4 h-4" />
            </button>
          )}
          {firstCompletedAttachment && (
            <button
              onClick={(e) => {
                e.stopPropagation()
                toggleWatchLater()
              }}
              className={`p-1.5 rounded-lg transition-colors ${
                hasWatchLaterItems ? 'text-violet-500' : 'text-muted-foreground hover:text-violet-500'
              }`}
              title={hasWatchLaterItems ? 'Remove from Watch Later' : 'Add to Watch Later'}
            >
              <Clock className={`w-4 h-4 ${hasWatchLaterItems ? 'fill-current' : ''}`} />
            </button>
          )}
          <button
            onClick={(e) => {
              e.stopPropagation()
              favoriteMutation.mutate()
            }}
            className={`p-1.5 rounded-lg transition-colors ${
              post.is_favorited ? 'text-red-500' : 'text-muted-foreground hover:text-red-500'
            }`}
          >
            <Heart className={`w-4 h-4 ${post.is_favorited ? 'fill-current' : ''}`} />
          </button>
          <button
            onClick={(e) => {
              e.stopPropagation()
              viewedMutation.mutate()
            }}
            className="p-1.5 rounded-lg transition-colors text-muted-foreground hover:text-foreground"
            title={post.is_viewed ? 'Mark as unviewed' : 'Mark as viewed'}
          >
            {post.is_viewed ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4 text-primary" />}
          </button>
        </div>
      </div>

      {/* Tags row */}
      {(post.tags?.length || previewFrameIds.size > 0 || post.tags?.some(t => t.slug === 'ppv' || t.name === 'PPV')) ? (
        <div className="flex flex-wrap gap-1 mt-2 ml-[3.25rem]">
          {/* PPV tags first */}
          {(post.tags || []).filter(t => t.slug === 'ppv' || t.name === 'PPV').map((tag) => (
            <span
              key={tag.id}
              className="px-2 py-0.5 rounded-full text-xs font-medium"
              style={{ backgroundColor: tag.color, color: '#fff' }}
            >
              {tag.name}
            </span>
          ))}
          {/* DRM badge second */}
          {previewFrameIds.size > 0 && (
            <span className="inline-flex items-center gap-0.5 px-2 py-0.5 rounded-full text-xs font-medium bg-amber-500/15 text-amber-600 dark:text-amber-400">
              <Lock className="w-2.5 h-2.5" />
              DRM
            </span>
          )}
          {/* Remaining tags */}
          {(post.tags || []).filter(t => t.slug !== 'ppv' && t.name !== 'PPV').map((tag) => (
            <span
              key={tag.id}
              className="px-2 py-0.5 rounded-full text-xs font-medium"
              style={{ backgroundColor: tag.color, color: '#fff' }}
            >
              {tag.name}
            </span>
          ))}
        </div>
      ) : null}

      {/* Content - For YouTube/Twitch, use title instead of content */}
      {(() => {
        const isVideoService = ['youtube', 'twitch', 'pornhub'].includes(post.platform?.toLowerCase() || '')
        const displayText = isVideoService && post.title ? post.title : (post.content || post.title)
        return displayText ? (
          <div className="mt-3">
            <p className="text-sm text-muted-foreground line-clamp-3">{cleanCaption(decodeHtmlEntities(displayText))}</p>
          </div>
        ) : null
      })()}

      {/* PPV Locked Attachments - placeholder grid (mobile only) */}
      {unavailableAttachments.length > 0 && completedAttachments.length === 0 && (
        <div className="mt-3 lg:hidden">
          <div className={`grid gap-1 rounded-lg overflow-hidden ${
            unavailableAttachments.length === 1 ? 'grid-cols-1' :
            'grid-cols-2'
          }`}>
            {unavailableAttachments.slice(0, 4).map((att) => (
              <div
                key={att.id}
                className={`relative bg-secondary/50 border border-dashed border-muted-foreground/20 overflow-hidden ${
                  unavailableAttachments.length === 1 ? 'aspect-video' : 'aspect-square'
                } flex flex-col items-center justify-center gap-1`}
              >
                <Lock className="w-5 h-5 text-muted-foreground/40" />
                <span className="text-[10px] text-muted-foreground/50">
                  {att.file_type === 'video' ? 'Video' : att.file_type === 'image' ? 'Image' : 'Media'} locked
                </span>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Attachment Grid - Inline thumbnails (mobile only, desktop uses detail panel) */}
      {visualAttachments.length > 0 && (
        <div className="mt-3 lg:hidden">
          <div className={`grid gap-1 rounded-lg overflow-hidden ${
            visualAttachments.length === 1 ? 'grid-cols-1' :
            visualAttachments.length === 2 ? 'grid-cols-2' :
            'grid-cols-2'
          }`}>
            {visualAttachments.slice(0, 4).map((att, idx) => {
              const isImage = att.file_type === 'image'
              const isVideo = att.file_type === 'video'

              return (
                <button
                  key={att.id}
                  type="button"
                  className={`relative bg-secondary overflow-hidden cursor-pointer active:opacity-70 ${
                    visualAttachments.length === 1 ? 'aspect-video' : 'aspect-square'
                  }`}
                  style={{ touchAction: 'manipulation', WebkitTapHighlightColor: 'transparent' }}
                  onTouchStart={(e) => e.stopPropagation()}
                  onTouchEnd={(e) => e.stopPropagation()}
                  onClick={(e) => {
                    e.stopPropagation()
                    if (onOpenLightbox) {
                      onOpenLightbox(idx)
                    }
                  }}
                >
                  {(isImage || isVideo) && att.id ? (
                    // Use cached thumbnail from database
                    <div className="w-full h-full bg-slate-800 relative pointer-events-none">
                      <img
                        src={`/api/paid-content/files/thumbnail/${att.id}?size=large&${att.file_hash ? `v=${att.file_hash.slice(0, 8)}` : THUMB_CACHE_V}`}
                        alt=""
                        className="w-full h-full object-cover"
                        loading="lazy"
                        onError={(e) => {
                          // Hide broken image, show fallback
                          (e.target as HTMLImageElement).style.display = 'none'
                        }}
                      />
                      {/* Lock overlay for video preview frames (PPV/DRM) */}
                      {previewFrameIds.has(att.id) ? (
                        <div className="absolute inset-0 bg-black/30 flex items-center justify-center">
                          <Lock className="w-8 h-8 text-white/80" />
                        </div>
                      ) : (
                        <>
                          {/* Play button overlay for videos */}
                          {isVideo && (
                            <div className="absolute inset-0 flex items-center justify-center">
                              <div className="w-12 h-12 rounded-full bg-black/50 flex items-center justify-center">
                                <Video className="w-6 h-6 text-white" />
                              </div>
                            </div>
                          )}
                          {/* Duration badge for videos */}
                          {isVideo && att.duration && (
                            <div className="absolute bottom-1 right-1 bg-black/70 text-white text-xs px-1.5 py-0.5 rounded">
                              {Math.floor(att.duration / 60)}:{Math.floor(att.duration % 60).toString().padStart(2, '0')}
                            </div>
                          )}
                        </>
                      )}
                    </div>
                  ) : (
                    <div className="w-full h-full flex items-center justify-center pointer-events-none">
                      <File className="w-8 h-8 text-muted-foreground" />
                    </div>
                  )}

                  {/* Show +N overlay on the 4th item if there are more */}
                  {idx === 3 && hasMoreAttachments && (
                    <div className="absolute inset-0 bg-black/60 flex items-center justify-center pointer-events-none">
                      <span className="text-white text-xl font-bold">
                        +{(post.attachment_count || 0) - 4}
                      </span>
                    </div>
                  )}
                </button>
              )
            })}
          </div>
        </div>
      )}

      {/* Inline Audio Player - mobile only, prevents tap from opening post */}
      {completedAttachments.some(a => a.file_type === 'audio') && (
        <div
          className="mt-3 lg:hidden space-y-2"
          onClick={(e) => e.stopPropagation()}
          onTouchStart={(e) => e.stopPropagation()}
          onTouchEnd={(e) => e.stopPropagation()}
        >
          {completedAttachments.filter(a => a.file_type === 'audio').map((audio) => {
            const audioUrl = audio.local_path
              ? audio.file_token ? `/api/paid-content/files/serve?t=${encodeURIComponent(audio.file_token)}` : `/api/paid-content/files/serve?path=${encodeURIComponent(audio.local_path)}`
              : null
            const fileSizeMB = audio.file_size ? (audio.file_size / 1024 / 1024).toFixed(1) : null
            return (
              <div key={audio.id} className="rounded-lg border border-border bg-card/50 p-2.5">
                <div className="flex items-center gap-2 mb-1.5">
                  <div className="flex-shrink-0 w-8 h-8 rounded-md bg-primary/10 flex items-center justify-center">
                    <Music className="w-4 h-4 text-primary" />
                  </div>
                  <div className="flex-1 min-w-0">
                    <div className="text-xs font-medium text-foreground truncate">{audio.name || 'Audio'}</div>
                    {fileSizeMB && <div className="text-[10px] text-muted-foreground">{fileSizeMB} MB</div>}
                  </div>
                </div>
                <audio
                  src={audioUrl || undefined}
                  controls
                  preload="metadata"
                  className="w-full h-8"
                  style={{ colorScheme: 'dark' }}
                />
              </div>
            )
          })}
        </div>
      )}

      {/* Footer */}
      <div className="mt-3 flex items-center justify-between text-xs text-muted-foreground">
        <div className="flex items-center gap-2">
          <span className="flex items-center space-x-1">
            <Calendar className="w-3 h-3" />
            <span>{post.published_at ? formatRelativeTime(post.published_at) : 'Unknown date'}</span>
          </span>
          {previewFrameIds.size > 0 && post.post_id && (post.service_id === 'onlyfans_direct' || post.platform === 'onlyfans') && (
            <a
              href={`https://onlyfans.com/${post.post_id}/${post.username}`}
              target="_blank"
              rel="noopener noreferrer"
              onClick={(e) => e.stopPropagation()}
              className="inline-flex items-center gap-0.5 text-primary hover:underline"
              title="View this post on OnlyFans"
            >
              <ExternalLink className="w-3 h-3" />
              <span>OF</span>
            </a>
          )}
          {previewFrameIds.size > 0 && post.post_id && (post.service_id === 'fansly_direct' || post.platform === 'fansly') && (
            <a
              href={`https://fansly.com/post/${post.post_id}`}
              target="_blank"
              rel="noopener noreferrer"
              onClick={(e) => e.stopPropagation()}
              className="inline-flex items-center gap-0.5 text-primary hover:underline"
              title="View this post on Fansly"
            >
              <ExternalLink className="w-3 h-3" />
              <span>Fansly</span>
            </a>
          )}
        </div>
        <div className="flex items-center space-x-2">
          {/* Content type tags */}
          {(() => {
            const imageCount = post.attachments?.filter(a => a.file_type === 'image').length || 0
            const videoCount = post.attachments?.filter(a => a.file_type === 'video').length || 0
            const audioCount = post.attachments?.filter(a => a.file_type === 'audio').length || 0
            return (
              <>
                {imageCount > 0 && (
                  <span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300">
                    <ImageIcon className="w-3 h-3" />
                    <span>{imageCount}</span>
                  </span>
                )}
                {videoCount > 0 && (
                  <span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300">
                    <Video className="w-3 h-3" />
                    <span>{videoCount}</span>
                  </span>
                )}
                {audioCount > 0 && (
                  <span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-300">
                    <Music className="w-3 h-3" />
                    <span>{audioCount}</span>
                  </span>
                )}
              </>
            )
          })()}
          {post.downloaded && (
            <span className="flex items-center space-x-1 text-emerald-600 dark:text-emerald-400">
              <Download className="w-3 h-3" />
            </span>
          )}
        </div>
      </div>

    </div>
  )
}

function PostDetail({ post, onClose, onPostUpdated }: { post: PaidContentPost; onClose: () => void; onPostUpdated?: (updatedPost?: Partial<PaidContentPost>) => void }) {
  const [lightboxIndex, setLightboxIndex] = useState<number | null>(null)
  const [isEditing, setIsEditing] = useState(false)
  const [editTitle, setEditTitle] = useState(post.title || '')
  const [editContent, setEditContent] = useState(post.content || '')
  const [editDate, setEditDate] = useState(post.published_at?.split('T')[0] || '')
  const [editTags, setEditTags] = useState<number[]>(post.tags?.map(t => t.id) || [])
  const [importAttachment, setImportAttachment] = useState<PaidContentAttachment | null>(null)
  const [isUploading, setIsUploading] = useState(false)
  const [deletingAttachmentId, setDeletingAttachmentId] = useState<number | null>(null)
  const [confirmDeleteId, setConfirmDeleteId] = useState<number | null>(null)
  // Selection mode for batch delete
  const [selectionMode, setSelectionMode] = useState(false)
  const [selectedIds, setSelectedIds] = useState<Set<number>>(new Set())
  const [isBatchDeleting, setIsBatchDeleting] = useState(false)
  const lastSelectedAttIdx = useRef<number | null>(null)
  const fileInputRef = useRef<HTMLInputElement>(null)
  const queryClient = useQueryClient()

  // Fetch available tags
  const { data: availableTags = [] } = useQuery({
    queryKey: ['paid-content-tags'],
    queryFn: () => api.paidContent.getTags(),
  })

  // Update edit state when post changes
  useEffect(() => {
    setEditTitle(post.title || '')
    setEditContent(post.content || '')
    setEditDate(post.published_at?.split('T')[0] || '')
    setEditTags(post.tags?.map(t => t.id) || [])
    setIsEditing(false)
    setConfirmDeleteId(null)
    setDeletingAttachmentId(null)
    setSelectionMode(false)
    setSelectedIds(new Set())
  }, [post.id])

  // Mutation for updating post
  const updateMutation = useMutation({
    mutationFn: (data: { title?: string; content?: string; published_at?: string }) =>
      api.put(`/paid-content/feed/${post.id}`, data),
    onSuccess: (_, variables) => {
      queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
      setIsEditing(false)
      // Pass updated fields back to parent so selectedPost updates immediately
      onPostUpdated?.(variables)
    },
    onError: (error) => {
      console.error('Failed to update post:', error)
    },
  })

  // Mutation for refreshing metadata
  const refreshMutation = useMutation({
    mutationFn: () => api.post(`/paid-content/feed/${post.id}/refresh`),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
      onPostUpdated?.()
    },
  })

  // Mutation for updating tags
  const tagsMutation = useMutation({
    mutationFn: (tagIds: number[]) => api.paidContent.setPostTags(post.id, tagIds),
    onSuccess: (_, tagIds) => {
      queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
      setIsEditing(false)
      // Pass updated tags back to parent so selectedPost updates immediately
      const updatedTags = availableTags.filter(t => tagIds.includes(t.id))
      onPostUpdated?.({ tags: updatedTags })
    },
  })

  const handleSave = async () => {
    const updates: { title?: string; content?: string; published_at?: string } = {}
    // Normalize null/undefined to empty string for comparison
    const originalTitle = post.title || ''
    const originalContent = post.content || ''
    const originalDate = post.published_at?.split('T')[0] || ''

    if (editTitle !== originalTitle) updates.title = editTitle
    if (editContent !== originalContent) updates.content = editContent
    if (editDate !== originalDate) updates.published_at = editDate ? `${editDate}T12:00:00` : undefined

    // Check if tags changed
    const currentTagIds = post.tags?.map(t => t.id) || []
    const tagsChanged = editTags.length !== currentTagIds.length ||
      editTags.some(id => !currentTagIds.includes(id))

    if (Object.keys(updates).length > 0) {
      updateMutation.mutate(updates)
    }

    if (tagsChanged) {
      tagsMutation.mutate(editTags)
    }

    if (Object.keys(updates).length === 0 && !tagsChanged) {
      setIsEditing(false)
    }
  }



  // Handle file upload
  const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const files = event.target.files
    if (!files || files.length === 0) return

    setIsUploading(true)
    try {
      for (const file of Array.from(files)) {
        await api.paidContent.uploadAttachment(post.id, file)
      }
      queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
      onPostUpdated?.()
    } catch (error: any) {
      console.error('Upload failed:', error)
      alert(`Upload failed: ${error.message || 'Unknown error'}`)
    } finally {
      setIsUploading(false)
      // Reset file input
      if (fileInputRef.current) {
        fileInputRef.current.value = ''
      }
    }
  }

  // Delete attachment handler
  const handleDeleteAttachment = async (attachmentId: number) => {
    // If this is the last attachment, offer to delete the entire post
    if (completedAttachments.length === 1) {
      setConfirmDeleteId(null)
      if (confirm('This is the last attachment. Delete the entire post?')) {
        try {
          await api.paidContent.deletePost(post.id)
          queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
          onClose()
        } catch (error) {
          console.error('Failed to delete post:', error)
          alert('Failed to delete post')
        }
      }
      return
    }

    setDeletingAttachmentId(attachmentId)
    setConfirmDeleteId(null)

    try {
      await api.paidContent.deleteAttachment(post.id, attachmentId, true)
      queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
      onPostUpdated?.()
    } catch (error) {
      console.error('Failed to delete attachment:', error)
      alert('Failed to delete attachment')
    } finally {
      setDeletingAttachmentId(null)
    }
  }

  // Batch delete handler
  const handleBatchDelete = async () => {
    if (selectedIds.size === 0) return

    // If deleting all attachments, offer to delete the post
    if (selectedIds.size === completedAttachments.length) {
      if (confirm(`Delete all ${completedAttachments.length} attachments and the entire post? This cannot be undone.`)) {
        try {
          setIsBatchDeleting(true)
          await api.paidContent.deletePost(post.id)
          queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
          onClose()
        } catch (error) {
          console.error('Failed to delete post:', error)
          alert('Failed to delete post')
        } finally {
          setIsBatchDeleting(false)
        }
      }
      return
    }

    if (!confirm(`Delete ${selectedIds.size} selected attachment${selectedIds.size > 1 ? 's' : ''}?`)) return

    setIsBatchDeleting(true)
    try {
      for (const id of selectedIds) {
        await api.paidContent.deleteAttachment(post.id, id, true)
      }
      setSelectedIds(new Set())
      setSelectionMode(false)
      queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
      onPostUpdated?.()
    } catch (error) {
      console.error('Failed to batch delete:', error)
      alert('Some attachments failed to delete')
    } finally {
      setIsBatchDeleting(false)
    }
  }

  // Lightbox delete handler (deletes current item and adjusts index)
  const handleLightboxDelete = async (attachment: PaidContentAttachment) => {
    if (!confirm('Delete this attachment?')) return
    try {
      await api.paidContent.deleteAttachment(post.id, attachment.id, true)
      queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
      onPostUpdated?.()

      // If no attachments left, close lightbox
      const remaining = completedAttachments.filter(a => a.id !== attachment.id)
      if (remaining.length === 0) {
        setLightboxIndex(null)
        return
      }

      // Adjust index if needed
      if (lightboxIndex !== null && lightboxIndex >= remaining.length) {
        setLightboxIndex(remaining.length - 1)
      }
    } catch (error) {
      console.error('Failed to delete attachment:', error)
      alert('Failed to delete attachment')
    }
  }

  // Filter to only completed media attachments for lightbox navigation
  const completedAttachments = post.attachments?.filter(
    (a) => a.status === 'completed' && (a.file_type === 'image' || a.file_type === 'video')
  ) || []

  const openLightbox = (attachment: PaidContentAttachment) => {
    if (attachment.status !== 'completed') return
    const index = completedAttachments.findIndex((a) => a.id === attachment.id)
    if (index !== -1) {
      setLightboxIndex(index)
    }
  }

  return (
    <div className="h-full flex flex-col">
      {/* BundleLightbox */}
      {lightboxIndex !== null && completedAttachments.length > 0 && (
        <BundleLightbox
          post={post}
          attachments={completedAttachments}
          currentIndex={lightboxIndex}
          onClose={() => setLightboxIndex(null)}
          onNavigate={setLightboxIndex}
          onDelete={handleLightboxDelete}
        />
      )}

      {/* Header */}
      <div className="p-4 border-b border-border flex items-center justify-between sticky top-0 bg-card z-10">
        {/* Mobile back button */}
        <button
          type="button"
          onClick={onClose}
          onTouchEnd={(e) => { e.preventDefault(); onClose(); }}
          className="lg:hidden p-3 -ml-2 rounded-lg hover:bg-secondary active:bg-secondary/80 transition-colors flex items-center gap-2 text-foreground min-w-[44px] min-h-[44px]"
          style={{ touchAction: 'manipulation', WebkitTapHighlightColor: 'transparent' }}
        >
          <ChevronLeft className="w-5 h-5" />
          <span className="text-sm font-medium">Back</span>
        </button>

        <div className="flex items-center space-x-3 lg:flex-1">
          <div className="w-10 h-10 rounded-full bg-gradient-to-br from-pink-500 to-violet-500 p-0.5 flex-shrink-0">
            <div className="w-full h-full rounded-full overflow-hidden bg-card">
              {post.profile_image_url ? (
                <img src={post.profile_image_url} alt="" className="w-full h-full object-cover" />
              ) : (
                <div className="w-full h-full flex items-center justify-center bg-secondary">
                  <User className="w-5 h-5 text-muted-foreground" />
                </div>
              )}
            </div>
          </div>
          <div className="hidden lg:block">
            <p className="font-medium text-foreground">{post.display_name || post.username}</p>
            <p className="text-xs text-muted-foreground capitalize">
              {post.platform} / {post.published_at ? formatRelativeTime(post.published_at) : 'Unknown date'}
            </p>
          </div>
        </div>

        {/* Action buttons */}
        <div className="flex items-center gap-2">
          {isEditing ? (
            <>
              <button
                type="button"
                onClick={(e) => {
                  e.stopPropagation()
                  if (selectionMode) {
                    setSelectionMode(false)
                    setSelectedIds(new Set())
                  } else {
                    setSelectionMode(true)
                  }
                }}
                className={`p-2 rounded-lg hover:bg-secondary active:bg-secondary/80 transition-colors min-w-[40px] min-h-[40px] flex items-center justify-center ${
                  selectionMode ? 'text-primary bg-primary/10' : 'text-muted-foreground'
                }`}
                style={{ touchAction: 'manipulation', WebkitTapHighlightColor: 'transparent' }}
                title={selectionMode ? 'Cancel selection' : 'Select items to delete'}
              >
                <CheckSquare className="w-5 h-5" />
              </button>
              <button
                onClick={() => setIsEditing(false)}
                className="p-2 rounded-lg hover:bg-secondary transition-colors text-muted-foreground"
                title="Cancel"
              >
                <X className="w-5 h-5" />
              </button>
              <button
                onClick={handleSave}
                disabled={updateMutation.isPending || tagsMutation.isPending}
                className="p-2 rounded-lg hover:bg-secondary transition-colors text-primary"
                title="Save"
              >
                {updateMutation.isPending || tagsMutation.isPending ? (
                  <Loader2 className="w-5 h-5 animate-spin" />
                ) : (
                  <Save className="w-5 h-5" />
                )}
              </button>
            </>
          ) : (
            <>
              <button
                type="button"
                onClick={(e) => {
                  e.stopPropagation()
                  if (selectionMode) {
                    setSelectionMode(false)
                    setSelectedIds(new Set())
                  } else {
                    setSelectionMode(true)
                  }
                }}
                className={`p-2 rounded-lg hover:bg-secondary active:bg-secondary/80 transition-colors min-w-[40px] min-h-[40px] flex items-center justify-center ${
                  selectionMode ? 'text-primary bg-primary/10' : 'text-muted-foreground'
                }`}
                style={{ touchAction: 'manipulation', WebkitTapHighlightColor: 'transparent' }}
                title={selectionMode ? 'Cancel selection' : 'Select items'}
              >
                <CheckSquare className="w-5 h-5" />
              </button>
              <button
                onClick={() => refreshMutation.mutate()}
                disabled={refreshMutation.isPending}
                className="p-2 rounded-lg hover:bg-secondary transition-colors text-muted-foreground"
                title="Refresh from source"
              >
                {refreshMutation.isPending ? (
                  <Loader2 className="w-5 h-5 animate-spin" />
                ) : (
                  <RefreshCw className="w-5 h-5" />
                )}
              </button>
              <button
                onClick={() => setIsEditing(true)}
                className="p-2 rounded-lg hover:bg-secondary transition-colors text-muted-foreground"
                title="Edit"
              >
                <Edit className="w-5 h-5" />
              </button>
              <button
                type="button"
                onClick={async (e) => {
                  e.stopPropagation()
                  if (confirm('Delete this post and all its media? This cannot be undone.')) {
                    try {
                      await api.paidContent.deletePost(post.id)
                      await queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
                      onClose()
                    } catch (error) {
                      console.error('Failed to delete post:', error)
                      alert('Failed to delete post')
                    }
                  }
                }}
                className="flex p-2 rounded-lg hover:bg-destructive/20 transition-colors min-w-[40px] min-h-[40px] items-center justify-center"
                title="Delete post"
              >
                <Trash2 className="w-5 h-5 text-destructive" />
              </button>
              {/* Desktop close button */}
              <button onClick={onClose} className="hidden lg:block p-2 rounded-lg hover:bg-secondary transition-colors">
                <X className="w-5 h-5 text-muted-foreground" />
              </button>
            </>
          )}
        </div>
      </div>

      {/* Content */}
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {isEditing ? (
          <>
            <div className="space-y-2">
              <label className="text-sm font-medium text-foreground">Title</label>
              <input
                type="text"
                value={editTitle}
                onChange={(e) => setEditTitle(e.target.value)}
                className="w-full px-3 py-2 bg-secondary border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
                placeholder="Post title"
              />
            </div>
            <div className="space-y-2">
              <label className="text-sm font-medium text-foreground">Date</label>
              <input
                type="date"
                value={editDate}
                onChange={(e) => setEditDate(e.target.value)}
                className="w-full px-3 py-2 bg-secondary border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
              />
            </div>
            <div className="space-y-2">
              <label className="text-sm font-medium text-foreground">Description</label>
              <textarea
                value={editContent}
                onChange={(e) => setEditContent(e.target.value)}
                className="w-full px-3 py-2 bg-secondary border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-primary min-h-[200px] resize-y"
                placeholder="Post description"
              />
            </div>
            <div className="space-y-2">
              <TagSearchSelector
                tags={availableTags}
                selectedTagIds={editTags}
                onChange={setEditTags}
                label="Tags"
              />
            </div>
          </>
        ) : (
          <>
            <h2 className="text-lg font-semibold text-foreground">{getPostDisplayTitle(post)}</h2>

            {/* Display tags in view mode */}
            {post.tags && post.tags.length > 0 && (
              <div className="flex flex-wrap gap-2">
                {post.tags.map((tag) => (
                  <span
                    key={tag.id}
                    className="px-2.5 py-1 rounded-full text-xs font-medium"
                    style={{ backgroundColor: tag.color, color: '#fff' }}
                  >
                    {tag.name}
                  </span>
                ))}
              </div>
            )}

            {post.content && (
              <p className="text-muted-foreground whitespace-pre-wrap">{cleanCaption(decodeHtmlEntities(post.content))}</p>
            )}
          </>
        )}

        {/* Attachments Grid */}
        <div className="space-y-3">
          <div className="flex items-center justify-between">
            <h3 className="font-medium text-foreground">
              Attachments ({post.attachments?.length || 0})
            </h3>
            {isEditing && (
              <>
                <input
                  ref={fileInputRef}
                  type="file"
                  multiple
                  onChange={handleFileUpload}
                  className="hidden"
                  accept="image/*,video/*,audio/*"
                />
                <button
                  onClick={() => fileInputRef.current?.click()}
                  disabled={isUploading}
                  className="flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50"
                >
                  {isUploading ? (
                    <Loader2 className="w-4 h-4 animate-spin" />
                  ) : (
                    <Plus className="w-4 h-4" />
                  )}
                  Upload
                </button>
              </>
            )}
          </div>
          {/* Batch delete bar - shown when items are selected */}
          {selectionMode && selectedIds.size > 0 && (
            <div className="p-3 bg-secondary/50 border border-border rounded-lg flex items-center justify-between gap-2">
              <div className="flex items-center gap-2">
                <span className="text-sm font-medium">
                  {selectedIds.size} selected
                </span>
                <button
                  onClick={() => {
                    if (selectedIds.size === completedAttachments.length) {
                      setSelectedIds(new Set())
                    } else {
                      setSelectedIds(new Set(completedAttachments.map(a => a.id)))
                    }
                  }}
                  className="text-xs text-primary hover:underline"
                >
                  {selectedIds.size === completedAttachments.length ? 'Deselect all' : 'Select all'}
                </button>
              </div>
              <button
                onClick={handleBatchDelete}
                disabled={isBatchDeleting}
                className="flex items-center gap-1.5 px-3 py-1.5 bg-red-500 text-white text-sm font-medium rounded-lg hover:bg-red-600 transition-colors disabled:opacity-50"
              >
                {isBatchDeleting ? (
                  <Loader2 className="w-4 h-4 animate-spin" />
                ) : (
                  <Trash2 className="w-4 h-4" />
                )}
                Delete
              </button>
            </div>
          )}
          {post.attachments && post.attachments.length > 0 ? (
            <div className="grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-3 gap-4">
              {post.attachments.map((attachment, attIdx) => {
                const isDeleting = deletingAttachmentId === attachment.id
                const isConfirming = confirmDeleteId === attachment.id
                const isSelected = selectedIds.has(attachment.id)

                const handleAttSelection = (e: React.MouseEvent) => {
                  e.preventDefault()
                  setSelectedIds(prev => {
                    const next = new Set(prev)
                    if (e.shiftKey && lastSelectedAttIdx.current !== null && lastSelectedAttIdx.current !== attIdx) {
                      const start = Math.min(lastSelectedAttIdx.current, attIdx)
                      const end = Math.max(lastSelectedAttIdx.current, attIdx)
                      for (let i = start; i <= end; i++) {
                        next.add(post.attachments![i].id)
                      }
                    } else {
                      if (next.has(attachment.id)) {
                        next.delete(attachment.id)
                      } else {
                        next.add(attachment.id)
                      }
                    }
                    return next
                  })
                  if (!e.shiftKey) lastSelectedAttIdx.current = attIdx
                }

                return (
                <div key={attachment.id} className={`relative group aspect-square ${selectionMode && isSelected ? 'ring-2 ring-primary ring-offset-1 ring-offset-background rounded-lg' : ''}`}>
                  <div className="w-full h-full">
                    <AttachmentThumbnail
                      attachment={attachment}
                      onClick={selectionMode ? () => handleAttSelection(new MouseEvent('click') as unknown as React.MouseEvent) : isEditing ? undefined : () => openLightbox(attachment)}
                      onImport={(att) => setImportAttachment(att)}
                    />
                  </div>

                  {/* Selection checkbox */}
                  {selectionMode && (
                    <button
                      onClick={(e) => {
                        e.stopPropagation()
                        handleAttSelection(e)
                      }}
                      className={`absolute top-1 left-1 w-6 h-6 rounded-md border-2 flex items-center justify-center transition-colors z-10 ${
                        isSelected
                          ? 'bg-primary border-primary text-primary-foreground'
                          : 'bg-black/50 border-white/70 text-transparent hover:border-white'
                      }`}
                    >
                      {isSelected && <Check className="w-4 h-4" />}
                    </button>
                  )}

                  {/* Delete button - only in edit mode */}
                  {isEditing && !selectionMode && !isConfirming && !isDeleting && (
                    <button
                      onClick={(e) => {
                        e.stopPropagation()
                        setConfirmDeleteId(attachment.id)
                      }}
                      className="absolute top-1 right-1 p-1.5 rounded-full bg-red-500 text-white shadow-md hover:bg-red-600 transition-colors z-10"
                      title="Delete attachment"
                    >
                      <Trash2 className="w-3.5 h-3.5" />
                    </button>
                  )}

                  {/* Confirmation overlay */}
                  {isConfirming && (
                    <div className="absolute inset-0 bg-black/80 rounded-lg flex flex-col items-center justify-center gap-2 p-2 z-10">
                      <AlertTriangle className="w-5 h-5 text-yellow-500" />
                      <span className="text-xs text-white text-center">Delete?</span>
                      <div className="flex gap-1">
                        <button
                          onClick={() => handleDeleteAttachment(attachment.id)}
                          className="px-2 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600"
                        >
                          Yes
                        </button>
                        <button
                          onClick={() => setConfirmDeleteId(null)}
                          className="px-2 py-1 text-xs bg-secondary text-foreground rounded hover:bg-secondary/80"
                        >
                          No
                        </button>
                      </div>
                    </div>
                  )}

                  {/* Deleting spinner overlay */}
                  {isDeleting && (
                    <div className="absolute inset-0 bg-black/80 rounded-lg flex items-center justify-center z-10">
                      <Loader2 className="w-6 h-6 text-white animate-spin" />
                    </div>
                  )}
                </div>
                )
              })}
            </div>
          ) : (
            <div className="text-center py-8 text-muted-foreground">
              <File className="w-8 h-8 mx-auto mb-2 opacity-50" />
              <p className="text-sm">No attachments</p>
              {isEditing && (
                <button
                  onClick={() => fileInputRef.current?.click()}
                  disabled={isUploading}
                  className="mt-2 text-primary hover:underline text-sm"
                >
                  Click to upload
                </button>
              )}
            </div>
          )}
          {/* Batch delete bar (bottom) */}
          {selectionMode && selectedIds.size > 0 && (
            <div className="p-3 bg-secondary/50 border border-border rounded-lg flex items-center justify-between gap-2">
              <div className="flex items-center gap-2">
                <span className="text-sm font-medium">
                  {selectedIds.size} selected
                </span>
                <button
                  onClick={() => {
                    if (selectedIds.size === completedAttachments.length) {
                      setSelectedIds(new Set())
                    } else {
                      setSelectedIds(new Set(completedAttachments.map(a => a.id)))
                    }
                  }}
                  className="text-xs text-primary hover:underline"
                >
                  {selectedIds.size === completedAttachments.length ? 'Deselect all' : 'Select all'}
                </button>
              </div>
              <button
                onClick={handleBatchDelete}
                disabled={isBatchDeleting}
                className="flex items-center gap-1.5 px-3 py-1.5 bg-red-500 text-white text-sm font-medium rounded-lg hover:bg-red-600 transition-colors disabled:opacity-50"
              >
                {isBatchDeleting ? (
                  <Loader2 className="w-4 h-4 animate-spin" />
                ) : (
                  <Trash2 className="w-4 h-4" />
                )}
                Delete
              </button>
            </div>
          )}
        </div>

      </div>

      {/* Import URL Modal */}
      {importAttachment && (
        <ImportUrlModal
          attachment={importAttachment}
          onClose={() => setImportAttachment(null)}
          onSuccess={async () => {
            // Fetch updated post to get new attachment data
            const updatedPost = await api.paidContent.getPost(post.id)
            // Invalidate feed cache
            queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
            // Update selectedPost with new attachments
            onPostUpdated?.({ attachments: updatedPost.attachments })
          }}
        />
      )}
    </div>
  )
}

// Parse filters from URL search params
function parseFiltersFromParams(searchParams: URLSearchParams): FeedFilters {
  const creatorIdsStr = searchParams.get('creator_ids')
  return {
    service: searchParams.get('service') || undefined,
    platform: searchParams.get('platform') || undefined,
    creator_id: searchParams.get('creator_id') ? parseInt(searchParams.get('creator_id')!) : undefined,
    creator_ids: creatorIdsStr ? creatorIdsStr.split(',').map(Number).filter(n => !isNaN(n)) : undefined,
    creator_group_id: searchParams.get('creator_group_id') ? parseInt(searchParams.get('creator_group_id')!) : undefined,
    content_type: searchParams.get('content_type') || undefined,
    min_resolution: searchParams.get('resolution') || undefined,
    tag_id: searchParams.get('tag') ? parseInt(searchParams.get('tag')!) : undefined,
    tagged_user: searchParams.get('tagged_user') || undefined,
    favorites_only: searchParams.get('favorites') === 'true',
    unviewed_only: searchParams.get('unviewed') === 'true',
    downloaded_only: searchParams.get('downloaded') === 'true',
    has_missing: searchParams.get('missing') === 'true',
    missing_description: searchParams.get('nodesc') === 'true',
    hide_empty: searchParams.get('hide_empty') !== 'false',
    date_from: searchParams.get('from') || undefined,
    date_to: searchParams.get('to') || undefined,
    sort_by: searchParams.get('sort') || 'published_at',
    sort_order: (searchParams.get('order') as 'asc' | 'desc') || 'desc',
    search: searchParams.get('q') || undefined,
  }
}

// Convert filters to URL search params
function filtersToParams(filters: FeedFilters): URLSearchParams {
  const params = new URLSearchParams()
  if (filters.service) params.set('service', filters.service)
  if (filters.platform) params.set('platform', filters.platform)
  if (filters.creator_id) params.set('creator_id', filters.creator_id.toString())
  if (filters.creator_ids?.length) params.set('creator_ids', filters.creator_ids.join(','))
  if (filters.creator_group_id) params.set('creator_group_id', filters.creator_group_id.toString())
  if (filters.content_type) params.set('content_type', filters.content_type)
  if (filters.min_resolution) params.set('resolution', filters.min_resolution)
  if (filters.tag_id) params.set('tag', filters.tag_id.toString())
  if (filters.tagged_user) params.set('tagged_user', filters.tagged_user)
  if (filters.favorites_only) params.set('favorites', 'true')
  if (filters.unviewed_only) params.set('unviewed', 'true')
  if (filters.downloaded_only) params.set('downloaded', 'true')
  if (filters.has_missing) params.set('missing', 'true')
  if (filters.missing_description) params.set('nodesc', 'true')
  if (!filters.hide_empty) params.set('hide_empty', 'false')
  if (filters.date_from) params.set('from', filters.date_from)
  if (filters.date_to) params.set('to', filters.date_to)
  if (filters.sort_by && filters.sort_by !== 'published_at') params.set('sort', filters.sort_by)
  if (filters.sort_order && filters.sort_order !== 'desc') params.set('order', filters.sort_order)
  if (filters.search) params.set('q', filters.search)
  return params
}

export default function PaidContentFeed() {
  useBreadcrumb(breadcrumbConfig['/paid-content/feed'])
  const { isFeatureEnabled } = useEnabledFeatures()

  const [searchParams, setSearchParams] = useSearchParams()
  const [selectedPost, setSelectedPost] = useState<PaidContentPost | null>(null)
  // Default to 'social' view on all devices
  const [viewMode, setViewMode] = useState<'feed' | 'gallery' | 'social'>('social')

  // Social view lightbox state
  const [socialLightboxPost, setSocialLightboxPost] = useState<PaidContentPost | null>(null)
  const [socialLightboxIndex, setSocialLightboxIndex] = useState(0)
  const [socialLightboxStartTime, setSocialLightboxStartTime] = useState<number | undefined>(undefined)
  const [socialLightboxWasPlaying, setSocialLightboxWasPlaying] = useState(false)
  // State to pass back to PostDetailView when lightbox closes
  const [videoReturnState, setVideoReturnState] = useState<{ attachmentId: number; time: number; wasPlaying: boolean } | null>(null)

  // Mobile full post modal state
  const [mobileSelectedPost, setMobileSelectedPost] = useState<PaidContentPost | null>(null)

  // Pinned posts collapsible section state (starts collapsed)
  const [pinnedCollapsed, setPinnedCollapsed] = useState(true)

  // Initialize filters from URL params
  const [filters, setFilters] = useState<FeedFilters>(() => parseFiltersFromParams(searchParams))

  // Sync filters to URL when they change (separate from state update to avoid render-time setState)
  // Skip initial mount — filters are already derived from URL params, and calling
  // setSearchParams on mount can interfere with React Router's startTransition
  // causing the component to show empty state before data loads (especially on mobile SPA navigation)
  const filtersInitializedRef = useRef(false)
  useEffect(() => {
    if (!filtersInitializedRef.current) {
      filtersInitializedRef.current = true
      return
    }
    const params = filtersToParams(filters)
    setSearchParams(params, { replace: true })
  }, [filters, setSearchParams])

  const [showFilters, setShowFilters] = useState(false)
  const scrollContainerRef = useRef<HTMLDivElement>(null)

  // Helper to scroll to top of the list
  const scrollToTop = useCallback(() => {
    if (scrollContainerRef.current) {
      scrollContainerRef.current.scrollTo({ top: 0, behavior: 'smooth' })
    } else {
      window.scrollTo({ top: 0, behavior: 'smooth' })
    }
  }, [])

  // Scroll to top when filters change
  const prevFilterKeyRef = useRef<string | null>(null)
  useEffect(() => {
    const currentFilterKey = JSON.stringify(filters)
    if (prevFilterKeyRef.current !== null && prevFilterKeyRef.current !== currentFilterKey) {
      scrollToTop()
    }
    prevFilterKeyRef.current = currentFilterKey
  }, [filters, scrollToTop])

  // Refs to avoid stale closures in scroll callback
  const hasNextPageRef = useRef(false)
  const isFetchingNextPageRef = useRef(false)
  const fetchNextPageRef = useRef<() => void>(() => {})

  // Mobile detection for conditional ref attachment
  const [isDesktop, setIsDesktop] = useState(() => typeof window !== 'undefined' && window.innerWidth >= 1024)
  useEffect(() => {
    const checkDesktop = () => setIsDesktop(window.innerWidth >= 1024)
    window.addEventListener('resize', checkDesktop)
    return () => window.removeEventListener('resize', checkDesktop)
  }, [])

  // Force feed view to social on mobile (feed needs split pane)
  useEffect(() => {
    if (!isDesktop && viewMode === 'feed') {
      setViewMode('social')
    }
  }, [isDesktop, viewMode])

  // Lightbox state for inline viewing from feed
  const [lightboxPost, setLightboxPost] = useState<PaidContentPost | null>(null)
  const [lightboxIndex, setLightboxIndex] = useState<number>(0)

  // Slideshow state
  const [slideshowActive, setSlideshowActive] = useState(false)
  const [slideshowFlatAttachments, setSlideshowFlatAttachments] = useState<PaidContentAttachment[]>([])
  const [slideshowAttachmentPostMap, setSlideshowAttachmentPostMap] = useState<Map<number, PaidContentPost>>(new Map())
  const [slideshowPost, setSlideshowPost] = useState<PaidContentPost | null>(null)
  const [slideshowIndex, setSlideshowIndex] = useState(0)

  // Backend-driven shuffle state (mirrors private gallery pattern)
  const [shuffleSeed, setShuffleSeed] = useState<number>(0)
  const [shuffleEnabled, setShuffleEnabled] = useState(false)
  const [shuffleLoading, setShuffleLoading] = useState(false)
  const [shuffleHasMore, setShuffleHasMore] = useState(false)
  const [shuffleTotal, setShuffleTotal] = useState(0)
  const shuffleOffsetRef = useRef(0)

  // Tag mode state for bulk tagging in gallery view
  const [tagMode, setTagMode] = useState(false)
  const [selectedPostIds, setSelectedPostIds] = useState<Set<number>>(new Set())
  const [selectedTagIds, setSelectedTagIds] = useState<Set<number>>(new Set())

  // Copy to Private Gallery modal state
  const [showCopyToGalleryModal, setShowCopyToGalleryModal] = useState(false)
  const [copyPost, setCopyPost] = useState<PaidContentPost | null>(null)

  const limit = 50

  // Debounce search to avoid firing API calls on every keystroke
  const [debouncedSearch, setDebouncedSearch] = useState(filters.search)
  useEffect(() => {
    const timer = setTimeout(() => setDebouncedSearch(filters.search), 300)
    return () => clearTimeout(timer)
  }, [filters.search])

  // Build query filters using debounced search value
  const queryFilters = useMemo(() => ({
    ...filters,
    search: debouncedSearch,
  }), [filters, debouncedSearch])

  // Stable filter key for query - ensures proper cache invalidation
  const filterKey = JSON.stringify(queryFilters)

  // Infinite query for endless scrolling
  const {
    data,
    isLoading,
    isPending,
    isError,
    error: _feedError,
    refetch,
    isFetchingNextPage,
    hasNextPage,
    fetchNextPage,
  } = useInfiniteQuery({
    queryKey: ['paid-content-feed', filterKey],
    queryFn: ({ pageParam = 0 }) =>
      api.paidContent.getFeed({
        ...queryFilters,
        limit,
        offset: pageParam * limit,
      }),
    getNextPageParam: (lastPage, allPages) => {
      // Handle both array response and object response
      if (!lastPage) return undefined
      const pageData = Array.isArray(lastPage) ? lastPage : (lastPage as any)?.posts || []
      // If the last page has fewer items than limit, we've reached the end
      if (!pageData || !Array.isArray(pageData) || pageData.length < limit) return undefined
      // Use optional chaining for safety (allPages may be undefined in some react-query versions)
      return allPages?.length ?? 0
    },
    initialPageParam: 0,
    refetchOnMount: 'always',
    staleTime: 0,
    gcTime: 5 * 60 * 1000, // Keep cached data for 5 minutes so navigating back shows content instantly
    retry: 3, // Retry more for mobile network resilience
  })

  // Flatten all pages into a single array, deduplicating by post ID
  // (offset-based pagination can produce duplicates when data shifts between page fetches)
  const pages = data?.pages
  const posts = useMemo(() => {
    if (!Array.isArray(pages)) return []
    const seen = new Set<number>()
    return pages.flatMap(page =>
      Array.isArray(page) ? page : (page as any)?.posts || []
    ).filter((post: any) => {
      if (seen.has(post.id)) return false
      seen.add(post.id)
      return true
    })
  }, [pages])

  // Split posts into pinned vs regular for collapsible section
  const pinnedPosts = posts.filter(p => p.is_pinned === 1)
  const regularPosts = posts.filter(p => p.is_pinned !== 1)

  // Gallery view: flat attachment list across all posts for lightbox navigation
  const galleryFlatAttachments = useMemo(() => {
    if (viewMode !== 'gallery' || !posts || posts.length === 0) return []
    const flat: PaidContentAttachment[] = []
    for (const p of [...pinnedPosts, ...regularPosts]) {
      const completed = (p.attachments || []).filter(
        (a: PaidContentAttachment) => a.status === 'completed' && (a.file_type === 'image' || a.file_type === 'video')
      )
      for (const att of completed) flat.push(att)
    }
    return flat
  }, [viewMode, posts, pinnedPosts, regularPosts])

  const galleryAttachmentPostMap = useMemo(() => {
    if (viewMode !== 'gallery' || !posts || posts.length === 0) return new Map<number, PaidContentPost>()
    const postMap = new Map<number, PaidContentPost>()
    for (const p of [...pinnedPosts, ...regularPosts]) {
      for (const att of (p.attachments || [])) {
        postMap.set(att.id, p)
      }
    }
    return postMap
  }, [viewMode, posts, pinnedPosts, regularPosts])

  // Keep refs in sync for IntersectionObserver callback (avoids stale closures)
  useEffect(() => {
    hasNextPageRef.current = !!hasNextPage
    isFetchingNextPageRef.current = isFetchingNextPage
    fetchNextPageRef.current = fetchNextPage
  }, [hasNextPage, isFetchingNextPage, fetchNextPage])

  // Fetch a page of shuffled posts from the backend
  const fetchShufflePage = useCallback(async (seed: number, offset: number, reset: boolean) => {
    setShuffleLoading(true)
    try {
      const response = await api.paidContent.getFeed({
        ...queryFilters,
        shuffle: true,
        shuffle_seed: seed,
        limit: 200,
        offset,
      })
      const newAttachments: PaidContentAttachment[] = []
      const newPostMap = new Map<number, PaidContentPost>()
      for (const post of (response.posts || [])) {
        const completed = (post.attachments || []).filter(
          (a: PaidContentAttachment) => a.status === 'completed' && (a.file_type === 'image' || a.file_type === 'video')
        )
        for (const att of completed) {
          newAttachments.push(att)
          newPostMap.set(att.id, post)
        }
      }
      // Shuffle individual attachments (backend shuffles posts, but we need
      // individual photos mixed across posts, not grouped by post)
      const rng = (s: number) => {
        // Simple seeded PRNG (mulberry32)
        return () => { s |= 0; s = s + 0x6D2B79F5 | 0; let t = Math.imul(s ^ s >>> 15, 1 | s); t ^= t + Math.imul(t ^ t >>> 7, 61 | t); return ((t ^ t >>> 14) >>> 0) / 4294967296 }
      }
      const random = rng(seed + offset)
      for (let i = newAttachments.length - 1; i > 0; i--) {
        const j = Math.floor(random() * (i + 1));
        [newAttachments[i], newAttachments[j]] = [newAttachments[j], newAttachments[i]]
      }
      if (reset) {
        setSlideshowFlatAttachments(newAttachments)
        setSlideshowAttachmentPostMap(newPostMap)
      } else {
        setSlideshowFlatAttachments(prev => [...prev, ...newAttachments])
        setSlideshowAttachmentPostMap(prev => {
          const merged = new Map(prev)
          newPostMap.forEach((v, k) => merged.set(k, v))
          return merged
        })
      }
      setShuffleTotal(response.total_media || response.total || 0)
      setShuffleHasMore((response.posts || []).length === 200)
      shuffleOffsetRef.current = offset + 200
    } catch (e) {
      console.error('Failed to fetch shuffle page:', e)
    } finally {
      setShuffleLoading(false)
    }
  }, [queryFilters])

  // Rebuild slideshow flat list when posts change (new pages loaded during slideshow)
  // Only for non-shuffle mode (shuffle mode uses fetchShufflePage)
  useEffect(() => {
    if (!slideshowActive || shuffleEnabled || !posts || posts.length === 0) return
    const flatAttachments: PaidContentAttachment[] = []
    const postMap = new Map<number, PaidContentPost>()
    for (const p of posts) {
      const completed = (p.attachments || []).filter(
        (a: PaidContentAttachment) => a.status === 'completed' && (a.file_type === 'image' || a.file_type === 'video')
      )
      for (const att of completed) {
        flatAttachments.push(att)
        postMap.set(att.id, p)
      }
    }
    if (flatAttachments.length > slideshowFlatAttachments.length) {
      setSlideshowFlatAttachments(flatAttachments)
      setSlideshowAttachmentPostMap(postMap)
    }
  }, [slideshowActive, shuffleEnabled, posts, slideshowFlatAttachments.length])

  // When pinned posts are collapsed and a pinned post is selected, switch to first regular post
  useEffect(() => {
    if (pinnedCollapsed && selectedPost && selectedPost.is_pinned === 1 && regularPosts.length > 0) {
      setSelectedPost(regularPosts[0])
    }
  }, [pinnedCollapsed])

  // Keyboard navigation for feed and social views
  useEffect(() => {
    if (viewMode !== 'social' && viewMode !== 'feed') return

    const handleKeyDown = (e: KeyboardEvent) => {
      // Don't handle if user is typing in an input
      if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return
      // Don't handle if lightbox is open
      if (socialLightboxPost || lightboxPost) return

      if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
        e.preventDefault()

        // When pinned are collapsed, only navigate through regular posts
        const navPosts = pinnedCollapsed ? regularPosts : posts
        if (!navPosts || navPosts.length === 0) return

        const currentIndex = selectedPost
          ? navPosts.findIndex((p: PaidContentPost) => p.id === selectedPost.id)
          : -1

        let newIndex: number
        if (e.key === 'ArrowDown') {
          newIndex = currentIndex < navPosts.length - 1 ? currentIndex + 1 : currentIndex
        } else {
          newIndex = currentIndex > 0 ? currentIndex - 1 : 0
        }

        if (newIndex !== currentIndex && navPosts[newIndex]) {
          const newPost = navPosts[newIndex]
          setSelectedPost(newPost)

          // Scroll the post into view in the list
          const postElement = scrollContainerRef.current?.querySelector(`[data-post-id="${newPost.id}"]`)
          if (postElement) {
            postElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
          }
        }
      }
    }

    window.addEventListener('keydown', handleKeyDown)
    return () => window.removeEventListener('keydown', handleKeyDown)
  }, [viewMode, posts, regularPosts, pinnedCollapsed, selectedPost, socialLightboxPost, lightboxPost])

  // Get total count from first page response
  const totalFilteredPosts = (pages?.[0] as any)?.total ?? 0
  const totalFilteredMedia = (pages?.[0] as any)?.total_media ?? 0

  const { data: creators } = useInfiniteQuery({
    queryKey: ['paid-content-creators-list'],
    queryFn: () => api.paidContent.getCreators(),
    getNextPageParam: () => undefined,
    initialPageParam: 0,
  })

  // Handle both array and object responses for creators
  const creatorsPages = creators?.pages
  const creatorsList = Array.isArray(creatorsPages) ? creatorsPages.flatMap(page =>
    Array.isArray(page) ? page : (page as any)?.creators || []
  ) : []

  // Service to platforms mapping
  const servicePlatforms: Record<string, string[]> = {
    coomer: ['onlyfans', 'fansly', 'candfans'],
    kemono: ['patreon', 'fanbox', 'gumroad', 'subscribestar', 'discord'],
  }

  // Derive unique platforms and services from creators
  const allPlatforms = (creatorsList && creatorsList.length > 0)
    ? [...new Set(creatorsList.map((c: PaidContentCreator) => c.platform))].sort()
    : []
  const availableServices = (creatorsList && creatorsList.length > 0)
    ? [...new Set(creatorsList.map((c: PaidContentCreator) => c.service_id))].sort()
    : []

  // Filter platforms based on selected service
  // Filter creators based on selected service and/or platform
  const filteredCreatorsList = creatorsList.filter((c: PaidContentCreator) => {
    if (filters.service && c.service_id !== filters.service) return false
    if (filters.platform && c.platform !== filters.platform) return false
    return true
  })

  // Creator groups for filtering
  const { data: creatorGroups = [] } = useQuery({
    queryKey: ['paid-content-creator-groups'],
    queryFn: () => api.paidContent.getCreatorGroups(),
  })

  // Fetch selected group details to show its members in the creator filter
  const { data: selectedGroupDetail } = useQuery({
    queryKey: ['paid-content-creator-group', filters.creator_group_id],
    queryFn: () => api.paidContent.getCreatorGroup(filters.creator_group_id!),
    enabled: !!filters.creator_group_id,
  })

  // When a group is selected, show its member IDs in the creator multiselect
  const groupMemberIds = new Set(
    selectedGroupDetail?.members?.map((m: { id: number }) => m.id) || []
  )

  // Compute active creator IDs for scoping filter dropdowns
  const activeCreatorIds = useMemo(() => {
    if (filters.creator_ids?.length) return filters.creator_ids
    if (filters.creator_id) return [filters.creator_id]
    if (filters.creator_group_id && selectedGroupDetail?.members?.length) {
      return selectedGroupDetail.members.map((m: { id: number }) => m.id)
    }
    return undefined
  }, [filters.creator_ids, filters.creator_id, filters.creator_group_id, selectedGroupDetail])

  // Tags for filtering and bulk tagging (scoped to selected creators)
  const { data: availableTags = [] } = useQuery({
    queryKey: ['paid-content-tags', activeCreatorIds],
    queryFn: () => api.paidContent.getTags(activeCreatorIds),
  })

  // Tagged users for filtering (scoped to selected creators)
  const { data: taggedUsersList = [] } = useQuery({
    queryKey: ['paid-content-tagged-users', activeCreatorIds],
    queryFn: () => api.paidContent.getTaggedUsers(activeCreatorIds),
  })

  // Content types for filtering (scoped to selected creators)
  const { data: scopedContentTypes } = useQuery({
    queryKey: ['paid-content-content-types', activeCreatorIds],
    queryFn: () => api.paidContent.getContentTypes(activeCreatorIds),
    enabled: !!activeCreatorIds?.length,
  })

  // Scoped filters: when a group is selected, narrow Service/Platform/Creator to group members
  const scopedServices = useMemo(() => {
    if (!filters.creator_group_id || !selectedGroupDetail?.members) return availableServices
    const memberServices = new Set(selectedGroupDetail.members.map((m: any) => m.service_id))
    return availableServices.filter((s: string) => memberServices.has(s))
  }, [filters.creator_group_id, selectedGroupDetail, availableServices])

  const scopedAllPlatforms = useMemo(() => {
    if (!filters.creator_group_id || !selectedGroupDetail?.members) return allPlatforms
    const memberPlatforms = new Set(selectedGroupDetail.members.map((m: any) => m.platform))
    return allPlatforms.filter((p: string) => memberPlatforms.has(p))
  }, [filters.creator_group_id, selectedGroupDetail, allPlatforms])

  const scopedAvailablePlatforms = useMemo(() => {
    if (filters.service) {
      return scopedAllPlatforms.filter((p: string) => servicePlatforms[filters.service!]?.includes(p))
    }
    return scopedAllPlatforms
  }, [filters.service, scopedAllPlatforms, servicePlatforms])

  const queryClient = useQueryClient()

  // Mutation for applying tags to multiple posts
  const applyTagsMutation = useMutation({
    mutationFn: ({ postIds, tagIds }: { postIds: number[]; tagIds: number[] }) =>
      api.paidContent.addTagsToPosts(postIds, tagIds),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
      setSelectedPostIds(new Set())
      setSelectedTagIds(new Set())
      setTagMode(false)
    },
  })

  // Toggle post selection in tag mode
  const togglePostSelection = (postId: number) => {
    setSelectedPostIds(prev => {
      const newSet = new Set(prev)
      if (newSet.has(postId)) {
        newSet.delete(postId)
      } else {
        newSet.add(postId)
      }
      return newSet
    })
  }

  // Toggle tag selection
  const toggleTagSelection = (tagId: number) => {
    setSelectedTagIds(prev => {
      const newSet = new Set(prev)
      if (newSet.has(tagId)) {
        newSet.delete(tagId)
      } else {
        newSet.add(tagId)
      }
      return newSet
    })
  }

  // Select all visible posts
  const selectAllPosts = () => {
    const allPostIds = new Set(posts.map(p => p.id))
    setSelectedPostIds(allPostIds)
  }

  // Clear selections and exit tag mode
  const exitTagMode = () => {
    setTagMode(false)
    setSelectedPostIds(new Set())
    setSelectedTagIds(new Set())
  }

  // Apply selected tags to selected posts
  const applyTags = () => {
    if (selectedPostIds.size === 0 || selectedTagIds.size === 0) return
    applyTagsMutation.mutate({
      postIds: Array.from(selectedPostIds),
      tagIds: Array.from(selectedTagIds),
    })
  }

  // Unviewed count query
  const { data: unviewedData } = useQuery({
    queryKey: ['paid-content-unviewed-count'],
    queryFn: () => api.paidContent.getUnviewedCount(),
    staleTime: 60000,
    refetchInterval: 60000,
  })
  const unviewedCount = unviewedData?.count ?? 0

  // Mark all viewed mutation
  const markAllViewedMutation = useMutation({
    mutationFn: () => api.paidContent.markAllViewed(),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['paid-content-unviewed-count'] })
      queryClient.resetQueries({ queryKey: ['paid-content-feed'] })
      if (filters.unviewed_only) {
        handleFilterChange('unviewed_only', false)
      }
    },
  })

  // Unread messages count query
  const { data: unreadMessagesData } = useQuery({
    queryKey: ['paid-content-unread-messages-count'],
    queryFn: () => api.paidContent.getUnreadMessagesCount(),
    staleTime: 60000,
    refetchInterval: 60000,
  })
  const unreadMessagesCount = unreadMessagesData?.count ?? 0

  // Mark all messages read mutation
  const markAllMessagesReadMutation = useMutation({
    mutationFn: () => api.paidContent.markAllMessagesRead(),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['paid-content-unread-messages-count'] })
    },
  })

  // Infinite scroll using scroll event
  useEffect(() => {
    // On mobile, always use window scroll. On desktop, use scroll container if available.
    const scrollContainer = isDesktop ? scrollContainerRef.current : null

    const handleScroll = () => {
      if (!hasNextPageRef.current || isFetchingNextPageRef.current) return

      let shouldFetch = false

      if (scrollContainer) {
        // Desktop: check scroll container
        const { scrollTop, scrollHeight, clientHeight } = scrollContainer
        shouldFetch = scrollHeight - scrollTop - clientHeight < 500
      } else {
        // Mobile: check window/document scroll
        const scrollTop = window.scrollY || document.documentElement.scrollTop
        const scrollHeight = document.documentElement.scrollHeight
        const clientHeight = window.innerHeight
        shouldFetch = scrollHeight - scrollTop - clientHeight < 500
      }

      if (shouldFetch) {
        fetchNextPageRef.current()
      }
    }

    // Attach to appropriate scroll target
    const target = scrollContainer || window
    target.addEventListener('scroll', handleScroll, { passive: true })

    // Check immediately and after a short delay (for initial render)
    handleScroll()
    const timeoutId = setTimeout(handleScroll, 500)

    return () => {
      target.removeEventListener('scroll', handleScroll)
      clearTimeout(timeoutId)
    }
  }, [viewMode, isDesktop])

  // After data changes (e.g. filter change loads page 0), re-check if the container
  // isn't full and more pages should be auto-loaded to fill the visible area
  useEffect(() => {
    if (!hasNextPage || isFetchingNextPage) return
    const checkAndLoad = () => {
      const scrollContainer = isDesktop ? scrollContainerRef.current : null
      if (scrollContainer) {
        const { scrollTop, scrollHeight, clientHeight } = scrollContainer
        if (scrollHeight - scrollTop - clientHeight < 500) {
          fetchNextPage()
        }
      }
    }
    // Small delay to let the DOM render after data change
    const timer = setTimeout(checkAndLoad, 100)
    return () => clearTimeout(timer)
  }, [data, hasNextPage, isFetchingNextPage, fetchNextPage, isDesktop])

  // Deep-link to a specific post via ?post=<id> param
  const [initialPostId] = useState<number | null>(() => {
    const postParam = searchParams.get('post')
    return postParam ? parseInt(postParam) : null
  })
  const [initialPostLoaded, setInitialPostLoaded] = useState(false)

  useEffect(() => {
    if (!initialPostId || initialPostLoaded) return

    const selectPost = (post: PaidContentPost) => {
      if (window.innerWidth < 1024) {
        setMobileSelectedPost(post)
      } else {
        setSelectedPost(post)
      }
    }

    // First check if the post is already in the loaded feed
    const found = posts?.find((p: PaidContentPost) => p.id === initialPostId)
    if (found) {
      selectPost(found)
      setInitialPostLoaded(true)
      // Clean the param from URL
      setSearchParams(prev => {
        const next = new URLSearchParams(prev)
        next.delete('post')
        return next
      }, { replace: true })
      return
    }

    // If posts have loaded but post not found, fetch it directly
    if (posts && posts.length > 0) {
      api.paidContent.getPost(initialPostId).then(post => {
        if (post) {
          selectPost(post)
        }
        setInitialPostLoaded(true)
        setSearchParams(prev => {
          const next = new URLSearchParams(prev)
          next.delete('post')
          return next
        }, { replace: true })
      }).catch(() => {
        setInitialPostLoaded(true)
      })
    }
  }, [initialPostId, initialPostLoaded, posts, setSearchParams])

  // Auto-select first post on desktop only (feed and social views)
  useEffect(() => {
    if (initialPostId && !initialPostLoaded) return // Wait for deep-linked post first
    if (posts && posts.length > 0 && !selectedPost && window.innerWidth >= 1024 && (viewMode === 'feed' || viewMode === 'social')) {
      const firstRegular = posts.find(p => p.is_pinned !== 1)
      if (firstRegular) {
        setSelectedPost(firstRegular)
      } else if (hasNextPage && !isFetchingNextPage) {
        // All loaded posts are pinned — fetch next page to find a regular post
        fetchNextPage()
      }
    }
  }, [posts, selectedPost, viewMode, initialPostId, initialPostLoaded, hasNextPage, isFetchingNextPage, fetchNextPage])

  const handleFilterChange = useCallback((key: keyof FeedFilters, value: any) => {
    setFilters((prev) => {
      const updated = { ...prev, [key]: value }

      // Clear dependent filters when parent filter changes
      if (key === 'service') {
        // When service changes, check if platform is still valid
        if (updated.platform && value) {
          const validPlatforms = servicePlatforms[value] || []
          if (!validPlatforms.includes(updated.platform)) {
            updated.platform = undefined
          }
        }
        // Check if creator is still valid for the new service
        if (updated.creator_id) {
          const creator = creatorsList.find((c: PaidContentCreator) => c.id === updated.creator_id)
          if (creator && creator.service_id !== value) {
            updated.creator_id = undefined
          }
        }
      }

      if (key === 'platform') {
        // When platform changes, check if creator is still valid
        if (updated.creator_id && value) {
          const creator = creatorsList.find((c: PaidContentCreator) => c.id === updated.creator_id)
          if (creator && creator.platform !== value) {
            updated.creator_id = undefined
          }
        }
      }

      // When creator selection changes, clear scoped filters that may become stale
      if (key === 'creator_id' || key === 'creator_ids' || key === 'creator_group_id') {
        updated.tag_id = undefined
        updated.tagged_user = undefined
        updated.content_type = undefined
      }

      return updated
    })
    setSelectedPost(null)
  }, [creatorsList, setFilters, setSelectedPost])

  const clearFilters = () => {
    setFilters({
      favorites_only: false,
      unviewed_only: false,
      downloaded_only: false,
      has_missing: false,
      missing_description: false,
      hide_empty: true,
      sort_by: 'published_at',
      sort_order: 'desc',
      date_from: undefined,
      date_to: undefined,
      min_resolution: undefined,
      creator_ids: undefined,
      creator_group_id: undefined,
    })
  }

  const hasActiveFilters =
    filters.service ||
    filters.platform ||
    filters.creator_id ||
    (filters.creator_ids && filters.creator_ids.length > 0) ||
    filters.creator_group_id ||
    filters.content_type ||
    filters.min_resolution ||
    filters.tag_id ||
    filters.tagged_user ||
    filters.favorites_only ||
    filters.unviewed_only ||
    filters.downloaded_only ||
    filters.has_missing ||
    filters.missing_description ||
    !filters.hide_empty ||
    filters.search ||
    filters.date_from ||
    filters.date_to

  // Build filter sections for FilterBar component
  const filterSections: FilterSection[] = useMemo(() => [
    {
      id: 'creator_group',
      label: 'Creator Group',
      type: 'select' as const,
      options: [
        { value: '', label: 'All Groups' },
        ...creatorGroups.map((group: PaidContentCreatorGroup) => ({
          value: group.id.toString(),
          label: group.name,
        })),
      ],
      value: filters.creator_group_id?.toString() || '',
      onChange: (value) => {
        const groupId = value ? Number(value as string) : undefined
        setFilters(prev => ({
          ...prev,
          creator_group_id: groupId,
          // Clear sub-filters when selecting a group
          creator_id: groupId ? undefined : prev.creator_id,
          creator_ids: groupId ? undefined : prev.creator_ids,
          service: groupId ? undefined : prev.service,
          platform: groupId ? undefined : prev.platform,
          tag_id: groupId ? undefined : prev.tag_id,
        }))
        setSelectedPost(null)
      },
    },
    {
      id: 'creator',
      label: filters.creator_group_id ? 'Group Members' : 'Creator',
      type: 'multiselect' as const,
      options: (filters.creator_group_id
        ? filteredCreatorsList.filter((c: any) => groupMemberIds.has(c.id))
        : filteredCreatorsList
      ).map((creator: any) => ({
        value: creator.id.toString(),
        label: `${creator.username} (${creator.platform})`,
      })),
      value: filters.creator_group_id
        ? [...groupMemberIds].map(String)
        : (filters.creator_ids?.map(String) || []),
      onChange: (value) => {
        const ids = (value as string[]).map(Number).filter(n => !isNaN(n))
        if (filters.creator_group_id) {
          // When a group is active, deselecting members narrows within the group
          // by switching to creator_ids mode
          if (ids.length < groupMemberIds.size && ids.length > 0) {
            setFilters(prev => ({
              ...prev,
              creator_ids: ids,
              creator_id: undefined,
              creator_group_id: undefined,
            }))
          } else if (ids.length === 0) {
            // Cleared all — remove group filter entirely
            setFilters(prev => ({
              ...prev,
              creator_ids: undefined,
              creator_id: undefined,
              creator_group_id: undefined,
            }))
          }
          // If all selected, keep the group filter as-is (no-op)
        } else {
          setFilters(prev => ({
            ...prev,
            creator_ids: ids.length > 0 ? ids : undefined,
            creator_id: undefined,
            creator_group_id: undefined,
          }))
        }
        setSelectedPost(null)
      },
    },
    {
      id: 'service',
      label: 'Service',
      type: 'select' as const,
      options: [
        { value: '', label: 'All Services' },
        ...scopedServices.map((service: string) => ({
          value: service,
          label: getServiceDisplayName(service),
        })),
      ],
      value: filters.service || '',
      onChange: (value) => handleFilterChange('service', (value as string) || undefined),
    },
    {
      id: 'platform',
      label: 'Platform',
      type: 'select' as const,
      options: [
        { value: '', label: 'All Platforms' },
        ...scopedAvailablePlatforms.map((platform: string) => ({
          value: platform,
          label: platform.charAt(0).toUpperCase() + platform.slice(1),
        })),
      ],
      value: filters.platform || '',
      onChange: (value) => handleFilterChange('platform', (value as string) || undefined),
    },
    {
      id: 'content_type',
      label: 'Content Type',
      type: 'select' as const,
      options: [
        { value: '', label: 'All Types' },
        ...(scopedContentTypes && activeCreatorIds?.length
          ? scopedContentTypes
          : ['image', 'video', 'audio']
        ).map(t => ({
          value: t,
          label: t === 'image' ? 'Images' : t === 'video' ? 'Videos' : t === 'audio' ? 'Audio' : t.charAt(0).toUpperCase() + t.slice(1),
        })),
      ],
      value: filters.content_type || '',
      onChange: (value) => handleFilterChange('content_type', (value as string) || undefined),
    },
    {
      id: 'tag',
      label: 'Tag',
      type: 'select' as const,
      options: [
        { value: '', label: 'All Tags' },
        ...availableTags
          .filter((tag: { id: number; name: string; color: string; post_count?: number }) => (tag.post_count ?? 0) > 0)
          .map((tag: { id: number; name: string; color: string; post_count?: number }) => ({
            value: tag.id.toString(),
            label: `${tag.name} (${tag.post_count})`,
          })),
      ],
      value: filters.tag_id?.toString() || '',
      onChange: (value) => handleFilterChange('tag_id', value ? parseInt(value as string) : undefined),
    },
    {
      id: 'tagged_user',
      label: 'Tagged User',
      type: 'select' as const,
      options: [
        { value: '', label: 'All Tagged Users' },
        { value: '__none__', label: 'No Tagged Users' },
        ...taggedUsersList
          .sort((a: {username: string}, b: {username: string}) => a.username.localeCompare(b.username))
          .map((tu: {username: string, post_count: number}) => ({
            value: tu.username,
            label: `@${tu.username} (${tu.post_count})`,
          })),
      ],
      value: filters.tagged_user || '',
      onChange: (value) => handleFilterChange('tagged_user', (value as string) || undefined),
    },
    {
      id: 'resolution',
      label: 'Min Resolution',
      type: 'select' as const,
      options: [
        { value: '', label: 'Any Resolution' },
        { value: '720p', label: '720p+' },
        { value: '1080p', label: '1080p+' },
        { value: '1440p', label: '1440p+' },
        { value: '4k', label: '4K+' },
      ],
      value: filters.min_resolution || '',
      onChange: (value) => handleFilterChange('min_resolution', (value as string) || undefined),
    },
    {
      id: 'sort',
      label: 'Sort By',
      type: 'select' as const,
      options: [
        { value: 'published_at-desc', label: 'Newest First' },
        { value: 'published_at-asc', label: 'Oldest First' },
        { value: 'added_at-desc', label: 'Recently Added' },
        { value: 'download_date-desc', label: 'Recently Downloaded' },
      ],
      value: `${filters.sort_by}-${filters.sort_order}`,
      onChange: (value) => {
        const [sortBy, sortOrder] = (value as string).split('-')
        setFilters((prev) => ({ ...prev, sort_by: sortBy, sort_order: sortOrder as 'asc' | 'desc' }))
        setSelectedPost(null)
      },
    },
    {
      id: 'status',
      label: 'Status',
      type: 'select' as const,
      options: [
        { value: '', label: 'All Status' },
        { value: 'favorites', label: 'Favorites Only' },
        { value: 'unviewed', label: 'Unviewed Only' },
        { value: 'downloaded', label: 'Downloaded Only' },
        { value: 'missing', label: 'Missing Files Only' },
        { value: 'nodesc', label: 'Missing Description' },
        { value: 'empty', label: 'Empty Posts Only' },
      ],
      value: filters.favorites_only ? 'favorites' :
             filters.unviewed_only ? 'unviewed' :
             filters.downloaded_only ? 'downloaded' :
             filters.has_missing ? 'missing' :
             filters.missing_description ? 'nodesc' :
             !filters.hide_empty ? 'empty' : '',
      onChange: (value) => {
        const v = value as string
        // Batch all status changes in a single setFilters call to avoid
        // multiple intermediate states and unnecessary fetches
        setFilters(prev => ({
          ...prev,
          favorites_only: v === 'favorites',
          unviewed_only: v === 'unviewed',
          downloaded_only: v === 'downloaded',
          has_missing: v === 'missing',
          missing_description: v === 'nodesc',
          hide_empty: v !== 'empty',
        }))
        setSelectedPost(null)
      },
    },
  ], [filters, scopedServices, scopedAvailablePlatforms, filteredCreatorsList, availableTags, taggedUsersList, creatorGroups, groupMemberIds, handleFilterChange, setFilters, setSelectedPost])

  // Build active filters array for FilterBar
  const activeFilters: ActiveFilter[] = useMemo(() => {
    const result: ActiveFilter[] = []

    if (filters.service) {
      result.push({
        id: 'service',
        label: 'Service',
        value: filters.service,
        displayValue: getServiceDisplayName(filters.service),
        onRemove: () => handleFilterChange('service', undefined),
      })
    }
    if (filters.platform) {
      result.push({
        id: 'platform',
        label: 'Platform',
        value: filters.platform,
        displayValue: filters.platform.charAt(0).toUpperCase() + filters.platform.slice(1),
        onRemove: () => handleFilterChange('platform', undefined),
      })
    }
    if (filters.creator_group_id) {
      const group = creatorGroups.find((g: PaidContentCreatorGroup) => g.id === filters.creator_group_id)
      result.push({
        id: 'creator_group',
        label: 'Group',
        value: filters.creator_group_id.toString(),
        displayValue: group?.name || `Group ${filters.creator_group_id}`,
        onRemove: () => handleFilterChange('creator_group_id', undefined),
      })
    }
    if (filters.creator_ids && filters.creator_ids.length > 0) {
      const names = filters.creator_ids.map(id => {
        const c = creatorsList.find((cr: PaidContentCreator) => cr.id === id)
        return c?.username || String(id)
      })
      result.push({
        id: 'creators',
        label: 'Creators',
        value: filters.creator_ids.join(','),
        displayValue: names.length <= 2 ? names.join(', ') : `${names.length} creators`,
        onRemove: () => setFilters(prev => ({ ...prev, creator_ids: undefined })),
      })
    }
    if (filters.creator_id) {
      const creator = creatorsList.find((c: PaidContentCreator) => c.id === filters.creator_id)
      result.push({
        id: 'creator',
        label: 'Creator',
        value: filters.creator_id.toString(),
        displayValue: creator?.username || filters.creator_id.toString(),
        onRemove: () => handleFilterChange('creator_id', undefined),
      })
    }
    if (filters.content_type) {
      result.push({
        id: 'content_type',
        label: 'Type',
        value: filters.content_type,
        displayValue: filters.content_type.charAt(0).toUpperCase() + filters.content_type.slice(1) + 's',
        onRemove: () => handleFilterChange('content_type', undefined),
      })
    }
    if (filters.tag_id) {
      const tag = availableTags.find((t: { id: number; name: string }) => t.id === filters.tag_id)
      result.push({
        id: 'tag',
        label: 'Tag',
        value: filters.tag_id.toString(),
        displayValue: tag?.name || filters.tag_id.toString(),
        onRemove: () => handleFilterChange('tag_id', undefined),
      })
    }
    if (filters.tagged_user) {
      result.push({
        id: 'tagged_user',
        label: 'Tagged',
        value: filters.tagged_user,
        displayValue: filters.tagged_user === '__none__' ? 'No Tagged Users' : `@${filters.tagged_user}`,
        onRemove: () => handleFilterChange('tagged_user', undefined),
      })
    }
    if (filters.min_resolution) {
      result.push({
        id: 'resolution',
        label: 'Resolution',
        value: filters.min_resolution,
        displayValue: filters.min_resolution + '+',
        onRemove: () => handleFilterChange('min_resolution', undefined),
      })
    }
    if (filters.date_from) {
      const fromDate = new Date(filters.date_from)
      fromDate.setDate(fromDate.getDate() + 7)
      result.push({
        id: 'date',
        label: 'Date',
        value: filters.date_from,
        displayValue: `${fromDate.toISOString().split('T')[0]} +/-1 week`,
        onRemove: () => {
          handleFilterChange('date_from', undefined)
          handleFilterChange('date_to', undefined)
        },
      })
    }
    if (filters.favorites_only) {
      result.push({
        id: 'favorites',
        label: 'Status',
        value: 'favorites',
        displayValue: 'Favorites',
        onRemove: () => handleFilterChange('favorites_only', false),
      })
    }
    if (filters.unviewed_only) {
      result.push({
        id: 'unviewed',
        label: 'Status',
        value: 'unviewed',
        displayValue: 'Unviewed',
        onRemove: () => handleFilterChange('unviewed_only', false),
      })
    }
    if (filters.downloaded_only) {
      result.push({
        id: 'downloaded',
        label: 'Status',
        value: 'downloaded',
        displayValue: 'Downloaded',
        onRemove: () => handleFilterChange('downloaded_only', false),
      })
    }
    if (filters.has_missing) {
      result.push({
        id: 'missing',
        label: 'Status',
        value: 'missing',
        displayValue: 'Missing Files',
        onRemove: () => handleFilterChange('has_missing', false),
      })
    }
    if (filters.missing_description) {
      result.push({
        id: 'nodesc',
        label: 'Status',
        value: 'nodesc',
        displayValue: 'Missing Description',
        onRemove: () => handleFilterChange('missing_description', false),
      })
    }
    if (!filters.hide_empty) {
      result.push({
        id: 'empty',
        label: 'Status',
        value: 'empty',
        displayValue: 'Empty Posts',
        onRemove: () => handleFilterChange('hide_empty', true),
      })
    }

    return result
  }, [filters, creatorsList, creatorGroups, availableTags, taggedUsersList, handleFilterChange, setFilters])

  // Get completed media attachments for current lightbox post
  const lightboxAttachments = lightboxPost?.attachments?.filter(
    a => a.status === 'completed' && (a.file_type === 'image' || a.file_type === 'video')
  ) || []

  return (
    <div className="space-y-4">
      {/* BundleLightbox for gallery view thumbnails — uses flat list across all posts */}
      {lightboxPost && viewMode === 'gallery' && galleryFlatAttachments.length > 0 && (
        <BundleLightbox
          post={lightboxPost}
          attachments={galleryFlatAttachments}
          currentIndex={lightboxIndex}
          initialSlideshow={slideshowActive}
          onClose={() => {
            setLightboxPost(null)
            setLightboxIndex(0)
            setSlideshowActive(false)
          }}
          onNavigate={(idx) => {
            setLightboxIndex(idx)
            const att = galleryFlatAttachments[idx]
            if (att) {
              const ownerPost = galleryAttachmentPostMap.get(att.id)
              if (ownerPost) setLightboxPost(ownerPost)
            }
          }}
          onViewPost={() => {
            const post = lightboxPost
            setLightboxPost(null)
            setLightboxIndex(0)
            if (post) {
              if (window.innerWidth < 1024) {
                setMobileSelectedPost(post)
              } else {
                setSelectedPost(post)
              }
            }
          }}
          onDelete={async (attachment) => {
            if (!lightboxPost) return
            if (!confirm('Delete this attachment?')) return
            try {
              await api.paidContent.deleteAttachment(lightboxPost.id, attachment.id, true)
              queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
              const remaining = galleryFlatAttachments.filter(a => a.id !== attachment.id)
              if (remaining.length === 0) {
                setLightboxPost(null)
                setLightboxIndex(0)
                return
              }
              if (lightboxIndex >= remaining.length) {
                setLightboxIndex(remaining.length - 1)
              }
            } catch (error) {
              console.error('Failed to delete attachment:', error)
              alert('Failed to delete attachment')
            }
          }}
        />
      )}
      {/* BundleLightbox for feed/social view — single post attachments */}
      {lightboxPost && viewMode !== 'gallery' && lightboxAttachments.length > 0 && (
        <BundleLightbox
          post={lightboxPost}
          attachments={lightboxAttachments}
          currentIndex={lightboxIndex}
          initialSlideshow={slideshowActive}
          onClose={() => {
            setLightboxPost(null)
            setLightboxIndex(0)
            setSlideshowActive(false)
          }}
          onNavigate={setLightboxIndex}
          onViewPost={() => {
            const post = lightboxPost
            setLightboxPost(null)
            setLightboxIndex(0)
            if (post) {
              if (window.innerWidth < 1024) {
                setMobileSelectedPost(post)
              } else {
                setSelectedPost(post)
              }
            }
          }}
          onDelete={async (attachment) => {
            if (!lightboxPost) return
            if (!confirm('Delete this attachment?')) return
            try {
              await api.paidContent.deleteAttachment(lightboxPost.id, attachment.id, true)
              queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
              const remaining = lightboxAttachments.filter(a => a.id !== attachment.id)
              if (remaining.length === 0) {
                setLightboxPost(null)
                setLightboxIndex(0)
                return
              }
              if (lightboxIndex >= remaining.length) {
                setLightboxIndex(remaining.length - 1)
              }
            } catch (error) {
              console.error('Failed to delete attachment:', error)
              alert('Failed to delete attachment')
            }
          }}
        />
      )}

      {/* Slideshow BundleLightbox - uses flat attachment list across all posts */}
      {slideshowActive && slideshowFlatAttachments.length > 0 && (
        <BundleLightbox
          post={slideshowAttachmentPostMap.get(slideshowFlatAttachments[slideshowIndex]?.id) || slideshowPost || posts[0]}
          attachments={slideshowFlatAttachments}
          currentIndex={slideshowIndex}
          onClose={() => {
            setSlideshowActive(false)
            setSlideshowFlatAttachments([])
            setSlideshowAttachmentPostMap(new Map())
            setSlideshowPost(null)
            setSlideshowIndex(0)
            setShuffleEnabled(false)
          }}
          onNavigate={(idx) => {
            setSlideshowIndex(idx)
            const att = slideshowFlatAttachments[idx]
            if (att) {
              const ownerPost = slideshowAttachmentPostMap.get(att.id)
              if (ownerPost) setSlideshowPost(ownerPost)
            }
          }}
          initialSlideshow={true}
          initialInterval={3000}
          totalCount={shuffleEnabled ? shuffleTotal : totalFilteredMedia}
          hasMore={shuffleEnabled ? shuffleHasMore : !!hasNextPage}
          onLoadMore={shuffleEnabled
            ? () => { if (!shuffleLoading && shuffleHasMore) fetchShufflePage(shuffleSeed, shuffleOffsetRef.current, false) }
            : () => { if (hasNextPage && !isFetchingNextPage) fetchNextPage() }
          }
          onShuffleChange={(enabled) => {
            if (enabled) {
              const seed = Math.floor(Math.random() * 1000000)
              setShuffleSeed(seed)
              setShuffleEnabled(true)
              setShuffleTotal(totalFilteredMedia || totalFilteredPosts || 0)
              setSlideshowIndex(0)
              shuffleOffsetRef.current = 0
              fetchShufflePage(seed, 0, true)
            } else {
              setShuffleEnabled(false)
              // Switch back to non-shuffled: rebuild from loaded posts
              const flatAttachments: PaidContentAttachment[] = []
              const postMap = new Map<number, PaidContentPost>()
              for (const p of posts) {
                const completed = (p.attachments || []).filter(
                  (a: PaidContentAttachment) => a.status === 'completed' && (a.file_type === 'image' || a.file_type === 'video')
                )
                for (const att of completed) {
                  flatAttachments.push(att)
                  postMap.set(att.id, p)
                }
              }
              setSlideshowFlatAttachments(flatAttachments)
              setSlideshowAttachmentPostMap(postMap)
              setSlideshowIndex(0)
            }
          }}
          isShuffled={shuffleEnabled}
        />
      )}

      {/* Header */}
      <div className="flex items-center justify-between">
        <div>
          <h1 className="text-2xl sm:text-3xl font-bold flex items-center gap-2 sm:gap-3">
            <ScrollText className="w-8 h-8 text-violet-500" />
            Feed
          </h1>
          <p className="text-slate-600 dark:text-slate-400 mt-1 hidden sm:block">
            Browse posts from tracked creators
          </p>
        </div>
        <div className="flex items-center gap-2">
          {/* Slideshow button - gallery view: all items, feed/social view: selected post only */}
          {(() => {
            if (viewMode === 'gallery') {
              const totalMedia = posts?.reduce((sum, p) => sum + ((p.attachments || []).filter((a: PaidContentAttachment) => a.status === 'completed' && (a.file_type === 'image' || a.file_type === 'video')).length), 0) || 0
              return totalMedia > 1
            }
            const post = selectedPost || (posts && posts.length > 0 ? posts[0] : null)
            const mediaCount = post?.attachments?.filter((a: PaidContentAttachment) => a.status === 'completed' && (a.file_type === 'image' || a.file_type === 'video')).length || 0
            return mediaCount > 1
          })() && (
            <button
              onClick={() => {
                if (viewMode === 'gallery') {
                  // Gallery view: start backend-driven shuffle slideshow
                  const seed = Math.floor(Math.random() * 1000000)
                  setShuffleSeed(seed)
                  setShuffleEnabled(true)
                  setShuffleTotal(totalFilteredMedia || totalFilteredPosts || 0)
                  shuffleOffsetRef.current = 0
                  fetchShufflePage(seed, 0, true)
                  setSlideshowIndex(0)
                  setSlideshowPost(null)
                  setSlideshowActive(true)
                } else {
                  // Feed/Social view: slideshow the currently selected post only
                  const post = selectedPost || (posts.length > 0 ? posts[0] : null)
                  if (post) {
                    const completed = (post.attachments || []).filter(
                      (a: PaidContentAttachment) => a.status === 'completed' && (a.file_type === 'image' || a.file_type === 'video')
                    )
                    if (completed.length > 0) {
                      setLightboxPost(post)
                      setLightboxIndex(0)
                      setSlideshowActive(true)
                    }
                  }
                }
              }}
              className="hidden lg:flex items-center gap-2 px-3 py-2 rounded-lg bg-secondary text-foreground hover:bg-secondary/80 transition-colors"
              title="Start Slideshow"
            >
              <Play className="w-4 h-4" />
              <span className="text-sm">Slideshow</span>
            </button>
          )}
          {/* Tag Mode button - only show in gallery view */}
          {viewMode === 'gallery' && !tagMode && (
            <button
              onClick={() => setTagMode(true)}
              className="hidden lg:flex items-center gap-2 px-3 py-2 rounded-lg bg-secondary text-foreground hover:bg-secondary/80 transition-colors"
            >
              <Tag className="w-4 h-4" />
              <span className="text-sm">Tag Mode</span>
            </button>
          )}
          {/* View toggle - Social + Gallery */}
          <div className="flex items-center bg-secondary rounded-lg p-1">
            <button
              onClick={() => { setViewMode('social'); exitTagMode(); }}
              className={`p-2 rounded-md transition-colors ${
                viewMode === 'social'
                  ? 'bg-primary text-primary-foreground'
                  : 'text-muted-foreground hover:text-foreground'
              }`}
              title="Social view"
            >
              <Columns className="w-4 h-4" />
            </button>
            <button
              onClick={() => setViewMode('gallery')}
              className={`p-2 rounded-md transition-colors ${
                viewMode === 'gallery'
                  ? 'bg-primary text-primary-foreground'
                  : 'text-muted-foreground hover:text-foreground'
              }`}
              title="Gallery view"
            >
              <LayoutGrid className="w-4 h-4" />
            </button>
          </div>
          {/* Mobile filter toggle */}
          <button
            onClick={() => setShowFilters(!showFilters)}
            className="lg:hidden flex items-center gap-2 px-3 py-2 rounded-lg bg-secondary text-foreground"
          >
            <span className="text-sm">Filters</span>
            {activeFilters.length > 0 && (
              <span className="w-2 h-2 rounded-full bg-blue-500"></span>
            )}
          </button>
        </div>
      </div>

      {/* Filters - Using FilterBar component */}
      <div className={`card-glass-hover rounded-xl p-4 relative z-30 ${showFilters ? 'block' : 'hidden lg:block'}`}>
        <FilterBar
          searchValue={filters.search || ''}
          onSearchChange={(value) => handleFilterChange('search', value || undefined)}
          searchPlaceholder="Search posts..."
          filterSections={filterSections}
          activeFilters={activeFilters}
          onClearAll={clearFilters}
          totalCount={viewMode === 'gallery' ? (totalFilteredMedia || totalFilteredPosts) : totalFilteredPosts}
          countLabel={viewMode === 'gallery' ? ((totalFilteredMedia || totalFilteredPosts) === 1 ? 'item' : 'items') : (totalFilteredPosts === 1 ? 'post' : 'posts')}
          advancedFilters={{
            dateFrom: {
              value: filters.date_from ? (() => {
                const fromDate = new Date(filters.date_from)
                fromDate.setDate(fromDate.getDate() + 7)
                return fromDate.toISOString().split('T')[0]
              })() : '',
              onChange: (value) => {
                if (value) {
                  const centerDate = new Date(value)
                  const fromDate = new Date(centerDate)
                  const toDate = new Date(centerDate)
                  fromDate.setDate(centerDate.getDate() - 7)
                  toDate.setDate(centerDate.getDate() + 7)
                  handleFilterChange('date_from', fromDate.toISOString().split('T')[0])
                  handleFilterChange('date_to', toDate.toISOString().split('T')[0])
                } else {
                  handleFilterChange('date_from', undefined)
                  handleFilterChange('date_to', undefined)
                }
              },
              label: 'Date (+/-1 week)',
            },
          }}
        />
      </div>

      {/* Unviewed posts banner */}
      {unviewedCount > 0 && !filters.unviewed_only && (
        <div className="flex items-center justify-between gap-3 bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 rounded-lg shadow-sm border border-blue-200 dark:border-blue-800 px-4 py-3">
          <div className="flex items-center gap-3">
            <div className="flex items-center justify-center w-8 h-8 rounded-full bg-blue-600 text-white text-sm font-bold shrink-0">
              {unviewedCount}
            </div>
            <span className="text-sm font-medium text-blue-700 dark:text-blue-300">
              {unviewedCount === 1 ? '1 unviewed post' : `${unviewedCount} unviewed posts`}
            </span>
          </div>
          <div className="flex items-center gap-2 shrink-0">
            <button
              onClick={() => handleFilterChange('unviewed_only', true)}
              className="px-3 py-1.5 rounded-lg bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 transition-colors flex items-center gap-1.5"
            >
              <Eye className="w-3.5 h-3.5" />
              View unviewed
            </button>
            <button
              onClick={() => markAllViewedMutation.mutate()}
              disabled={markAllViewedMutation.isPending}
              className="px-3 py-1.5 rounded-md text-blue-700 dark:text-blue-300 text-sm hover:bg-blue-100 dark:hover:bg-blue-900/30 transition-colors"
            >
              {markAllViewedMutation.isPending ? 'Marking...' : 'Mark all viewed'}
            </button>
          </div>
        </div>
      )}

      {/* Active unviewed filter banner */}
      {filters.unviewed_only && (
        <div className="flex items-center justify-between gap-3 bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 rounded-lg shadow-sm border border-blue-200 dark:border-blue-800 px-4 py-3">
          <span className="text-sm font-medium text-blue-700 dark:text-blue-300">
            Showing unviewed posts
          </span>
          <div className="flex items-center gap-2 shrink-0">
            <button
              onClick={() => markAllViewedMutation.mutate()}
              disabled={markAllViewedMutation.isPending}
              className="px-3 py-1.5 rounded-md text-blue-700 dark:text-blue-300 text-sm font-medium hover:bg-blue-100 dark:hover:bg-blue-900/30 transition-colors"
            >
              {markAllViewedMutation.isPending ? 'Marking...' : 'Mark all viewed'}
            </button>
            <button
              onClick={() => handleFilterChange('unviewed_only', false)}
              className="px-3 py-1.5 rounded-md text-blue-700 dark:text-blue-300 text-sm hover:bg-blue-100 dark:hover:bg-blue-900/30 transition-colors"
            >
              Show all
            </button>
          </div>
        </div>
      )}

      {/* Unread messages banner */}
      {unreadMessagesCount > 0 && (
        <div className="flex items-center justify-between gap-3 bg-gradient-to-r from-violet-50 to-purple-50 dark:from-violet-900/20 dark:to-purple-900/20 rounded-lg shadow-sm border border-violet-200 dark:border-violet-800 px-4 py-3">
          <div className="flex items-center gap-3">
            <div className="flex items-center justify-center w-8 h-8 rounded-full bg-violet-600 text-white text-sm font-bold shrink-0">
              {unreadMessagesCount}
            </div>
            <span className="text-sm font-medium text-violet-700 dark:text-violet-300">
              {unreadMessagesCount === 1 ? '1 unread message' : `${unreadMessagesCount} unread messages`}
            </span>
          </div>
          <div className="flex items-center gap-2 shrink-0">
            <RouterLink
              to="/paid-content/messages"
              className="px-3 py-1.5 rounded-lg bg-violet-600 text-white text-sm font-medium hover:bg-violet-700 transition-colors flex items-center gap-1.5"
            >
              <MessageSquare className="w-3.5 h-3.5" />
              View messages
            </RouterLink>
            <button
              onClick={() => markAllMessagesReadMutation.mutate()}
              disabled={markAllMessagesReadMutation.isPending}
              className="px-3 py-1.5 rounded-md text-violet-700 dark:text-violet-300 text-sm hover:bg-violet-100 dark:hover:bg-violet-900/30 transition-colors"
            >
              {markAllMessagesReadMutation.isPending ? 'Marking...' : 'Mark all read'}
            </button>
          </div>
        </div>
      )}

      {/* Mobile: Full-screen post detail modal */}
      {selectedPost && (
        <div
          className="lg:hidden fixed inset-0 z-50 bg-background overflow-y-auto"
          style={{ paddingTop: 'env(safe-area-inset-top)', overscrollBehavior: 'contain' }}
        >
          <PostDetail
            post={selectedPost}
            onClose={() => setSelectedPost(null)}
            onPostUpdated={(updates) => {
              if (updates) {
                setSelectedPost(prev => prev ? { ...prev, ...updates } : null)
              }
            }}
          />
        </div>
      )}

      {/* Gallery View */}
      {viewMode === 'gallery' && (
        <div className="bg-card rounded-xl border border-border overflow-hidden relative">
          {/* Tag Mode Header Bar */}
          {tagMode && (
            <div className="sticky top-0 z-10 bg-primary/10 border-b border-primary/20 px-4 py-3 flex items-center justify-between">
              <div className="flex items-center gap-4">
                <span className="text-sm font-medium text-foreground">
                  {selectedPostIds.size} post{selectedPostIds.size !== 1 ? 's' : ''} selected
                </span>
                <button
                  onClick={selectAllPosts}
                  className="text-sm text-primary hover:underline"
                >
                  Select All ({posts.length})
                </button>
                {selectedPostIds.size > 0 && (
                  <button
                    onClick={() => setSelectedPostIds(new Set())}
                    className="text-sm text-muted-foreground hover:text-foreground"
                  >
                    Clear Selection
                  </button>
                )}
              </div>
              <button
                onClick={exitTagMode}
                className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-secondary text-foreground hover:bg-secondary/80 transition-colors"
              >
                <X className="w-4 h-4" />
                <span className="text-sm">Exit Tag Mode</span>
              </button>
            </div>
          )}

          <div
            ref={viewMode === 'gallery' ? scrollContainerRef : undefined}
            className="p-4 overflow-y-auto"
            style={{ maxHeight: tagMode ? 'calc(100vh - 400px)' : 'calc(100vh - 280px)', minHeight: '400px' }}
          >
            {(isLoading || isPending) && (!posts || posts.length === 0) ? (
              <div className="flex items-center justify-center h-64">
                <div className="animate-spin rounded-full h-8 w-8 border-2 border-primary border-t-transparent"></div>
              </div>
            ) : isError && (!posts || posts.length === 0) ? (
              <div className="flex flex-col items-center justify-center h-64 text-muted-foreground">
                <AlertTriangle className="w-12 h-12 mb-2 opacity-50 text-destructive" />
                <p>Failed to load feed</p>
                <button onClick={() => refetch()} className="mt-2 text-sm text-primary hover:underline">
                  Tap to retry
                </button>
              </div>
            ) : (!posts || posts.length === 0) ? (
              <div className="flex flex-col items-center justify-center h-64 text-muted-foreground">
                <LayoutGrid className="w-12 h-12 mb-2 opacity-50" />
                <p>No media found</p>
                {hasActiveFilters && (
                  <button onClick={clearFilters} className="mt-2 text-sm text-primary hover:underline">
                    Clear filters
                  </button>
                )}
              </div>
            ) : (
              <>
                {/* Thumbnail grid - all attachments from all posts (pinned first, then regular - same as social view) */}
                <div className="grid grid-cols-4 xl:grid-cols-6 2xl:grid-cols-8 gap-2">
                  {[...pinnedPosts, ...regularPosts].flatMap((post) =>
                    (post.attachments || [])
                      .filter((att: PaidContentAttachment) => att.status === 'completed' && (att.file_type === 'image' || att.file_type === 'video'))
                      .map((att: PaidContentAttachment) => {
                        const _pfExts = ['jpg', 'jpeg', 'png', 'webp', 'gif']
                        const _isPF = att.file_type === 'video' && _pfExts.includes(att.extension?.toLowerCase() || '')
                        const isVideo = att.file_type === 'video' && !_isPF
                        const isPostSelected = selectedPostIds.has(post.id)
                        return (
                          <button
                            key={`${post.id}-${att.id}`}
                            type="button"
                            className={`relative aspect-square bg-secondary rounded-lg overflow-hidden cursor-pointer transition-all group ${
                              tagMode && isPostSelected
                                ? 'ring-2 ring-primary ring-offset-2 ring-offset-background'
                                : 'hover:ring-2 hover:ring-primary'
                            }`}
                            onClick={() => {
                              if (tagMode) {
                                // In tag mode, toggle post selection
                                togglePostSelection(post.id)
                              } else {
                                // Normal mode - open lightbox with all gallery attachments
                                const flatIdx = galleryFlatAttachments.findIndex((a: PaidContentAttachment) => a.id === att.id)
                                setLightboxPost(post)
                                setLightboxIndex(flatIdx >= 0 ? flatIdx : 0)
                              }
                            }}
                          >
                            <img
                              src={`/api/paid-content/files/thumbnail/${att.id}?size=large&${att.file_hash ? `v=${att.file_hash.slice(0, 8)}` : THUMB_CACHE_V}`}
                              alt=""
                              className={`w-full h-full object-cover transition-opacity ${
                                tagMode && isPostSelected ? 'opacity-80' : ''
                              }`}
                              loading="lazy"
                              onError={(e) => {
                                (e.target as HTMLImageElement).style.display = 'none'
                              }}
                            />
                            {/* Selection checkbox overlay in tag mode */}
                            {tagMode && (
                              <div className={`absolute top-2 left-2 w-6 h-6 rounded-md flex items-center justify-center transition-all ${
                                isPostSelected
                                  ? 'bg-primary text-primary-foreground'
                                  : 'bg-black/50 text-white'
                              }`}>
                                {isPostSelected ? (
                                  <Check className="w-4 h-4" />
                                ) : (
                                  <Square className="w-4 h-4" />
                                )}
                              </div>
                            )}
                            {/* Video indicator */}
                            {isVideo && !tagMode && (
                              <div className="absolute inset-0 flex items-center justify-center">
                                <div className="w-10 h-10 rounded-full bg-black/50 flex items-center justify-center">
                                  <Video className="w-5 h-5 text-white" />
                                </div>
                              </div>
                            )}
                            {/* Duration badge for videos */}
                            {isVideo && att.duration && (
                              <div className="absolute bottom-1 right-1 bg-black/70 text-white text-xs px-1.5 py-0.5 rounded">
                                {Math.floor(att.duration / 60)}:{Math.floor(att.duration % 60).toString().padStart(2, '0')}
                              </div>
                            )}
                            {/* Lock overlay for DRM preview frames */}
                            {_isPF && !tagMode && (
                              <div className="absolute inset-0 bg-black/20 flex items-center justify-center">
                                <Lock className="w-6 h-6 text-white/70" />
                              </div>
                            )}
                            {/* Creator/date info on hover - hide in tag mode */}
                            {!tagMode && (
                              <div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/80 to-transparent p-2 opacity-0 group-hover:opacity-100 transition-opacity">
                                <div className="text-white text-xs truncate font-medium">{post.display_name || post.username}</div>
                                <div className="text-white/70 text-xs">
                                  @{post.username} · {post.published_at ? new Date(post.published_at).toLocaleDateString() : 'Unknown'}
                                </div>
                              </div>
                            )}
                          </button>
                        )
                      })
                  )}
                </div>

                {/* Infinite scroll trigger */}
                <div className="p-4 flex items-center justify-center">
                  {isFetchingNextPage ? (
                    <div className="flex items-center gap-2 text-muted-foreground">
                      <Loader2 className="w-5 h-5 animate-spin" />
                      <span className="text-sm">Loading more...</span>
                    </div>
                  ) : hasNextPage ? (
                    <span className="text-sm text-muted-foreground">Scroll for more</span>
                  ) : (posts && posts.length > 0) ? (
                    <span className="text-sm text-muted-foreground">End of gallery</span>
                  ) : null}
                </div>
              </>
            )}
          </div>

          {/* Tag Selection Bar - Bottom of gallery in tag mode */}
          {tagMode && (
            <div className="sticky bottom-0 z-10 bg-card border-t border-border p-4">
              <div className="flex flex-col gap-3">
                <div className="flex items-center justify-between">
                  <span className="text-sm font-medium text-foreground flex items-center gap-2">
                    <Tag className="w-4 h-4" />
                    Select tags to apply:
                  </span>
                  {selectedTagIds.size > 0 && (
                    <span className="text-sm text-muted-foreground">
                      {selectedTagIds.size} tag{selectedTagIds.size !== 1 ? 's' : ''} selected
                    </span>
                  )}
                </div>
                <div className="flex flex-wrap gap-2 max-h-32 overflow-y-auto">
                  {availableTags.map((tag) => {
                    const isSelected = selectedTagIds.has(tag.id)
                    return (
                      <button
                        key={tag.id}
                        type="button"
                        onClick={() => toggleTagSelection(tag.id)}
                        className={`px-3 py-1.5 rounded-full text-sm font-medium transition-all border-2 ${
                          isSelected
                            ? 'shadow-md'
                            : 'opacity-60 hover:opacity-100'
                        }`}
                        style={{
                          backgroundColor: isSelected ? tag.color : `${tag.color}20`,
                          color: isSelected ? '#fff' : tag.color,
                          borderColor: tag.color,
                        }}
                      >
                        {tag.name}
                      </button>
                    )
                  })}
                </div>
                <div className="flex items-center justify-end gap-3 pt-2 border-t border-border">
                  <button
                    onClick={exitTagMode}
                    className="px-4 py-2 rounded-lg bg-secondary text-foreground hover:bg-secondary/80 transition-colors"
                  >
                    Cancel
                  </button>
                  {isFeatureEnabled('/private-gallery') && (
                  <button
                    onClick={() => setShowCopyToGalleryModal(true)}
                    disabled={selectedPostIds.size === 0}
                    className="px-4 py-2 rounded-lg bg-violet-600 text-white hover:bg-violet-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
                  >
                    <ImagePlus className="w-4 h-4" />
                    Copy to Private ({selectedPostIds.size})
                  </button>
                  )}
                  <button
                    onClick={applyTags}
                    disabled={selectedPostIds.size === 0 || selectedTagIds.size === 0 || applyTagsMutation.isPending}
                    className="px-4 py-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
                  >
                    {applyTagsMutation.isPending ? (
                      <>
                        <Loader2 className="w-4 h-4 animate-spin" />
                        Applying...
                      </>
                    ) : (
                      <>
                        <Check className="w-4 h-4" />
                        Apply Tags ({selectedPostIds.size} post{selectedPostIds.size !== 1 ? 's' : ''})
                      </>
                    )}
                  </button>
                </div>
              </div>
            </div>
          )}
        </div>
      )}

      {/* Social View - Desktop only (Fansly-style) */}
      {viewMode === 'social' && (
        <>
          {/* BundleLightbox for social view */}
          {socialLightboxPost && (
            <BundleLightbox
              post={socialLightboxPost}
              attachments={socialLightboxPost.attachments?.filter(a => a.status === 'completed' && a.file_type !== 'audio') || []}
              currentIndex={socialLightboxIndex}
              startTime={socialLightboxStartTime}
              wasPlaying={socialLightboxWasPlaying}
              onClose={() => {
                setSocialLightboxPost(null)
                setSocialLightboxIndex(0)
                setSocialLightboxStartTime(undefined)
                setSocialLightboxWasPlaying(false)
              }}
              onCloseWithVideoState={(attachmentId, time, wasPlaying) => {
                // Pass video state back to PostDetailView
                setVideoReturnState({ attachmentId, time, wasPlaying })
                setSocialLightboxPost(null)
                setSocialLightboxIndex(0)
                setSocialLightboxStartTime(undefined)
                setSocialLightboxWasPlaying(false)
              }}
              onNavigate={(index) => {
                setSocialLightboxIndex(index)
                setSocialLightboxStartTime(undefined)  // Clear startTime when navigating to new item
              }}
              onViewPost={() => {
                const post = socialLightboxPost
                setSocialLightboxPost(null)
                setSocialLightboxIndex(0)
                setSocialLightboxStartTime(undefined)
                setSocialLightboxWasPlaying(false)
                if (post) {
                  if (window.innerWidth < 1024) {
                    setMobileSelectedPost(post)
                  } else {
                    setSelectedPost(post)
                  }
                }
              }}
              onToggleFavorite={async () => {
                await api.paidContent.toggleFavorite(socialLightboxPost.id)
                queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
                // Update local state to reflect change
                setSocialLightboxPost(prev => prev ? { ...prev, is_favorited: prev.is_favorited ? 0 : 1 } : null)
              }}
              onDelete={async (attachment) => {
                if (!confirm('Delete this attachment?')) return
                try {
                  await api.paidContent.deleteAttachment(socialLightboxPost.id, attachment.id, true)
                  const newAttachments = (socialLightboxPost.attachments || []).filter(a => a.id !== attachment.id)
                  queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
                  // If no completed visual attachments left, close lightbox
                  const remaining = newAttachments.filter(a => a.status === 'completed' && a.file_type !== 'audio')
                  if (remaining.length === 0) {
                    setSocialLightboxPost(null)
                    setSocialLightboxIndex(0)
                    return
                  }
                  // Adjust index if needed
                  setSocialLightboxPost({ ...socialLightboxPost, attachments: newAttachments })
                  if (socialLightboxIndex >= remaining.length) {
                    setSocialLightboxIndex(remaining.length - 1)
                  }
                } catch (error) {
                  console.error('Failed to delete attachment:', error)
                  alert('Failed to delete attachment')
                }
              }}
            />
          )}

          <div className="hidden lg:block bg-card rounded-xl border border-border overflow-hidden">
            <div className="flex h-[calc(100vh-280px)] min-h-[500px]">
              {/* Post List - Left sidebar (narrower in social view) */}
              <div
                ref={viewMode === 'social' ? scrollContainerRef : undefined}
                className="w-80 xl:w-96 border-r border-border overflow-y-auto flex-shrink-0"
              >
                {(isLoading || isPending) && (!posts || posts.length === 0) ? (
                  <div className="flex items-center justify-center h-64">
                    <div className="animate-spin rounded-full h-8 w-8 border-2 border-primary border-t-transparent"></div>
                  </div>
                ) : isError && (!posts || posts.length === 0) ? (
                  <div className="flex flex-col items-center justify-center h-64 text-muted-foreground">
                    <AlertTriangle className="w-12 h-12 mb-2 opacity-50 text-destructive" />
                    <p>Failed to load feed</p>
                    <button onClick={() => refetch()} className="mt-2 text-sm text-primary hover:underline">
                      Tap to retry
                    </button>
                  </div>
                ) : (!posts || posts.length === 0) ? (
                  <div className="flex flex-col items-center justify-center h-64 text-muted-foreground">
                    <ScrollText className="w-12 h-12 mb-2 opacity-50" />
                    <p>No posts found</p>
                    {hasActiveFilters && (
                      <button onClick={clearFilters} className="mt-2 text-sm text-primary hover:underline">
                        Clear filters
                      </button>
                    )}
                  </div>
                ) : (
                  <>
                    {/* Pinned posts - collapsible */}
                    {pinnedPosts.length > 0 && (
                      <div className="border-b border-border">
                        <button
                          onClick={() => setPinnedCollapsed(!pinnedCollapsed)}
                          className="w-full flex items-center gap-2 px-3 py-2 text-sm text-amber-500 hover:bg-accent/50"
                        >
                          <Pin className="w-3.5 h-3.5" />
                          <span>Pinned ({pinnedPosts.length})</span>
                          <ChevronDown className={`w-4 h-4 ml-auto transition-transform ${pinnedCollapsed ? '-rotate-90' : ''}`} />
                        </button>
                        {!pinnedCollapsed && pinnedPosts.map(post => (
                          <PostCard
                            key={post.id}
                            post={post}
                            isSelected={selectedPost?.id === post.id}
                            onClick={() => setSelectedPost(post)}
                            onOpenLightbox={(attachmentIndex) => {
                              setSocialLightboxPost(post)
                              setSocialLightboxIndex(attachmentIndex)
                            }}
                            onCopyToGallery={isFeatureEnabled('/private-gallery') ? setCopyPost : undefined}
  
                          />
                        ))}
                      </div>
                    )}

                    {/* Regular posts */}
                    {regularPosts.map((post) => (
                      <PostCard
                        key={post.id}
                        post={post}
                        isSelected={selectedPost?.id === post.id}
                        onClick={() => setSelectedPost(post)}
                        onOpenLightbox={(attachmentIndex) => {
                          setSocialLightboxPost(post)
                          setSocialLightboxIndex(attachmentIndex)
                        }}
                        onCopyToGallery={isFeatureEnabled('/private-gallery') ? setCopyPost : undefined}
                      />
                    ))}

                    {/* Infinite scroll trigger */}
                    <div className="p-4 flex items-center justify-center">
                      {isFetchingNextPage ? (
                        <div className="flex items-center gap-2 text-muted-foreground">
                          <Loader2 className="w-5 h-5 animate-spin" />
                          <span className="text-sm">Loading more...</span>
                        </div>
                      ) : hasNextPage ? (
                        <span className="text-sm text-muted-foreground">Scroll for more</span>
                      ) : (posts && posts.length > 0) ? (
                        <span className="text-sm text-muted-foreground">No more posts</span>
                      ) : null}
                    </div>
                  </>
                )}
              </div>

              {/* Fansly-style Post Detail - Right panel */}
              {selectedPost ? (
                <div className="flex-1 overflow-hidden">
                  <PostDetailView
                    post={selectedPost}
                    highlighted={selectedPost.is_pinned === 1}
                    onClose={() => setSelectedPost(null)}
                    onPostUpdated={(updates) => {
                      if (updates) {
                        setSelectedPost(prev => prev ? { ...prev, ...updates } : null)
                      }
                      queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
                    }}
                    onOpenLightbox={(attachmentIndex, _isVideo, startTime, wasPlaying) => {
                      setSocialLightboxPost(selectedPost)
                      setSocialLightboxIndex(attachmentIndex)
                      setSocialLightboxStartTime(startTime)
                      setSocialLightboxWasPlaying(wasPlaying ?? false)
                    }}
                    videoReturnState={videoReturnState}
                    onVideoReturnStateHandled={() => setVideoReturnState(null)}
                    onCopyToGallery={isFeatureEnabled('/private-gallery') && selectedPost.attachments?.some((a: PaidContentAttachment) => a.status === 'completed' && a.local_path) ? () => setCopyPost(selectedPost) : undefined}
                  />
                </div>
              ) : (
                <div className="flex-1 flex items-center justify-center text-muted-foreground">
                  <div className="text-center">
                    <Columns className="w-12 h-12 mx-auto mb-2 opacity-50" />
                    <p>Select a post to view details</p>
                  </div>
                </div>
              )}
            </div>
          </div>

          {/* Mobile - Continuous scroll of full posts (like Instagram) */}
          <div className="lg:hidden space-y-4">
            {(isLoading || isPending) && (!posts || posts.length === 0) ? (
              <div className="flex items-center justify-center h-64">
                <div className="animate-spin rounded-full h-8 w-8 border-2 border-primary border-t-transparent"></div>
              </div>
            ) : isError && (!posts || posts.length === 0) ? (
              <div className="flex flex-col items-center justify-center h-64 text-muted-foreground">
                <AlertTriangle className="w-12 h-12 mb-2 opacity-50 text-destructive" />
                <p>Failed to load feed</p>
                <button onClick={() => refetch()} className="mt-2 text-sm text-primary hover:underline">
                  Tap to retry
                </button>
              </div>
            ) : (!posts || posts.length === 0) ? (
              <div className="flex flex-col items-center justify-center h-64 text-muted-foreground">
                <ScrollText className="w-12 h-12 mb-2 opacity-50" />
                <p>No posts found</p>
              </div>
            ) : (
              <>
                {/* Pinned posts - collapsible (mobile) */}
                {pinnedPosts.length > 0 && (
                  <div className="bg-card rounded-xl border border-border overflow-hidden">
                    <button
                      onClick={() => setPinnedCollapsed(!pinnedCollapsed)}
                      className="w-full flex items-center gap-2 px-3 py-2 text-sm text-amber-500 hover:bg-accent/50"
                    >
                      <Pin className="w-3.5 h-3.5" />
                      <span>Pinned ({pinnedPosts.length})</span>
                      <ChevronDown className={`w-4 h-4 ml-auto transition-transform ${pinnedCollapsed ? '-rotate-90' : ''}`} />
                    </button>
                    {!pinnedCollapsed && pinnedPosts.map(post => (
                      <div
                        key={post.id}
                        className="border-t border-border cursor-pointer"
                        onClick={() => setMobileSelectedPost(post)}
                      >
                        <PostDetailView
                          post={post}
                          highlighted={true}
                          onPostUpdated={() => {
                            queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
                          }}
                          onOpenLightbox={(attachmentIndex, _isVideo, startTime, wasPlaying) => {
                            setSocialLightboxPost(post)
                            setSocialLightboxIndex(attachmentIndex)
                            setSocialLightboxStartTime(startTime)
                            setSocialLightboxWasPlaying(wasPlaying ?? false)
                            setMobileSelectedPost(null)
                          }}
                          videoReturnState={videoReturnState?.attachmentId && post.attachments?.some((a: PaidContentAttachment) => a.id === videoReturnState.attachmentId) ? videoReturnState : null}
                          onVideoReturnStateHandled={() => setVideoReturnState(null)}
                          compactMode={true}
                          onCopyToGallery={isFeatureEnabled('/private-gallery') && post.attachments?.some((a: PaidContentAttachment) => a.status === 'completed' && a.local_path) ? () => setCopyPost(post) : undefined}
                        />
                      </div>
                    ))}
                  </div>
                )}

                {/* Regular posts (mobile) */}
                {regularPosts.map((post) => (
                  <div
                    key={post.id}
                    className="bg-card rounded-xl border border-border overflow-hidden cursor-pointer"
                    onClick={() => setMobileSelectedPost(post)}
                  >
                    <PostDetailView
                      post={post}
                      onPostUpdated={() => {
                        queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
                      }}
                      onOpenLightbox={(attachmentIndex, _isVideo, startTime, wasPlaying) => {
                        setSocialLightboxPost(post)
                        setSocialLightboxIndex(attachmentIndex)
                        setSocialLightboxStartTime(startTime)
                        setSocialLightboxWasPlaying(wasPlaying ?? false)
                        setMobileSelectedPost(null)
                      }}
                      videoReturnState={videoReturnState?.attachmentId && post.attachments?.some((a: PaidContentAttachment) => a.id === videoReturnState.attachmentId) ? videoReturnState : null}
                      onVideoReturnStateHandled={() => setVideoReturnState(null)}
                      compactMode={true}
                      onCopyToGallery={isFeatureEnabled('/private-gallery') && post.attachments?.some((a: PaidContentAttachment) => a.status === 'completed' && a.local_path) ? () => setCopyPost(post) : undefined}
                    />
                  </div>
                ))}

                {/* Infinite scroll trigger - mobile only (always social view) */}
                <div className="p-4 flex items-center justify-center">
                  {isFetchingNextPage ? (
                    <div className="flex items-center gap-2 text-muted-foreground">
                      <Loader2 className="w-5 h-5 animate-spin" />
                      <span className="text-sm">Loading more...</span>
                    </div>
                  ) : hasNextPage ? (
                    <span className="text-sm text-muted-foreground">Scroll for more</span>
                  ) : (posts && posts.length > 0) ? (
                    <span className="text-sm text-muted-foreground">No more posts</span>
                  ) : null}
                </div>
              </>
            )}
          </div>

          {/* Mobile Full Post Modal (hidden when lightbox is open) */}
          {mobileSelectedPost && !socialLightboxPost && (
            <div className="fixed inset-0 z-50 bg-background overflow-y-auto" style={{ paddingTop: 'env(safe-area-inset-top, 0px)' }}>
              {/* Header with back button */}
              <div className="sticky top-0 z-10 bg-background/95 backdrop-blur border-b border-border px-4 py-3 flex items-center gap-3">
                <button
                  onClick={() => setMobileSelectedPost(null)}
                  className="p-2 -ml-2 hover:bg-secondary rounded-lg"
                >
                  <X className="w-5 h-5" />
                </button>
                <span className="font-medium">Post</span>
              </div>
              {/* Full post view */}
              <div className="p-4">
                <PostDetailView
                  post={mobileSelectedPost}
                  highlighted={mobileSelectedPost.is_pinned === 1}
                  onPostUpdated={() => {
                    queryClient.invalidateQueries({ queryKey: ['paid-content-feed'] })
                  }}
                  onOpenLightbox={(attachmentIndex, _isVideo, startTime, wasPlaying) => {
                    setSocialLightboxPost(mobileSelectedPost)
                    setSocialLightboxIndex(attachmentIndex)
                    setSocialLightboxStartTime(startTime)
                    setSocialLightboxWasPlaying(wasPlaying ?? false)
                    setMobileSelectedPost(null) // Close modal so lightbox shows on top
                  }}
                  videoReturnState={videoReturnState}
                  onVideoReturnStateHandled={() => setVideoReturnState(null)}
                  compactMode={false}
                  onCopyToGallery={isFeatureEnabled('/private-gallery') && mobileSelectedPost.attachments?.some((a: PaidContentAttachment) => a.status === 'completed' && a.local_path) ? () => setCopyPost(mobileSelectedPost) : undefined}
                />
              </div>
            </div>
          )}
        </>
      )}

      {/* Desktop: Split View / Mobile: Full-width post list (Feed view) */}
      {/* Only show when feed view is active */}
      {viewMode === 'feed' && (
      <div className="bg-card rounded-xl border border-border overflow-hidden">
          <div className="flex lg:h-[calc(100vh-280px)] lg:min-h-[500px]">
            {/* Post List - Full width on mobile, sidebar on desktop */}
            <div
              ref={viewMode === 'feed' ? scrollContainerRef : undefined}
              className={`w-full lg:w-1/3 lg:border-r lg:border-border overflow-y-auto ${
                selectedPost ? 'lg:block' : ''
              }`}
            >
              {(isLoading || isPending) && (!posts || posts.length === 0) ? (
                <div className="flex items-center justify-center h-64">
                  <div className="animate-spin rounded-full h-8 w-8 border-2 border-primary border-t-transparent"></div>
                </div>
              ) : isError && (!posts || posts.length === 0) ? (
                <div className="flex flex-col items-center justify-center h-64 text-muted-foreground">
                  <AlertTriangle className="w-12 h-12 mb-2 opacity-50 text-destructive" />
                  <p>Failed to load feed</p>
                  <button onClick={() => refetch()} className="mt-2 text-sm text-primary hover:underline">
                    Tap to retry
                  </button>
                </div>
              ) : (!posts || posts.length === 0) ? (
                <div className="flex flex-col items-center justify-center h-64 text-muted-foreground">
                  <ScrollText className="w-12 h-12 mb-2 opacity-50" />
                  <p>No posts found</p>
                  {hasActiveFilters && (
                    <button onClick={clearFilters} className="mt-2 text-sm text-primary hover:underline">
                      Clear filters
                    </button>
                  )}
                </div>
              ) : (
                <>
                  {/* Pinned posts - collapsible */}
                  {pinnedPosts.length > 0 && (
                    <div className="border-b border-border">
                      <button
                        onClick={() => setPinnedCollapsed(!pinnedCollapsed)}
                        className="w-full flex items-center gap-2 px-3 py-2 text-sm text-amber-500 hover:bg-accent/50"
                      >
                        <Pin className="w-3.5 h-3.5" />
                        <span>Pinned ({pinnedPosts.length})</span>
                        <ChevronDown className={`w-4 h-4 ml-auto transition-transform ${pinnedCollapsed ? '-rotate-90' : ''}`} />
                      </button>
                      {!pinnedCollapsed && pinnedPosts.map(post => (
                        <PostCard
                          key={post.id}
                          post={post}
                          isSelected={selectedPost?.id === post.id}
                          onClick={() => setSelectedPost(post)}
                          onOpenLightbox={(attachmentIndex) => {
                            setLightboxPost(post)
                            setLightboxIndex(attachmentIndex)
                          }}
                          onCopyToGallery={isFeatureEnabled('/private-gallery') ? setCopyPost : undefined}

                        />
                      ))}
                    </div>
                  )}

                  {/* Regular posts */}
                  {regularPosts.map((post) => (
                    <PostCard
                      key={post.id}
                      post={post}
                      isSelected={selectedPost?.id === post.id}
                      onClick={() => setSelectedPost(post)}
                      onOpenLightbox={(attachmentIndex) => {
                        setLightboxPost(post)
                        setLightboxIndex(attachmentIndex)
                      }}
                      onCopyToGallery={isFeatureEnabled('/private-gallery') ? setCopyPost : undefined}
                    />
                  ))}

                  {/* Infinite scroll trigger */}
                  <div className="p-4 flex items-center justify-center">
                    {isFetchingNextPage ? (
                      <div className="flex items-center gap-2 text-muted-foreground">
                        <Loader2 className="w-5 h-5 animate-spin" />
                        <span className="text-sm">Loading more...</span>
                      </div>
                    ) : hasNextPage ? (
                      <span className="text-sm text-muted-foreground">Scroll for more</span>
                    ) : (posts && posts.length > 0) ? (
                      <span className="text-sm text-muted-foreground">No more posts</span>
                    ) : null}
                  </div>
                </>
              )}
            </div>

            {/* Desktop Post Detail - hidden on mobile (uses modal above) */}
            {selectedPost && (
              <div className="hidden lg:block lg:w-2/3">
                <PostDetail
                  post={selectedPost}
                  onClose={() => setSelectedPost(null)}
                  onPostUpdated={(updates) => {
                    if (updates) {
                      setSelectedPost(prev => prev ? { ...prev, ...updates } : null)
                    }
                  }}
                />
              </div>
            )}
          </div>
        </div>
      )}

      {/* Copy to Private Gallery Modal (bulk - tag mode) */}
      <CopyToGalleryModal
        open={showCopyToGalleryModal}
        onClose={() => setShowCopyToGalleryModal(false)}
        sourcePaths={
          // Get all local_path from completed attachments of selected posts
          posts
            .filter((p: PaidContentPost) => selectedPostIds.has(p.id))
            .flatMap((p: PaidContentPost) => (p.attachments || [])
              .filter((a: PaidContentAttachment) => a.status === 'completed' && a.local_path)
              .map((a: PaidContentAttachment) => a.local_path!))
        }
        sourceType="paid_content"
        onSuccess={() => {
          setShowCopyToGalleryModal(false)
          setSelectedPostIds(new Set())
          setTagMode(false)
        }}
      />

      {/* Copy to Private Gallery Modal (single post) */}
      {copyPost && (
        <CopyToGalleryModal
          open={true}
          onClose={() => setCopyPost(null)}
          sourcePaths={
            (copyPost.attachments || [])
              .filter((a: PaidContentAttachment) => a.status === 'completed' && a.local_path)
              .map((a: PaidContentAttachment) => a.local_path!)
          }
          sourceNames={
            Object.fromEntries(
              (copyPost.attachments || [])
                .filter((a: PaidContentAttachment) => a.status === 'completed' && a.local_path)
                .map((a: PaidContentAttachment) => [a.local_path!, a.name || a.local_path!.split('/').pop() || a.local_path!])
            )
          }
          sourceType="paid_content"
          onSuccess={() => setCopyPost(null)}
        />
      )}
    </div>
  )
}
