import React, { useState, useEffect, useMemo, useRef } from 'react'
import tagsApi, { PhotoWithTagsItem, TagResponse } from '../api/tags'
import { useDeveloperMode } from '../context/DeveloperModeContext'

type ViewMode = 'list' | 'icons' | 'compact'

// Removed unused interfaces PendingTagChange and PendingTagRemoval

interface FolderGroup {
  folderPath: string
  folderName: string
  photos: PhotoWithTagsItem[]
  photoCount: number
}

// SessionStorage key for persisting folder states
const FOLDER_STATES_KEY = 'tags_folder_states'

// Helper function to load folder states from sessionStorage synchronously
const loadFolderStatesFromStorage = (): Record<string, boolean> => {
  try {
    const saved = sessionStorage.getItem(FOLDER_STATES_KEY)
    if (saved) {
      const states = JSON.parse(saved)
      if (states && typeof states === 'object') {
        return states
      }
    }
  } catch (error) {
    console.error('Error loading folder states from sessionStorage:', error)
  }
  return {}
}

export default function Tags() {
  const { isDeveloperMode: _isDeveloperMode } = useDeveloperMode()
  const [viewMode, setViewMode] = useState<ViewMode>('list')
  const [photos, setPhotos] = useState<PhotoWithTagsItem[]>([])
  const [tags, setTags] = useState<TagResponse[]>([])
  // Initialize folder states from sessionStorage synchronously
  const [folderStates, setFolderStates] = useState<Record<string, boolean>>(() => loadFolderStatesFromStorage())
  const [pendingTagChanges, setPendingTagChanges] = useState<Record<number, number[]>>({})
  const [pendingTagRemovals, setPendingTagRemovals] = useState<Record<number, number[]>>({})
  const [loading, setLoading] = useState(false)
  const [_saving, setSaving] = useState(false)
  const [showManageTags, setShowManageTags] = useState(false)
  const [showTagDialog, setShowTagDialog] = useState<number | null>(null)
  const [showBulkTagDialog, setShowBulkTagDialog] = useState<string | null>(null)
  const [selectedPhotoIds, setSelectedPhotoIds] = useState<Set<number>>(new Set())
  const [showTagSelectedDialog, setShowTagSelectedDialog] = useState(false)
  
  // Sorting state - default to date_taken (descending, matching the default sort behavior)
  const [sortColumn, setSortColumn] = useState<string | null>('date_taken')
  const [sortDir, setSortDir] = useState<'asc' | 'desc'>('desc')
  
  // Filter state
  const [showOnlyUnidentified, setShowOnlyUnidentified] = useState(false)
  
  // Refs to capture latest folder states for unmount save
  const folderStatesRef = useRef(folderStates)

  // Update ref whenever folder states change
  useEffect(() => {
    folderStatesRef.current = folderStates
  }, [folderStates])

  // Save folder states to sessionStorage whenever they change
  useEffect(() => {
    try {
      sessionStorage.setItem(FOLDER_STATES_KEY, JSON.stringify(folderStates))
    } catch (error) {
      console.error('Error saving folder states to sessionStorage:', error)
    }
  }, [folderStates])

  // Save folder states on unmount (when navigating away)
  useEffect(() => {
    return () => {
      try {
        sessionStorage.setItem(FOLDER_STATES_KEY, JSON.stringify(folderStatesRef.current))
      } catch (error) {
        console.error('Error saving folder states on unmount:', error)
      }
    }
  }, [])

  // Load photos and tags
  useEffect(() => {
    loadData()
  }, [])

  const loadData = async () => {
    setLoading(true)
    try {
      const [photosRes, tagsRes] = await Promise.all([
        tagsApi.getPhotosWithTags(),
        tagsApi.list(),
      ])
      // Debug: Check if unidentified_face_count is present
      if (photosRes.items.length > 0) {
        console.log('Sample photo data:', photosRes.items[0])
        console.log('unidentified_face_count:', photosRes.items[0].unidentified_face_count)
      }
      setPhotos(photosRes.items)
      setTags(tagsRes.items)
    } catch (error) {
      console.error('Failed to load data:', error)
      alert('Failed to load photos and tags')
    } finally {
      setLoading(false)
    }
  }

  // Group photos by folder
  // Handle sorting
  const handleSort = (column: string) => {
    if (sortColumn === column) {
      setSortDir(sortDir === 'asc' ? 'desc' : 'asc')
    } else {
      setSortColumn(column)
      setSortDir('asc')
    }
  }


  const folderGroups = useMemo(() => {
    // Filter photos if "Show only unIdentified photos" is checked
    const filteredPhotos = showOnlyUnidentified
      ? photos.filter(photo => {
          const unidentifiedCount = photo.unidentified_face_count || 0
          return unidentifiedCount > 0
        })
      : photos
    
    const groups: Record<string, PhotoWithTagsItem[]> = {}
    filteredPhotos.forEach(photo => {
      const folderPath = photo.path.substring(0, photo.path.lastIndexOf('/') || photo.path.length)
      if (!groups[folderPath]) {
        groups[folderPath] = []
      }
      groups[folderPath].push(photo)
    })

    const sortedFolders: FolderGroup[] = []
    Object.keys(groups).sort().forEach(folderPath => {
      const folderName = folderPath.substring(folderPath.lastIndexOf('/') + 1) || 'Root'
      let photosInFolder = groups[folderPath]
      
      // Apply sorting if a column is selected
      if (sortColumn) {
        photosInFolder = [...photosInFolder].sort((a, b) => {
          let aVal: any
          let bVal: any

          switch (sortColumn) {
            case 'id':
              aVal = a.id
              bVal = b.id
              break
            case 'filename':
              aVal = a.filename || ''
              bVal = b.filename || ''
              break
            case 'path':
              aVal = a.path || ''
              bVal = b.path || ''
              break
            case 'processed':
              aVal = a.processed ? 'Yes' : 'No'
              bVal = b.processed ? 'Yes' : 'No'
              break
            case 'media_type':
              aVal = (a.media_type || 'image').toLowerCase()
              bVal = (b.media_type || 'image').toLowerCase()
              break
            case 'date_taken':
              aVal = a.date_taken || '9999-12-31'
              bVal = b.date_taken || '9999-12-31'
              break
            case 'faces':
              aVal = a.face_count || 0
              bVal = b.face_count || 0
              break
            case 'identified': {
              // Sort by identified count (identified/total ratio)
              const aTotal = a.face_count || 0
              const aIdentified = aTotal - (a.unidentified_face_count || 0)
              const bTotal = b.face_count || 0
              const bIdentified = bTotal - (b.unidentified_face_count || 0)
              // Use ratio for sorting (identified/total), or just identified count if same ratio
              const aRatio = aTotal > 0 ? aIdentified / aTotal : 0
              const bRatio = bTotal > 0 ? bIdentified / bTotal : 0
              if (aRatio !== bRatio) {
                aVal = aRatio
                bVal = bRatio
              } else {
                aVal = aIdentified
                bVal = bIdentified
              }
              break
            }
            case 'tags': {
              // Get tags for comparison - use photo.tags directly
              const aTags = (a.tags || '').toLowerCase()
              const bTags = (b.tags || '').toLowerCase()
              aVal = aTags
              bVal = bTags
              break
            }
            default:
              return 0
          }

          // Handle string comparison
          if (typeof aVal === 'string' && typeof bVal === 'string') {
            aVal = aVal.toLowerCase()
            bVal = bVal.toLowerCase()
          }

          // Numeric comparison
          if (typeof aVal === 'number' && typeof bVal === 'number') {
            if (aVal < bVal) return sortDir === 'asc' ? -1 : 1
            if (aVal > bVal) return sortDir === 'asc' ? 1 : -1
            return 0
          }

          // String comparison
          if (aVal < bVal) return sortDir === 'asc' ? -1 : 1
          if (aVal > bVal) return sortDir === 'asc' ? 1 : -1
          return 0
        })
      } else {
        // Default sort by date_taken (descending)
        photosInFolder = [...photosInFolder].sort((a, b) => {
          const dateA = a.date_taken || ''
          const dateB = b.date_taken || ''
          return dateB.localeCompare(dateA)
        })
      }
      
      sortedFolders.push({
        folderPath,
        folderName,
        photos: photosInFolder,
        photoCount: photosInFolder.length,
      })
    })

    return sortedFolders
  }, [photos, sortColumn, sortDir, showOnlyUnidentified])

  // Count selected photos with unidentified faces (for Identify Faces button)
  const selectedPhotosWithFacesCount = useMemo(() => {
    return Array.from(selectedPhotoIds).filter(photoId => {
      const photo = photos.find(p => p.id === photoId)
      return photo && photo.processed && photo.unidentified_face_count > 0
    }).length
  }, [selectedPhotoIds, photos])

  // Check if all photos in a folder are selected
  const isFolderFullySelected = (folder: FolderGroup): boolean => {
    return folder.photos.length > 0 && folder.photos.every(photo => selectedPhotoIds.has(photo.id))
  }

  // Check if some (but not all) photos in a folder are selected
  const isFolderPartiallySelected = (folder: FolderGroup): boolean => {
    const selectedCount = folder.photos.filter(photo => selectedPhotoIds.has(photo.id)).length
    return selectedCount > 0 && selectedCount < folder.photos.length
  }

  // Toggle selection of all photos in a folder
  const toggleFolderSelection = (folder: FolderGroup) => {
    const newSet = new Set(selectedPhotoIds)
    const allSelected = isFolderFullySelected(folder)
    
    if (allSelected) {
      // Deselect all photos in folder
      folder.photos.forEach(photo => newSet.delete(photo.id))
    } else {
      // Select all photos in folder
      folder.photos.forEach(photo => newSet.add(photo.id))
    }
    
    setSelectedPhotoIds(newSet)
  }

  // Get tags for a photo (including pending changes)
  const getPhotoTags = (photoId: number): string => {
    const photo = photos.find(p => p.id === photoId)
    if (!photo) return ''

    const existingTags = photo.tags ? photo.tags.split(',').map(t => t.trim()).filter(t => t) : []
    const pendingTagIds = pendingTagChanges[photoId] || []
    const pendingRemovalIds = pendingTagRemovals[photoId] || []

    // Get tag names for pending additions
    const pendingTagNames = pendingTagIds
      .filter(tagId => !pendingRemovalIds.includes(tagId))
      .map(tagId => {
        const tag = tags.find(t => t.id === tagId)
        return tag ? tag.tag_name : `Unknown ${tagId}`
      })

    // Combine existing and pending tags, remove pending removals
    const allTags = [...existingTags, ...pendingTagNames]
    const removalTagIds = pendingRemovalIds
    const tagIdToName = new Map(tags.map(t => [t.id, t.tag_name]))
    const removalTagNames = removalTagIds.map(id => tagIdToName.get(id) || '').filter(Boolean)
    
    const finalTags = allTags.filter(tag => !removalTagNames.includes(tag))
    return finalTags.join(', ') || 'None'
  }

  // Toggle folder expand/collapse
  const toggleFolder = (folderPath: string) => {
    setFolderStates(prev => ({
      ...prev,
      [folderPath]: !prev[folderPath],
    }))
  }

  // Handle identifying faces from selected photos
  const handleIdentifyFaces = (photoIds: number[]) => {
    // Filter to only processed photos with faces
    const processedPhotos = photos.filter(p => 
      photoIds.includes(p.id) && p.processed && p.unidentified_face_count > 0
    )
    
    if (processedPhotos.length === 0) {
      alert('No processed photos with faces selected. Please select photos that have been processed and contain faces.')
      return
    }
    
    // Navigate to Identify page with photo IDs in new tab
    const photoIdsStr = processedPhotos.map(p => p.id).join(',')
    window.open(`/identify?photo_ids=${photoIdsStr}`, '_blank')
  }

  // Add tag to photo (with optional immediate save)
  const addTagToPhoto = async (photoId: number, tagName: string, saveImmediately: boolean = false) => {
    const tag = tags.find(t => t.tag_name.toLowerCase() === tagName.toLowerCase().trim())
    let tagId: number

    if (tag) {
      tagId = tag.id
    } else {
      // Create new tag
      try {
        const newTag = await tagsApi.create(tagName.trim())
        tagId = newTag.id
        setTags(prev => [...prev, newTag].sort((a, b) => a.tag_name.localeCompare(b.tag_name)))
      } catch (error) {
        console.error('Failed to create tag:', error)
        alert('Failed to create tag')
        return
      }
    }

    if (saveImmediately) {
      // Save immediately to database
      try {
        await tagsApi.addToPhotos({
          photo_ids: [photoId],
          tag_names: [tagName.trim()],
        })
        // Don't reload here - will reload when dialog closes
      } catch (error) {
        console.error('Failed to save tag:', error)
        alert('Failed to save tag')
        return
      }
    } else {
      // Add to pending changes
      setPendingTagChanges(prev => ({
        ...prev,
        [photoId]: [...(prev[photoId] || []), tagId],
      }))
    }
  }

  // Remove tag from photo (with optional immediate save)
  const removeTagFromPhoto = async (photoId: number, tagId: number, saveImmediately: boolean = false) => {
    const tag = tags.find(t => t.id === tagId)
    if (!tag) return

    if (saveImmediately) {
      // Save immediately to database
      try {
        await tagsApi.removeFromPhotos({
          photo_ids: [photoId],
          tag_names: [tag.tag_name],
        })
        // Don't reload here - will reload when dialog closes
      } catch (error) {
        console.error('Failed to remove tag:', error)
        alert('Failed to remove tag')
        return
      }
    } else {
      // If it's a pending addition, just remove it
      if (pendingTagChanges[photoId]?.includes(tagId)) {
        setPendingTagChanges(prev => ({
          ...prev,
          [photoId]: (prev[photoId] || []).filter(id => id !== tagId),
        }))
        return
      }

      // Otherwise, add to pending removals
      setPendingTagRemovals(prev => ({
        ...prev,
        [photoId]: [...(prev[photoId] || []), tagId],
      }))
    }
  }

  // Save pending changes (currently unused, kept for future use)
  // @ts-expect-error - Intentionally unused, kept for future use
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const _saveChanges = async () => {
    const pendingPhotoIds = new Set([
      ...Object.keys(pendingTagChanges).map(Number),
      ...Object.keys(pendingTagRemovals).map(Number),
    ])

    if (pendingPhotoIds.size === 0) {
      alert('No tag changes to save.')
      return
    }

    setSaving(true)
    try {
      // Process additions
      for (const photoId of Object.keys(pendingTagChanges).map(Number)) {
        const tagIds = pendingTagChanges[photoId] || []
        if (tagIds.length === 0) continue

        const tagNames = tagIds.map(id => {
          const tag = tags.find(t => t.id === id)
          return tag ? tag.tag_name : ''
        }).filter(Boolean)

        await tagsApi.addToPhotos({
          photo_ids: [photoId],
          tag_names: tagNames,
        })
      }

      // Process removals
      for (const photoId of Object.keys(pendingTagRemovals).map(Number)) {
        const tagIds = pendingTagRemovals[photoId] || []
        if (tagIds.length === 0) continue

        const tagNames = tagIds.map(id => {
          const tag = tags.find(t => t.id === id)
          return tag ? tag.tag_name : ''
        }).filter(Boolean)

        await tagsApi.removeFromPhotos({
          photo_ids: [photoId],
          tag_names: tagNames,
        })
      }

      // Clear pending changes
      setPendingTagChanges({})
      setPendingTagRemovals({})

      // Reload data
      await loadData()

      const additionsCount = Object.keys(pendingTagChanges).length
      const removalsCount = Object.keys(pendingTagRemovals).length
      let message = `Saved ${additionsCount} tag addition(s)`
      if (removalsCount > 0) {
        message += ` and ${removalsCount} tag removal(s)`
      }
      message += '.'
      alert(message)
    } catch (error) {
      console.error('Failed to save changes:', error)
      alert('Failed to save tag changes')
    } finally {
      setSaving(false)
    }
  }

  // Get pending changes count (currently unused, kept for future use)
  // @ts-expect-error - Intentionally unused, kept for future use
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const _pendingChangesCount = useMemo(() => {
    const additions = Object.values(pendingTagChanges).reduce((sum, ids) => sum + ids.length, 0)
    const removals = Object.values(pendingTagRemovals).reduce((sum, ids) => sum + ids.length, 0)
    return additions + removals
  }, [pendingTagChanges, pendingTagRemovals])

  if (loading) {
    return (
      <div className="flex items-center justify-center h-full">
        <p className="text-gray-600">Loading photos and tags...</p>
      </div>
    )
  }

  // Sort options for dropdown
  const sortOptions = [
    { value: 'date_taken', label: 'Default (Date Taken)' },
    { value: 'id', label: 'ID' },
    { value: 'filename', label: 'Filename' },
    { value: 'path', label: 'Path' },
    { value: 'processed', label: 'Processed' },
    { value: 'media_type', label: 'Media Type' },
    { value: 'faces', label: 'Faces' },
    { value: 'identified', label: 'Identified' },
    { value: 'tags', label: 'Tags' },
  ]

  const handleSortChange = (value: string) => {
    setSortColumn(value)
    setSortDir('asc')
  }

  const toggleSortDirection = () => {
    if (sortColumn) {
      setSortDir(sortDir === 'asc' ? 'desc' : 'asc')
    }
  }

  return (
    <div className="flex flex-col h-full">
      <div className="flex items-center justify-between mb-4">
        <div className="flex items-center gap-4">
          <div className="flex items-center gap-2">
            <input
              type="checkbox"
              id="showOnlyUnidentified"
              checked={showOnlyUnidentified}
              onChange={(e) => setShowOnlyUnidentified(e.target.checked)}
              className="w-4 h-4"
            />
            <label htmlFor="showOnlyUnidentified" className="text-sm font-medium text-gray-700 cursor-pointer">
              Show only unIdentified photos
            </label>
          </div>
          <div className="flex items-center gap-2">
            <label className="text-sm font-medium text-gray-700">Sort by:</label>
            <div className="flex items-center gap-2">
              <select
                value={sortColumn || 'date_taken'}
                onChange={(e) => handleSortChange(e.target.value)}
                className="px-3 py-1 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
              >
                {sortOptions.map(option => (
                  <option key={option.value} value={option.value}>
                    {option.label}
                  </option>
                ))}
              </select>
              {sortColumn && (
                <button
                  onClick={toggleSortDirection}
                  className="px-2 py-1 bg-gray-100 text-gray-700 rounded text-sm hover:bg-gray-200"
                  title={`Sort ${sortDir === 'asc' ? 'Ascending' : 'Descending'}`}
                >
                  {sortDir === 'asc' ? '↑' : '↓'}
                </button>
              )}
            </div>
          </div>
        </div>
        <div className="flex items-center gap-4">
          <div className="flex items-center gap-2">
            <label className="text-sm font-medium text-gray-700">View:</label>
            <div className="flex gap-2">
              <button
                onClick={() => setViewMode('list')}
                className={`px-3 py-1 rounded text-sm ${
                  viewMode === 'list'
                    ? 'bg-blue-600 text-white'
                    : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
                }`}
              >
                List
              </button>
              <button
                onClick={() => setViewMode('icons')}
                className={`px-3 py-1 rounded text-sm ${
                  viewMode === 'icons'
                    ? 'bg-blue-600 text-white'
                    : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
                }`}
              >
                Icons
              </button>
            </div>
          </div>
          <button
            onClick={() => setShowTagSelectedDialog(true)}
            disabled={selectedPhotoIds.size === 0}
            className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
          >
            Tag Selected Photos ({selectedPhotoIds.size})
          </button>
          <button
            onClick={() => handleIdentifyFaces(Array.from(selectedPhotoIds))}
            disabled={selectedPhotosWithFacesCount === 0}
            className="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
            title="Open Identify page in new tab with faces from selected photos"
          >
            👤 Identify Faces ({selectedPhotosWithFacesCount})
          </button>
          <button
            onClick={() => setSelectedPhotoIds(new Set())}
            disabled={selectedPhotoIds.size === 0}
            className="px-4 py-2 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 disabled:bg-gray-50 disabled:text-gray-400 disabled:cursor-not-allowed"
            title="Clear all selected photos"
          >
            Clear Selected
          </button>
          <button
            onClick={() => setShowManageTags(true)}
            className="px-4 py-2 bg-gray-100 text-gray-700 rounded hover:bg-gray-200"
          >
            Manage Tags
          </button>
        </div>
      </div>

      <div className="flex-1 overflow-auto bg-white rounded-lg shadow">
        {viewMode === 'list' && (
          <div className="p-4">
            <table className="w-full border-collapse">
              <thead>
                <tr className="border-b">
                  <th className="text-left p-2 font-semibold w-12">
                    <input
                      type="checkbox"
                      checked={selectedPhotoIds.size > 0 && selectedPhotoIds.size === photos.length}
                      onChange={(e) => {
                        if (e.target.checked) {
                          setSelectedPhotoIds(new Set(photos.map(p => p.id)))
                        } else {
                          setSelectedPhotoIds(new Set())
                        }
                      }}
                      className="w-4 h-4"
                    />
                  </th>
                  <th 
                    className="text-left p-2 font-semibold cursor-pointer hover:bg-gray-50"
                    onClick={() => handleSort('id')}
                  >
                    ID {sortColumn === 'id' && (sortDir === 'asc' ? '↑' : '↓')}
                  </th>
                  <th 
                    className="text-left p-2 font-semibold cursor-pointer hover:bg-gray-50"
                    onClick={() => handleSort('filename')}
                  >
                    Filename {sortColumn === 'filename' && (sortDir === 'asc' ? '↑' : '↓')}
                  </th>
                  <th 
                    className="text-left p-2 font-semibold cursor-pointer hover:bg-gray-50"
                    onClick={() => handleSort('path')}
                  >
                    Path {sortColumn === 'path' && (sortDir === 'asc' ? '↑' : '↓')}
                  </th>
                  <th 
                    className="text-left p-2 font-semibold cursor-pointer hover:bg-gray-50"
                    onClick={() => handleSort('processed')}
                  >
                    Processed {sortColumn === 'processed' && (sortDir === 'asc' ? '↑' : '↓')}
                  </th>
                  <th 
                    className="text-left p-2 font-semibold cursor-pointer hover:bg-gray-50"
                    onClick={() => handleSort('media_type')}
                  >
                    Media type {sortColumn === 'media_type' && (sortDir === 'asc' ? '↑' : '↓')}
                  </th>
                  <th 
                    className="text-left p-2 font-semibold cursor-pointer hover:bg-gray-50"
                    onClick={() => handleSort('date_taken')}
                  >
                    Date Taken {sortColumn === 'date_taken' && (sortDir === 'asc' ? '↑' : '↓')}
                  </th>
                  <th 
                    className="text-left p-2 font-semibold cursor-pointer hover:bg-gray-50"
                    onClick={() => handleSort('faces')}
                  >
                    Faces {sortColumn === 'faces' && (sortDir === 'asc' ? '↑' : '↓')}
                  </th>
                  <th 
                    className="text-left p-2 font-semibold cursor-pointer hover:bg-gray-50"
                    onClick={() => handleSort('identified')}
                  >
                    Identified {sortColumn === 'identified' && (sortDir === 'asc' ? '↑' : '↓')}
                  </th>
                  <th 
                    className="text-left p-2 font-semibold cursor-pointer hover:bg-gray-50"
                    onClick={() => handleSort('tags')}
                  >
                    Tags {sortColumn === 'tags' && (sortDir === 'asc' ? '↑' : '↓')}
                  </th>
                </tr>
              </thead>
              <tbody>
                {folderGroups.map(folder => (
                  <React.Fragment key={folder.folderPath}>
                    <tr className="bg-gray-50 border-b">
                      <td colSpan={10} className="p-2">
                        <div className="flex items-center gap-2">
                          <input
                            type="checkbox"
                            checked={isFolderFullySelected(folder)}
                            onChange={() => toggleFolderSelection(folder)}
                            className="w-4 h-4 cursor-pointer"
                            title="Select all photos in this folder"
                            ref={(el) => {
                              if (el) {
                                el.indeterminate = isFolderPartiallySelected(folder)
                              }
                            }}
                          />
                          <button
                            onClick={() => toggleFolder(folder.folderPath)}
                            className="px-2 py-1 text-sm"
                          >
                            {folderStates[folder.folderPath] === true ? '▼' : '▶'}
                          </button>
                          <span className="font-semibold">
                            📁 {folder.folderName} ({folder.photoCount} photos)
                          </span>
                        </div>
                      </td>
                    </tr>
                    {folderStates[folder.folderPath] === true && folder.photos.map(photo => (
                      <tr key={photo.id} className="border-b hover:bg-gray-50">
                        <td className="p-2">
                          <input
                            type="checkbox"
                            checked={selectedPhotoIds.has(photo.id)}
                            onChange={(e) => {
                              const newSet = new Set(selectedPhotoIds)
                              if (e.target.checked) {
                                newSet.add(photo.id)
                              } else {
                                newSet.delete(photo.id)
                              }
                              setSelectedPhotoIds(newSet)
                            }}
                            className="w-4 h-4"
                          />
                        </td>
                        <td className="p-2">{photo.id}</td>
                        <td className="p-2">
                          <a
                            href={`/api/v1/photos/${photo.id}/image`}
                            target="_blank"
                            rel="noopener noreferrer"
                            className="text-blue-600 hover:underline"
                          >
                            {photo.filename}
                          </a>
                        </td>
                        <td className="p-2 text-sm text-gray-600">{photo.path}</td>
                        <td className="p-2">{photo.processed ? 'Yes' : 'No'}</td>
                        <td className="p-2">{photo.media_type === 'video' ? 'Video' : 'Photo'}</td>
                        <td className="p-2">{photo.date_taken || 'Unknown'}</td>
                        <td className="p-2">
                          <div className="flex items-center justify-center">
                            {photo.processed && (photo.unidentified_face_count ?? 0) > 0 ? (
                              <button
                                onClick={() => handleIdentifyFaces([photo.id])}
                                className="px-1 py-0.5 bg-gray-100 text-gray-700 rounded text-sm hover:bg-gray-200"
                                title="Identify faces in this photo"
                              >
                                👤
                              </button>
                            ) : (
                              <span className="text-center">{photo.face_count || 0}</span>
                            )}
                          </div>
                        </td>
                        <td className="p-2">
                          {(() => {
                            const isProcessed = photo.processed
                            const totalFaces = isProcessed ? Math.max(0, photo.face_count || 0) : 0
                            const unidentifiedFaces = isProcessed
                              ? Math.max(0, photo.unidentified_face_count || 0)
                              : 0
                            const identifiedFaces = Math.max(0, totalFaces - unidentifiedFaces)
                            const allIdentified = totalFaces > 0 && identifiedFaces === totalFaces
                            const someIdentified = totalFaces > 0 && identifiedFaces < totalFaces
                            const peopleNames = photo.people_names || ''
                            
                            return (
                              <span
                                className={`px-2 py-1 rounded text-sm cursor-help ${
                                  allIdentified
                                    ? 'bg-green-100 text-green-800'
                                    : someIdentified
                                    ? 'bg-red-100 text-red-800'
                                    : 'bg-gray-100 text-gray-600'
                                }`}
                                title={
                                  peopleNames ||
                                  (!isProcessed
                                    ? 'Photo not processed'
                                    : totalFaces > 0
                                    ? 'No people identified'
                                    : 'No faces detected')
                                }
                              >
                                {identifiedFaces}/{totalFaces}
                              </span>
                            )
                          })()}
                        </td>
                        <td className="p-2">
                          <div className="flex items-center gap-2">
                            <span className="text-sm">{getPhotoTags(photo.id)}</span>
                            <button
                              onClick={() => setShowTagDialog(photo.id)}
                              className="px-2 py-1 bg-gray-100 text-gray-700 rounded text-sm hover:bg-gray-200"
                              title="Tag this photo"
                            >
                              🏷️
                            </button>
                          </div>
                        </td>
                      </tr>
                    ))}
                  </React.Fragment>
                ))}
              </tbody>
            </table>
          </div>
        )}

        {viewMode === 'icons' && (
          <div className="p-4">
            {folderGroups.map(folder => (
              <div key={folder.folderPath} className="mb-6">
                {/* Folder Header */}
                <div className="bg-gray-50 border-b p-3 mb-3 rounded-t">
                  <div className="flex items-center gap-2">
                    <input
                      type="checkbox"
                      checked={isFolderFullySelected(folder)}
                      onChange={() => toggleFolderSelection(folder)}
                      className="w-4 h-4 cursor-pointer"
                      title="Select all photos in this folder"
                      ref={(el) => {
                        if (el) {
                          el.indeterminate = isFolderPartiallySelected(folder)
                        }
                      }}
                    />
                    <button
                      onClick={() => toggleFolder(folder.folderPath)}
                      className="px-2 py-1 text-sm hover:bg-gray-200 rounded"
                    >
                      {folderStates[folder.folderPath] === true ? '▼' : '▶'}
                    </button>
                    <span className="font-semibold">
                      📁 {folder.folderName} ({folder.photoCount} photos)
                    </span>
                  </div>
                </div>

                {/* Photo Grid */}
                {folderStates[folder.folderPath] === true && (
                  <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4">
                    {folder.photos.map(photo => {
                      const photoUrl = `/api/v1/photos/${photo.id}/image`
                      const isSelected = selectedPhotoIds.has(photo.id)
                      
                      return (
                        <div
                          key={photo.id}
                          className="relative bg-white border border-gray-200 rounded-lg overflow-hidden hover:shadow-lg transition-shadow"
                        >
                          {/* Checkbox */}
                          <div className="absolute top-2 left-2 z-10">
                            <input
                              type="checkbox"
                              checked={isSelected}
                              onChange={(e) => {
                                const newSet = new Set(selectedPhotoIds)
                                if (e.target.checked) {
                                  newSet.add(photo.id)
                                } else {
                                  newSet.delete(photo.id)
                                }
                                setSelectedPhotoIds(newSet)
                              }}
                              className="w-5 h-5 cursor-pointer"
                              onClick={(e) => e.stopPropagation()}
                            />
                          </div>

                          {/* Action Buttons */}
                          <div className="absolute top-2 right-2 z-10 flex gap-1">
                            {photo.processed && (photo.unidentified_face_count ?? 0) > 0 && (
                              <button
                                onClick={(e) => {
                                  e.stopPropagation()
                                  handleIdentifyFaces([photo.id])
                                }}
                                className="px-2 py-1 bg-gray-100 text-gray-700 rounded text-sm hover:bg-gray-200 shadow"
                                title="Identify faces in this photo"
                              >
                                👤
                              </button>
                            )}
                            <button
                              onClick={(e) => {
                                e.stopPropagation()
                                setShowTagDialog(photo.id)
                              }}
                              className="px-2 py-1 bg-gray-100 text-gray-700 rounded text-sm hover:bg-gray-200 shadow"
                              title="Tag this photo"
                            >
                              🏷️
                            </button>
                          </div>

                          {/* Thumbnail */}
                          <div className="w-full aspect-square bg-gray-100 overflow-hidden">
                            <img
                              src={photoUrl}
                              alt={photo.filename}
                              className="w-full h-full object-cover cursor-pointer"
                              onClick={() => window.open(photoUrl, '_blank')}
                              onError={(e) => {
                                const target = e.target as HTMLImageElement
                                target.src = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="150" height="150"%3E%3Crect fill="%23ddd" width="150" height="150"/%3E%3Ctext x="50%25" y="50%25" text-anchor="middle" dy=".3em" fill="%23999"%3ENo Image%3C/text%3E%3C/svg%3E'
                              }}
                            />
                          </div>

                          {/* Metadata Overlay */}
                          <div className="p-2 bg-white">
                            <div className="text-xs space-y-1">
                              <div className="font-semibold text-gray-900 truncate" title={`ID: ${photo.id}`}>
                                ID: {photo.id}
                              </div>
                              <div className="text-gray-700 truncate" title={photo.filename}>
                                {photo.filename}
                              </div>
                              <div className="flex items-center gap-2 text-gray-600">
                                <span className={photo.processed ? 'text-green-600' : 'text-orange-600'}>
                                  {photo.processed ? '✓ Processed' : '○ Unprocessed'}
                                </span>
                              </div>
                              {photo.date_taken && (
                                <div className="text-gray-600 truncate" title={photo.date_taken}>
                                  📅 {photo.date_taken}
                                </div>
                              )}
                              <div className="text-gray-600">
                                {(() => {
                                  const totalFaces = photo.face_count || 0
                                  const identifiedFaces = totalFaces - (photo.unidentified_face_count || 0)
                                  const allIdentified = totalFaces > 0 && identifiedFaces === totalFaces
                                  const someIdentified = totalFaces > 0 && identifiedFaces < totalFaces
                                  const peopleNames = photo.people_names || ''
                                  
                                  return (
                                    <span
                                      className={`px-2 py-1 rounded text-sm cursor-help inline-block ${
                                        allIdentified
                                          ? 'bg-green-100 text-green-800'
                                          : someIdentified
                                          ? 'bg-red-100 text-red-800'
                                          : 'bg-gray-100 text-gray-600'
                                      }`}
                                      title={peopleNames || (totalFaces > 0 ? 'No people identified' : 'No faces detected')}
                                    >
                                      👤 {totalFaces > 0 ? (
                                        <span>{identifiedFaces}/{totalFaces}</span>
                                      ) : (
                                        <span>—</span>
                                      )}
                                    </span>
                                  )
                                })()}
                              </div>
                              <div className="text-gray-600 truncate" title={getPhotoTags(photo.id)}>
                                🏷️ {getPhotoTags(photo.id)}
                              </div>
                            </div>
                          </div>
                        </div>
                      )
                    })}
                  </div>
                )}
              </div>
            ))}
          </div>
        )}

      </div>

      {/* Manage Tags Dialog */}
      {showManageTags && (
        <ManageTagsDialog
          tags={tags}
          onClose={async () => {
            setShowManageTags(false)
            // Refresh photos when exiting the dialog
            await loadData()
          }}
          onTagsChange={setTags}
        />
      )}

      {/* Tag Dialog for Individual Photo */}
      {showTagDialog !== null && (
        <PhotoTagDialog
          photoId={showTagDialog}
          photo={photos.find(p => p.id === showTagDialog)}
          tags={tags}
          pendingTagChanges={pendingTagChanges[showTagDialog] || []}
          pendingTagRemovals={pendingTagRemovals[showTagDialog] || []}
          onClose={async () => {
            setShowTagDialog(null)
            // Reload data when dialog is closed
            await loadData()
          }}
          onAddTag={(tagName) => addTagToPhoto(showTagDialog, tagName, true)}
          onRemoveTag={(tagId) => removeTagFromPhoto(showTagDialog, tagId, true)}
          getPhotoTags={async (photoId) => {
            return await tagsApi.getPhotoTags(photoId)
          }}
        />
      )}

      {/* Bulk Tag Dialog for Folder */}
      {showBulkTagDialog !== null && (
        <BulkTagDialog
          folderPath={showBulkTagDialog}
          folder={folderGroups.find(f => f.folderPath === showBulkTagDialog)}
          tags={tags}
          pendingTagChanges={pendingTagChanges}
          pendingTagRemovals={pendingTagRemovals}
          onClose={async () => {
            setShowBulkTagDialog(null)
            // Reload data when dialog is closed
            await loadData()
          }}
          onAddTag={async (tagName) => {
            const folder = folderGroups.find(f => f.folderPath === showBulkTagDialog)
            if (!folder || folder.photos.length === 0) return

            // Check if tag exists, create if not
            let tag = tags.find(t => t.tag_name.toLowerCase() === tagName.toLowerCase().trim())
            if (!tag) {
              try {
                tag = await tagsApi.create(tagName.trim())
                setTags(prev => [...prev, tag!].sort((a, b) => a.tag_name.localeCompare(b.tag_name)))
              } catch (error) {
                console.error('Failed to create tag:', error)
                alert('Failed to create tag')
                return
              }
            }

            // Collect all photo IDs
            const photoIds = folder.photos.map(photo => photo.id)

            // Make single batch API call for all photos
            try {
              await tagsApi.addToPhotos({
                photo_ids: photoIds,
                tag_names: [tagName.trim()],
              })
            } catch (error) {
              console.error('Failed to save tags:', error)
              alert('Failed to save tags')
            }
          }}
          onRemoveTag={async (tagId) => {
            const folder = folderGroups.find(f => f.folderPath === showBulkTagDialog)
            if (!folder || folder.photos.length === 0) return

            // Find tag name
            const tag = tags.find(t => t.id === tagId)
            if (!tag) return

            // Collect all photo IDs
            const photoIds = folder.photos.map(photo => photo.id)

            // Make single batch API call for all photos
            try {
              await tagsApi.removeFromPhotos({
                photo_ids: photoIds,
                tag_names: [tag.tag_name],
              })
            } catch (error) {
              console.error('Failed to remove tags:', error)
              alert('Failed to remove tags')
            }
          }}
          getPhotoTags={async (photoId) => {
            return await tagsApi.getPhotoTags(photoId)
          }}
        />
      )}

      {/* Tag Selected Photos Dialog */}
      {showTagSelectedDialog && (
        <TagSelectedPhotosDialog
          selectedPhotoIds={Array.from(selectedPhotoIds)}
          photos={photos.filter(p => selectedPhotoIds.has(p.id))}
          tags={tags}
          onClose={async () => {
            setShowTagSelectedDialog(false)
            setSelectedPhotoIds(new Set())
            // Reload data when dialog is closed
            await loadData()
          }}
        />
      )}
    </div>
  )
}

