import { create } from 'zustand'
import type { Photo } from '../types/photo'

type ViewMode = 'grid' | 'preview'

interface PhotoStore {
  photos: Photo[]
  selectedPhotos: string[]
  activePhotoId: string | null
  /** The id of the photo where the current range-selection anchor lives.
   *  Set when the user clicks (without modifiers) or arrow-navigates,
   *  read by selectRange to figure out the start of a Shift+Click /
   *  Shift+Arrow range. Tracked as an id (not an index) so it survives
   *  filter changes and works correctly in tag-grouped mode where
   *  visual indices != API indices. */
  rangeStartId: string | null
  viewMode: ViewMode
  /** Flat sequence of photo ids in the order they currently appear in
   *  the timeline grid (including duplicates from tag-grouping). Used
   *  for both preview navigation order and range selection. Owned by
   *  the Timeline component. */
  visiblePhotoIds: string[]
  /** The id of the photo the preview was originally opened on. Captured
   *  by openPreview, restored by closePreview, so the user lands back
   *  on the photo they came from instead of whatever neighbour they
   *  arrow-navigated to inside the preview. */
  previewOriginPhotoId: string | null

  setPhotos: (photos: Photo[]) => void
  selectPhoto: (id: string) => void
  togglePhotoSelection: (id: string) => void
  /** Replace the selection with every photo between the current
   *  rangeStartId and the supplied endId, walking visiblePhotoIds in
   *  visual order. If there's no anchor yet, anchors at endId. */
  selectRange: (endId: string) => void
  deselectPhoto: (id: string) => void
  clearSelection: () => void
  setActivePhoto: (id: string | null) => void
  setViewMode: (mode: ViewMode) => void
  setVisiblePhotoIds: (ids: string[]) => void
  /** Open preview on a specific photo. The visibleSequence (optional)
   *  is the ordered list of photo ids the user currently sees in the
   *  timeline; passing it from the click site avoids a race where the
   *  passive Timeline publisher hasn't updated yet. */
  openPreview: (id: string, visibleSequence?: string[]) => void
  /** Close the preview overlay. The optional restoreId pins the active
   *  photo to a specific id (typically the photo preview was opened on)
   *  so the user lands back on it instead of whatever neighbour they
   *  arrow-navigated to inside the preview. PreviewView passes the
   *  value it captured at mount time, which is the most reliable
   *  source — the in-store previewOriginPhotoId is a fallback. */
  closePreview: (restoreId?: string | null) => void
  /** Drop photos that just left the timeline (e.g. discarded). Strips
   *  them from selection / visible sequence and advances activePhotoId
   *  to the next surviving neighbour so the grid still has a target
   *  for arrow keys after the removal. */
  removePhotosFromTimeline: (ids: string[]) => void
}