// Confirmation Dialog Component
function ConfirmDialog({
  message,
  onConfirm,
  onCancel,
}: {
  message: string
  onConfirm: () => void
  onCancel: () => void
}) {
  return (
    <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[60]">
      <div className="bg-white rounded-lg shadow-xl w-full max-w-md p-6">
        <h3 className="text-lg font-bold mb-4">Confirm Delete</h3>
        <p className="text-gray-700 mb-6">{message}</p>
        <div className="flex justify-end gap-3">
          <button
            onClick={onCancel}
            className="px-4 py-2 bg-gray-100 text-gray-700 rounded hover:bg-gray-200"
          >
            Cancel
          </button>
          <button
            onClick={onConfirm}
            className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
          >
            Delete
          </button>
        </div>
      </div>
    </div>
  )
}

// Manage Tags Dialog Component
function ManageTagsDialog({
  tags,
  onClose,
  onTagsChange,
}: {
  tags: TagResponse[]
  onClose: () => void
  onTagsChange: (tags: TagResponse[]) => void
}) {
  const [newTagName, setNewTagName] = useState('')
  const [selectedTagIds, setSelectedTagIds] = useState<Set<number>>(new Set())
  const [editingTagId, setEditingTagId] = useState<number | null>(null)
  const [editTagName, setEditTagName] = useState('')
  const [showConfirmDialog, setShowConfirmDialog] = useState(false)
  const [tagError, setTagError] = useState<string>('')

  // Check if tag name already exists (case-insensitive)
  const isTagDuplicate = (tagName: string): boolean => {
    if (!tagName.trim()) return false
    const trimmedName = tagName.trim().toLowerCase()
    return tags.some(tag => tag.tag_name.toLowerCase() === trimmedName)
  }

  const handleCreateTag = async () => {
    if (!newTagName.trim()) return

    // Check for duplicate (case-insensitive)
    if (isTagDuplicate(newTagName)) {
      setTagError('A tag with this name already exists')
      return
    }

    setTagError('')
    try {
      const newTag = await tagsApi.create(newTagName.trim())
      onTagsChange([...tags, newTag].sort((a, b) => a.tag_name.localeCompare(b.tag_name)))
      setNewTagName('')
      setTagError('')
    } catch (error) {
      console.error('Failed to create tag:', error)
      alert('Failed to create tag')
    }
  }

  const handleEditTag = async (tagId: number) => {
    if (!editTagName.trim()) return

    try {
      const updatedTag = await tagsApi.update(tagId, editTagName.trim())
      onTagsChange(tags.map(t => (t.id === tagId ? updatedTag : t)).sort((a, b) => a.tag_name.localeCompare(b.tag_name)))
      setEditingTagId(null)
      setEditTagName('')
    } catch (error) {
      console.error('Failed to update tag:', error)
      alert('Failed to update tag')
    }
  }

  const handleDeleteTags = () => {
    if (selectedTagIds.size === 0) return
    setShowConfirmDialog(true)
  }

  const confirmDeleteTags = async () => {
    setShowConfirmDialog(false)
    
    try {
      await tagsApi.delete(Array.from(selectedTagIds))
      onTagsChange(tags.filter(t => !selectedTagIds.has(t.id)))
      setSelectedTagIds(new Set())
    } catch (error) {
      console.error('Failed to delete tags:', error)
      alert('Failed to delete tags')
    }
  }

  return (
    <>
      {showConfirmDialog && (
        <ConfirmDialog
          message={`Delete ${selectedTagIds.size} selected tag(s)? This will unlink them from photos.`}
          onConfirm={confirmDeleteTags}
          onCancel={() => setShowConfirmDialog(false)}
        />
      )}
      <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
        <div className="bg-white rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] flex flex-col">
        <div className="p-4 border-b">
          <h2 className="text-xl font-bold">Manage Tags</h2>
        </div>
        <div className="p-4 flex-1 overflow-auto">
          <div className="mb-4">
            <div className="flex gap-2">
              <div className="flex-1">
                <input
                  type="text"
                  value={newTagName}
                  onChange={(e) => {
                    setNewTagName(e.target.value)
                    // Check for duplicate as user types
                    if (e.target.value.trim() && isTagDuplicate(e.target.value)) {
                      setTagError('A tag with this name already exists')
                    } else {
                      setTagError('')
                    }
                  }}
                  onKeyPress={(e) => e.key === 'Enter' && handleCreateTag()}
                  placeholder="Enter new tag name"
                  className={`w-full px-3 py-2 border rounded ${
                    tagError ? 'border-red-500' : 'border-gray-300'
                  }`}
                />
                {tagError && (
                  <p className="mt-1 text-sm text-red-600">{tagError}</p>
                )}
              </div>
              <button
                onClick={handleCreateTag}
                disabled={!!tagError || !newTagName.trim()}
                className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
              >
                Add tag
              </button>
            </div>
          </div>

          <div className="border-b mb-2 pb-2 flex">
            <span className="w-12 font-semibold">Delete</span>
            <span className="flex-1 font-semibold">Tag name</span>
            <span className="w-20 font-semibold">Edit</span>
          </div>

          <div className="space-y-2">
            {tags.map(tag => (
              <div key={tag.id} className="flex items-center gap-2 py-1">
                <input
                  type="checkbox"
                  checked={selectedTagIds.has(tag.id)}
                  onChange={(e) => {
                    const newSet = new Set(selectedTagIds)
                    if (e.target.checked) {
                      newSet.add(tag.id)
                    } else {
                      newSet.delete(tag.id)
                    }
                    setSelectedTagIds(newSet)
                  }}
                  className="w-4 h-4"
                />
                {editingTagId === tag.id ? (
                  <div className="flex-1 flex gap-2">
                    <input
                      type="text"
                      value={editTagName}
                      onChange={(e) => setEditTagName(e.target.value)}
                      onKeyPress={(e) => e.key === 'Enter' && handleEditTag(tag.id)}
                      className="flex-1 px-2 py-1 border border-gray-300 rounded text-sm"
                    />
                    <button
                      onClick={() => handleEditTag(tag.id)}
                      className="px-2 py-1 bg-blue-600 text-white rounded text-sm hover:bg-blue-700"
                    >
                      Save
                    </button>
                    <button
                      onClick={() => {
                        setEditingTagId(null)
                        setEditTagName('')
                      }}
                      className="px-2 py-1 bg-gray-100 text-gray-700 rounded text-sm hover:bg-gray-200"
                    >
                      Cancel
                    </button>
                  </div>
                ) : (
                  <>
                    <span className="flex-1">{tag.tag_name}</span>
                    <button
                      onClick={() => {
                        setEditingTagId(tag.id)
                        setEditTagName(tag.tag_name)
                      }}
                      className="px-2 py-1 bg-gray-100 text-gray-700 rounded text-sm hover:bg-gray-200"
                    >
                      Edit
                    </button>
                  </>
                )}
              </div>
            ))}
          </div>
        </div>
        <div className="p-4 border-t flex justify-between">
          <button
            onClick={handleDeleteTags}
            disabled={selectedTagIds.size === 0}
            className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
          >
            Delete selected tags
          </button>
          <button
            onClick={onClose}
            className="px-4 py-2 bg-gray-100 text-gray-700 rounded hover:bg-gray-200"
          >
            Close
          </button>
        </div>
      </div>
    </div>
    </>
  )
}

// Photo Tag Dialog Component
function PhotoTagDialog({
  photoId,
  photo,
  tags,
  pendingTagChanges,
  pendingTagRemovals,
  onClose,
  onAddTag,
  onRemoveTag,
  getPhotoTags,
}: {
  photoId: number
  photo: PhotoWithTagsItem | undefined
  tags: TagResponse[]
  pendingTagChanges: number[]
  pendingTagRemovals: number[]
  onClose: () => void
  onAddTag: (tagName: string) => Promise<void>
  onRemoveTag: (tagId: number) => Promise<void>
  getPhotoTags: (photoId: number) => Promise<any>
}) {
  const [selectedTagName, setSelectedTagName] = useState('')
  const [photoTags, setPhotoTags] = useState<any[]>([])
  const [selectedTagIds, setSelectedTagIds] = useState<Set<number>>(new Set())
  const [showConfirmDialog, setShowConfirmDialog] = useState(false)

  useEffect(() => {
    loadPhotoTags()
  }, [photoId])

  const loadPhotoTags = async () => {
    try {
      const response = await getPhotoTags(photoId)
      setPhotoTags(response.tags || [])
    } catch (error) {
      console.error('Failed to load photo tags:', error)
    }
  }

  const handleAddTag = async () => {
    if (!selectedTagName.trim()) return
    await onAddTag(selectedTagName.trim())
    setSelectedTagName('')
    await loadPhotoTags()
  }

  const handleRemoveTags = () => {
    if (selectedTagIds.size === 0) return
    setShowConfirmDialog(true)
  }

  const confirmRemoveTags = async () => {
    setShowConfirmDialog(false)
    if (selectedTagIds.size === 0) return
    for (const tagId of selectedTagIds) {
      await onRemoveTag(tagId)
    }
    setSelectedTagIds(new Set())
    await loadPhotoTags()
  }

  // Combine saved and pending tags
  const allTags = useMemo(() => {
    const savedTags = photoTags.filter(t => !pendingTagRemovals.includes(t.tag_id))
    const pendingTagObjs = pendingTagChanges
      .filter(id => !pendingTagRemovals.includes(id))
      .map(id => {
        const tag = tags.find(t => t.id === id)
        return tag ? { tag_id: id, tag_name: tag.tag_name } : null
      })
      .filter(Boolean) as any[]
    return [...savedTags, ...pendingTagObjs]
  }, [photoTags, pendingTagChanges, pendingTagRemovals, tags])

  // Get selected tag names for confirmation message
  const selectedTagNames = useMemo(() => {
    return Array.from(selectedTagIds)
      .map(id => {
        const tag = allTags.find(t => t.tag_id === id)
        return tag ? tag.tag_name : ''
      })
      .filter(Boolean)
      .join(', ')
  }, [selectedTagIds, allTags])

  return (
    <>
      {showConfirmDialog && (
        <ConfirmDialog
          message={`Remove ${selectedTagIds.size} selected tag(s) from this photo? (${selectedTagNames})`}
          onConfirm={confirmRemoveTags}
          onCancel={() => setShowConfirmDialog(false)}
        />
      )}
      <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
      <div className="bg-white rounded-lg shadow-xl w-full max-w-lg max-h-[80vh] flex flex-col">
        <div className="p-4 border-b">
          <h2 className="text-xl font-bold">Manage Photo Tags</h2>
          {photo && <p className="text-sm text-gray-600">{photo.filename}</p>}
        </div>
        <div className="p-4 flex-1 overflow-auto">
          <div className="mb-4 flex gap-2">
            <select
              value={selectedTagName}
              onChange={(e) => setSelectedTagName(e.target.value)}
              className="flex-1 px-3 py-2 border border-gray-300 rounded"
            >
              <option value="">Select tag...</option>
              {tags.map(tag => (
                <option key={tag.id} value={tag.tag_name}>
                  {tag.tag_name}
                </option>
              ))}
            </select>
            <button
              onClick={handleAddTag}
              className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
            >
              Add
            </button>
          </div>

          <div className="space-y-2">
            {allTags.length === 0 ? (
              <p className="text-gray-500 text-sm">No tags linked to this photo</p>
            ) : (
              allTags.map(tag => {
                const isPending = pendingTagChanges.includes(tag.tag_id)

                return (
                  <div key={tag.tag_id} className="flex items-center gap-2 py-1">
                    <input
                      type="checkbox"
                      checked={selectedTagIds.has(tag.tag_id)}
                      onChange={(e) => {
                        const newSet = new Set(selectedTagIds)
                        if (e.target.checked) {
                          newSet.add(tag.tag_id)
                        } else {
                          newSet.delete(tag.tag_id)
                        }
                        setSelectedTagIds(newSet)
                      }}
                      className="w-4 h-4"
                    />
                    <span className="flex-1">
                      {tag.tag_name}
                      {isPending && ' (pending)'}
                    </span>
                  </div>
                )
              })
            )}
          </div>
        </div>
        <div className="p-4 border-t flex justify-between">
          <button
            onClick={handleRemoveTags}
            disabled={selectedTagIds.size === 0}
            className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
          >
            Remove selected tags
          </button>
          <button
            onClick={onClose}
            className="px-4 py-2 bg-gray-100 text-gray-700 rounded hover:bg-gray-200"
          >
            Close
          </button>
        </div>
      </div>
    </div>
    </>
  )
}