export const usePhotoStore = create<PhotoStore>((set) => ({
  photos: [],
  selectedPhotos: [],
  activePhotoId: null,
  rangeStartId: null,
  viewMode: 'grid',
  visiblePhotoIds: [],
  previewOriginPhotoId: null,

  setPhotos: (photos) => set({ photos }),

  selectPhoto: (id) => set({
    selectedPhotos: [id],
    activePhotoId: id,
    rangeStartId: id,
  }),

  togglePhotoSelection: (id) => set((state) => {
    const isSelected = state.selectedPhotos.includes(id)
    return {
      selectedPhotos: isSelected
        ? state.selectedPhotos.filter((photoId) => photoId !== id)
        : [...state.selectedPhotos, id],
      // A toggle-add anchors the range here so a subsequent Shift+click
      // extends from this id. A toggle-remove leaves the anchor alone.
      rangeStartId: isSelected ? state.rangeStartId : id,
      activePhotoId: id,
    }
  }),

  selectRange: (endId) =>
    set((state) => {
      const ids = state.visiblePhotoIds
      // No published sequence yet → just behave like a single-pick.
      if (ids.length === 0) {
        return {
          selectedPhotos: [endId],
          activePhotoId: endId,
          rangeStartId: endId,
        }
      }
      const anchorId = state.rangeStartId ?? state.activePhotoId ?? endId
      const startIdx = ids.indexOf(anchorId)
      const endIdx = ids.indexOf(endId)
      if (startIdx < 0 || endIdx < 0) {
        return {
          selectedPhotos: [endId],
          activePhotoId: endId,
          rangeStartId: endId,
        }
      }
      const min = Math.min(startIdx, endIdx)
      const max = Math.max(startIdx, endIdx)
      // De-dupe because the visible sequence can repeat photos in
      // tag-grouped mode.
      const seen = new Set<string>()
      const next: string[] = []
      for (let i = min; i <= max; i++) {
        const id = ids[i]
        if (!seen.has(id)) {
          seen.add(id)
          next.push(id)
        }
      }
      return {
        selectedPhotos: next,
        activePhotoId: endId,
        // Anchor stays put — the user can keep extending from the
        // original click point, matching Lightroom / Finder behavior.
      }
    }),

  deselectPhoto: (id) => set((state) => ({
    selectedPhotos: state.selectedPhotos.filter(photoId => photoId !== id)
  })),

  clearSelection: () => set({
    selectedPhotos: [],
    rangeStartId: null,
    activePhotoId: null,
  }),
  
  setActivePhoto: (id) => set({ activePhotoId: id }),

  setViewMode: (mode) => set({ viewMode: mode }),

  // No-op when the content is identical so callers can fire from an
  // effect without risking a re-render loop.
  setVisiblePhotoIds: (visiblePhotoIds) =>
    set((s) => {
      const prev = s.visiblePhotoIds
      if (prev.length === visiblePhotoIds.length) {
        let same = true
        for (let i = 0; i < prev.length; i++) {
          if (prev[i] !== visiblePhotoIds[i]) {
            same = false
            break
          }
        }
        if (same) return s
      }
      return { visiblePhotoIds }
    }),

  openPreview: (id, visibleSequence) =>
    set((s) => ({
      viewMode: 'preview',
      activePhotoId: id,
      // Snapshot the entry photo so closing returns the user there even
      // after they arrow-navigated through several neighbours.
      previewOriginPhotoId: id,
      // Adopt the caller-provided sequence when they pass one. Falls
      // back to whatever Timeline most recently published, which is
      // correct for paths like the global Space hotkey that don't have
      // a click site to compute the sequence from.
      visiblePhotoIds:
        visibleSequence && visibleSequence.length > 0
          ? visibleSequence
          : s.visiblePhotoIds,
    })),

  closePreview: (restoreId) =>
    set((s) => {
      // Prefer the explicit restoreId from PreviewView (captured at
      // mount time, immune to anything that might have cleared the
      // in-store origin during the preview session). Fall back to the
      // store-tracked origin and finally to the current active photo.
      const finalId =
        (restoreId ?? s.previewOriginPhotoId ?? s.activePhotoId) || null
      return {
        viewMode: 'grid',
        activePhotoId: finalId,
        rangeStartId: finalId,
        selectedPhotos: finalId ? [finalId] : [],
        previewOriginPhotoId: null,
      }
    }),

  removePhotosFromTimeline: (ids) =>
    set((state) => {
      if (ids.length === 0) return state
      const removed = new Set(ids)
      const newSelected = state.selectedPhotos.filter((id) => !removed.has(id))
      const newVisible = state.visiblePhotoIds.filter((id) => !removed.has(id))

      // Pick a survivor for activePhotoId so arrow nav still has a
      // landing spot. Walk forward from the old position first (the
      // intuitive direction for "delete current, advance to next"),
      // then fall back to walking backward if we were at the end.
      let newActive = state.activePhotoId
      if (newActive && removed.has(newActive)) {
        const oldIdx = state.visiblePhotoIds.indexOf(newActive)
        let found: string | null = null
        if (oldIdx >= 0) {
          for (let i = oldIdx + 1; i < state.visiblePhotoIds.length; i++) {
            if (!removed.has(state.visiblePhotoIds[i])) {
              found = state.visiblePhotoIds[i]
              break
            }
          }
          if (!found) {
            for (let i = oldIdx - 1; i >= 0; i--) {
              if (!removed.has(state.visiblePhotoIds[i])) {
                found = state.visiblePhotoIds[i]
                break
              }
            }
          }
        }
        newActive = found ?? newVisible[0] ?? null
      }

      return {
        selectedPhotos: newSelected,
        visiblePhotoIds: newVisible,
        activePhotoId: newActive,
        rangeStartId: newActive,
      }
    }),
}))