// Bulk Tag Dialog Component
function BulkTagDialog({
  folderPath: _folderPath,
  folder,
  tags,
  pendingTagChanges,
  pendingTagRemovals,
  onClose,
  onAddTag,
  onRemoveTag,
  getPhotoTags,
}: {
  folderPath: string
  folder: FolderGroup | undefined
  tags: TagResponse[]
  pendingTagChanges: Record<number, number[]>
  pendingTagRemovals: Record<number, number[]>
  onClose: () => void
  onAddTag: (tagName: string) => Promise<void>
  onRemoveTag: (tagId: number) => Promise<void>
  getPhotoTags: (photoId: number) => Promise<any>
}) {
  const [selectedTagName, setSelectedTagName] = useState('')
  const [folderTags, setFolderTags] = useState<any[]>([])
  const [selectedTagIds, setSelectedTagIds] = useState<Set<number>>(new Set())
  const [showConfirmDialog, setShowConfirmDialog] = useState(false)

  useEffect(() => {
    loadFolderTags()
  }, [folder])

  const loadFolderTags = async () => {
    if (!folder || folder.photos.length === 0) {
      setFolderTags([])
      return
    }

    try {
      // Get tags from the first photo in the folder
      const response = await getPhotoTags(folder.photos[0].id)
      setFolderTags(response.tags || [])
    } catch (error) {
      console.error('Failed to load folder tags:', error)
      setFolderTags([])
    }
  }

  const handleAddTag = async () => {
    if (!selectedTagName.trim()) return
    const tagName = selectedTagName.trim()
    await onAddTag(tagName)
    setSelectedTagName('')
    
    // Update local state immediately to reflect the change
    const tag = tags.find(t => t.tag_name.toLowerCase() === tagName.toLowerCase())
    if (tag) {
      const newTag = { tag_id: tag.id, tag_name: tag.tag_name }
      setFolderTags(prev => {
        // Check if tag already exists
        if (prev.some(t => t.tag_id === tag.id)) {
          return prev
        }
        return [...prev, newTag]
      })
    }
  }

  const handleRemoveTags = () => {
    if (selectedTagIds.size === 0) return
    setShowConfirmDialog(true)
  }

  const confirmRemoveTags = async () => {
    setShowConfirmDialog(false)
    if (selectedTagIds.size === 0) return
    const tagIdsToRemove = Array.from(selectedTagIds)
    for (const tagId of tagIdsToRemove) {
      await onRemoveTag(tagId)
    }
    setSelectedTagIds(new Set())
    
    // Update local state immediately to reflect the removal
    setFolderTags(prev => prev.filter(t => !tagIdsToRemove.includes(t.tag_id)))
  }

  // Combine saved and pending tags
  const allTags = useMemo(() => {
    if (!folder || folder.photos.length === 0) return []
    
    // Get pending changes and removals for the first photo (they should be the same for all)
    const firstPhotoId = folder.photos[0].id
    const pendingTagIds = pendingTagChanges[firstPhotoId] || []
    const pendingRemovalIds = pendingTagRemovals[firstPhotoId] || []

    // Filter out pending removals from saved tags
    const savedTags = folderTags.filter(t => !pendingRemovalIds.includes(t.tag_id))
    
    // Add pending tags
    const pendingTagObjs = pendingTagIds
      .filter(id => !pendingRemovalIds.includes(id))
      .map(id => {
        const tag = tags.find(t => t.id === id)
        return tag ? { tag_id: id, tag_name: tag.tag_name } : null
      })
      .filter(Boolean) as any[]
    
    return [...savedTags, ...pendingTagObjs]
  }, [folderTags, pendingTagChanges, pendingTagRemovals, tags, folder])

  // Get selected tag names for confirmation message
  const selectedTagNames = useMemo(() => {
    return Array.from(selectedTagIds)
      .map(id => {
        const tag = allTags.find(t => t.tag_id === id)
        return tag ? tag.tag_name : ''
      })
      .filter(Boolean)
      .join(', ')
  }, [selectedTagIds, allTags])

  return (
    <>
      {showConfirmDialog && (
        <ConfirmDialog
          message={`Remove ${selectedTagIds.size} selected tag(s) from all photos in this folder? (${selectedTagNames})`}
          onConfirm={confirmRemoveTags}
          onCancel={() => setShowConfirmDialog(false)}
        />
      )}
      <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
      <div className="bg-white rounded-lg shadow-xl w-full max-w-lg max-h-[80vh] flex flex-col">
        <div className="p-4 border-b">
          <h2 className="text-xl font-bold">Link Tags to Folder</h2>
          {folder && (
            <p className="text-sm text-gray-600">
              Folder: {folder.folderName} ({folder.photoCount} photos)
            </p>
          )}
        </div>
        <div className="p-4 flex-1 overflow-auto">
          <div className="mb-4 flex gap-2">
            <select
              value={selectedTagName}
              onChange={(e) => setSelectedTagName(e.target.value)}
              className="flex-1 px-3 py-2 border border-gray-300 rounded"
            >
              <option value="">Select tag...</option>
              {tags.map(tag => (
                <option key={tag.id} value={tag.tag_name}>
                  {tag.tag_name}
                </option>
              ))}
            </select>
            <button
              onClick={handleAddTag}
              disabled={!selectedTagName.trim()}
              className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
            >
              Add
            </button>
          </div>

          <div className="space-y-2">
            {allTags.length === 0 ? (
              <p className="text-gray-500 text-sm">No tags linked to this folder</p>
            ) : (
              allTags.map(tag => {
                if (!folder || folder.photos.length === 0) return null
                const firstPhotoId = folder.photos[0].id
                const isPending = (pendingTagChanges[firstPhotoId] || []).includes(tag.tag_id)

                return (
                  <div key={tag.tag_id} className="flex items-center gap-2 py-1">
                    <input
                      type="checkbox"
                      checked={selectedTagIds.has(tag.tag_id)}
                      onChange={(e) => {
                        const newSet = new Set(selectedTagIds)
                        if (e.target.checked) {
                          newSet.add(tag.tag_id)
                        } else {
                          newSet.delete(tag.tag_id)
                        }
                        setSelectedTagIds(newSet)
                      }}
                      className="w-4 h-4"
                    />
                    <span className="flex-1">
                      {tag.tag_name}
                      {isPending && ' (pending)'}
                    </span>
                  </div>
                )
              })
            )}
          </div>
        </div>
        <div className="p-4 border-t flex justify-between">
          <button
            onClick={handleRemoveTags}
            disabled={selectedTagIds.size === 0}
            className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
          >
            Remove selected tags
          </button>
          <button
            onClick={onClose}
            className="px-4 py-2 bg-gray-100 text-gray-700 rounded hover:bg-gray-200"
          >
            Close
          </button>
        </div>
      </div>
    </div>
    </>
  )
}

// Tag Selected Photos Dialog Component
function TagSelectedPhotosDialog({
  selectedPhotoIds,
  photos,
  tags,
  onClose,
}: {
  selectedPhotoIds: number[]
  photos: PhotoWithTagsItem[]
  tags: TagResponse[]
  onClose: () => void
}) {
  const [selectedTagName, setSelectedTagName] = useState('')
  const [selectedTagIds, setSelectedTagIds] = useState<Set<number>>(new Set())
  const [showConfirmDialog, setShowConfirmDialog] = useState(false)
  const [photoTagsData, setPhotoTagsData] = useState<Record<number, any[]>>({})

  // Load tag linkage information for all selected photos
  useEffect(() => {
    const loadPhotoTags = async () => {
      const tagsData: Record<number, any[]> = {}
      for (const photoId of selectedPhotoIds) {
        try {
          const response = await tagsApi.getPhotoTags(photoId)
          tagsData[photoId] = response.tags || []
        } catch (error) {
          console.error(`Failed to load tags for photo ${photoId}:`, error)
          tagsData[photoId] = []
        }
      }
      setPhotoTagsData(tagsData)
    }

    if (selectedPhotoIds.length > 0) {
      loadPhotoTags()
    }
  }, [selectedPhotoIds])

  const handleAddTag = async () => {
    if (!selectedTagName.trim() || selectedPhotoIds.length === 0) return

    // Check if tag exists, create if not
    let tag = tags.find(t => t.tag_name.toLowerCase() === selectedTagName.toLowerCase().trim())
    if (!tag) {
      try {
        tag = await tagsApi.create(selectedTagName.trim())
        // Note: We don't update the tags list here since it's passed from parent
      } catch (error) {
        console.error('Failed to create tag:', error)
        alert('Failed to create tag')
        return
      }
    }

    // Make single batch API call for all selected photos
    try {
      await tagsApi.addToPhotos({
        photo_ids: selectedPhotoIds,
        tag_names: [selectedTagName.trim()],
      })
      setSelectedTagName('')
      
      // Reload photo tags data to update the common tags list
      const tagsData: Record<number, any[]> = {}
      for (const photoId of selectedPhotoIds) {
        try {
          const response = await tagsApi.getPhotoTags(photoId)
          tagsData[photoId] = response.tags || []
        } catch (error) {
          console.error(`Failed to load tags for photo ${photoId}:`, error)
          tagsData[photoId] = []
        }
      }
      setPhotoTagsData(tagsData)
    } catch (error) {
      console.error('Failed to save tags:', error)
      alert('Failed to save tags')
    }
  }

  const handleRemoveTags = () => {
    if (selectedTagIds.size === 0) return
    setShowConfirmDialog(true)
  }

  const confirmRemoveTags = async () => {
    setShowConfirmDialog(false)
    if (selectedTagIds.size === 0) return

    const tagIdsToRemove = Array.from(selectedTagIds)

    for (const tagId of tagIdsToRemove) {
      const tag = tags.find(t => t.id === tagId)
      if (!tag) continue

      try {
        await tagsApi.removeFromPhotos({
          photo_ids: selectedPhotoIds,
          tag_names: [tag.tag_name],
        })
      } catch (error) {
        console.error('Failed to remove tags:', error)
        alert(`Failed to remove tag: ${tag.tag_name}`)
      }
    }
    setSelectedTagIds(new Set())
    
    // Reload photo tags data to update the common tags list
    const tagsData: Record<number, any[]> = {}
    for (const photoId of selectedPhotoIds) {
      try {
        const response = await tagsApi.getPhotoTags(photoId)
        tagsData[photoId] = response.tags || []
      } catch (error) {
        console.error(`Failed to load tags for photo ${photoId}:`, error)
        tagsData[photoId] = []
      }
    }
    setPhotoTagsData(tagsData)
  }

  // Get common tags across all selected photos
  const commonTags = useMemo(() => {
    if (photos.length === 0 || selectedPhotoIds.length === 0) return []

    // Get tag linkage information for all selected photos
    const allPhotoTags: Record<number, any[]> = {}
    selectedPhotoIds.forEach(photoId => {
      allPhotoTags[photoId] = photoTagsData[photoId] || []
    })

    const tagIdToName = new Map(tags.map(t => [t.id, t.tag_name]))

    // Get all unique tag IDs from all photos
    const allTagIds = new Set<number>()
    Object.values(allPhotoTags).forEach(photoTags => {
      photoTags.forEach(tag => allTagIds.add(tag.tag_id))
    })

    // Find tags that are present in all selected photos
    const commonTagIds = Array.from(allTagIds).filter(tagId => {
      // Check if tag exists in all photos
      return selectedPhotoIds.every(photoId => {
        const photoTags = allPhotoTags[photoId] || []
        return photoTags.some(t => t.tag_id === tagId)
      })
    })

    // Convert to tag objects
    return commonTagIds
      .map(tagId => {
        const tagName = tagIdToName.get(tagId)
        if (!tagName) return null

        return {
          tag_id: tagId,
          tag_name: tagName,
        }
      })
      .filter(Boolean) as any[]
  }, [photos, tags, selectedPhotoIds, photoTagsData])

  // Get selected tag names for confirmation message
  const selectedTagNames = useMemo(() => {
    return Array.from(selectedTagIds)
      .map(id => {
        const tag = commonTags.find(t => t.tag_id === id)
        return tag ? tag.tag_name : ''
      })
      .filter(Boolean)
      .join(', ')
  }, [selectedTagIds, commonTags])

  return (
    <>
      {showConfirmDialog && (
        <ConfirmDialog
          message={`Remove ${selectedTagIds.size} selected tag(s) from ${selectedPhotoIds.length} photo(s)? (${selectedTagNames})`}
          onConfirm={confirmRemoveTags}
          onCancel={() => setShowConfirmDialog(false)}
        />
      )}
      <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
        <div className="bg-white rounded-lg shadow-xl w-full max-w-lg max-h-[80vh] flex flex-col">
          <div className="p-4 border-b">
            <h2 className="text-xl font-bold">Tag Selected Photos</h2>
            <p className="text-sm text-gray-600">
              {selectedPhotoIds.length} photo(s) selected
            </p>
          </div>
          <div className="p-4 flex-1 overflow-auto">
            <div className="mb-4 flex gap-2">
              <select
                value={selectedTagName}
                onChange={(e) => setSelectedTagName(e.target.value)}
                className="flex-1 px-3 py-2 border border-gray-300 rounded"
              >
                <option value="">Select tag...</option>
                {tags.map(tag => (
                  <option key={tag.id} value={tag.tag_name}>
                    {tag.tag_name}
                  </option>
                ))}
              </select>
              <button
                onClick={handleAddTag}
                disabled={!selectedTagName.trim()}
                className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
              >
                Add
              </button>
            </div>

            <div className="space-y-2">
              <h3 className="font-semibold text-sm text-gray-700 mb-2">
                {selectedPhotoIds.length === 1 ? 'Tags:' : 'Common Tags:'}
              </h3>
              {commonTags.length === 0 ? (
                <p className="text-gray-500 text-sm">No common tags found</p>
              ) : (
                commonTags.map(tag => {
                  return (
                    <div key={tag.tag_id} className="flex items-center gap-2 py-1">
                      <input
                        type="checkbox"
                        checked={selectedTagIds.has(tag.tag_id)}
                        onChange={(e) => {
                          const newSet = new Set(selectedTagIds)
                          if (e.target.checked) {
                            newSet.add(tag.tag_id)
                          } else {
                            newSet.delete(tag.tag_id)
                          }
                          setSelectedTagIds(newSet)
                        }}
                        className="w-4 h-4"
                      />
                      <span className="flex-1">
                        {tag.tag_name}
                      </span>
                    </div>
                  )
                })
              )}
            </div>
          </div>
          <div className="p-4 border-t flex justify-between">
            <button
              onClick={handleRemoveTags}
              disabled={selectedTagIds.size === 0}
              className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
            >
              Remove selected tags
            </button>
            <button
              onClick={onClose}
              className="px-4 py-2 bg-gray-100 text-gray-700 rounded hover:bg-gray-200"
            >
              Close
            </button>
          </div>
        </div>
      </div>
    </>
  )
}
