import { create } from 'zustand'
import type { ColorLabel } from '../constants/colorLabels'

export type MediaType = 'photo' | 'video' | 'raw' | 'heic'
export type { ColorLabel }
export type FlagFilter = 'any' | 'discarded' | 'date_warning'
export type SortField =
  | 'taken_at'
  | 'added_at'
  | 'filename'
  | 'file_size'
  | 'rating'
export type SortOrder = 'asc' | 'desc'
export type GroupBy = 'date' | 'tag' | 'rating' | 'color'

export interface FilterState {
  q: string
  dateFrom: string | null // ISO yyyy-mm-dd
  dateTo: string | null
  mediaTypes: MediaType[]
  ratingMin: number // 0-5; 0 means no filter
  ratingMax: number // 0-5; 0 means no filter
  colorLabel: ColorLabel | null
  flag: FlagFilter
  /** When set, restrict to photos in this heap. Independent of `activeHeapId`
   *  on the heap store — that's the target for the T shortcut. */
  heapId: string | null
  /** When set, restrict to photos in this folder. */
  folderId: string | null
  /** Restrict to photos that have ALL of these tag ids (AND semantics). */
  tagIds: string[]
  /** When true, restrict to photos flagged as duplicates by the scanner. */
  duplicates: boolean
  /** When true, restrict to photos classified as 'other' (needs_review). */
  needsReview: boolean
  /** Visual grouping mode. 'date' groups by month when sortBy is a date
   *  field; 'tag' groups by photo tag membership. Independent of filters. */
  groupBy: GroupBy
  sortBy: SortField
  sortOrder: SortOrder
}

/** Identifies which "section" of the app the user is currently viewing.
 *  Sections each carry their own filter state — switching to one restores
 *  whatever filters were active there last time, switching away saves the
 *  current state under the section being left. */
export const ALL_PHOTOS_SECTION = 'all-photos'

interface FilterStore extends FilterState {
  /** The active section id. Changes via navigateToSection. */
  currentSection: string
  /** Per-section snapshot of filter state, in-memory. Restored on return. */
  sectionFilters: Record<string, FilterState>
  /** Per-section "intrinsic" filters — the preset that defines what makes
   *  a section that section (e.g. flag=discarded for the discarded
   *  section). Used by clearAll to reset within a section without
   *  navigating away. */
  sectionPresets: Record<string, Partial<FilterState>>

  setQ: (q: string) => void
  setDateFrom: (date: string | null) => void
  setDateTo: (date: string | null) => void
  toggleMediaType: (t: MediaType) => void
  setRatingMin: (rating: number) => void
  setRatingMax: (rating: number) => void
  setColorLabel: (label: ColorLabel | null) => void
  setFlag: (flag: FlagFilter) => void
  setHeapId: (id: string | null) => void
  setFolderId: (id: string | null) => void
  setTagIds: (ids: string[]) => void
  toggleTagId: (id: string) => void
  setDuplicates: (v: boolean) => void
  setNeedsReview: (v: boolean) => void
  setGroupBy: (mode: GroupBy) => void
  setSortBy: (field: SortField) => void
  setSortOrder: (order: SortOrder) => void
  toggleSortOrder: () => void

  /** Navigate to a section. Saves the current section's filter state into
   *  the in-memory map under the OLD section id, then loads the saved
   *  state for the destination — or, if none exists, applies the preset
   *  overrides on top of INITIAL_FILTERS. The preset is also stored so
   *  clearAll inside the section resets correctly. */
  navigateToSection: (
    sectionId: string,
    presetOverrides?: Partial<FilterState>
  ) => void

  hydrate: (partial: Partial<FilterState> & { currentSection?: string }) => void
  /** Reset filters within the CURRENT section back to its preset. Doesn't
   *  navigate. For an explicit "go to all photos" use navigateToSection. */
  clearAll: () => void
}

export const INITIAL_FILTERS: FilterState = {
  q: '',
  dateFrom: null,
  dateTo: null,
  mediaTypes: [],
  ratingMin: 0,
  ratingMax: 0,
  colorLabel: null,
  flag: 'any',
  heapId: null,
  folderId: null,
  tagIds: [],
  duplicates: false,
  needsReview: false,
  groupBy: 'date',
  sortBy: 'taken_at',
  sortOrder: 'desc',
}

/** Pull the FilterState slice out of the full store, dropping the
 *  control fields. Used when snapshotting current filters into the
 *  per-section map. */
function snapshotFilters(s: FilterState): FilterState {
  return {
    q: s.q,
    dateFrom: s.dateFrom,
    dateTo: s.dateTo,
    mediaTypes: [...s.mediaTypes],
    ratingMin: s.ratingMin,
    ratingMax: s.ratingMax,
    colorLabel: s.colorLabel,
    flag: s.flag,
    heapId: s.heapId,
    folderId: s.folderId,
    tagIds: [...s.tagIds],
    duplicates: s.duplicates,
    needsReview: s.needsReview,
    groupBy: s.groupBy,
    sortBy: s.sortBy,
    sortOrder: s.sortOrder,
  }
}

export const useFilterStore = create<FilterStore>((set) => ({
  ...INITIAL_FILTERS,
  currentSection: ALL_PHOTOS_SECTION,
  sectionFilters: {},
  sectionPresets: { [ALL_PHOTOS_SECTION]: {} },

  setQ: (q) => set({ q }),
  setDateFrom: (dateFrom) => set({ dateFrom }),
  setDateTo: (dateTo) => set({ dateTo }),
  toggleMediaType: (t) =>
    set((s) => ({
      mediaTypes: s.mediaTypes.includes(t)
        ? s.mediaTypes.filter((x) => x !== t)
        : [...s.mediaTypes, t],
    })),
  setRatingMin: (ratingMin) => set({ ratingMin }),
  setRatingMax: (ratingMax) => set({ ratingMax }),
  setColorLabel: (colorLabel) => set({ colorLabel }),
  setFlag: (flag) => set({ flag }),
  setHeapId: (heapId) => set({ heapId }),
  setFolderId: (folderId) => set({ folderId }),
  setTagIds: (tagIds) => set({ tagIds }),
  toggleTagId: (id) =>
    set((s) => ({
      tagIds: s.tagIds.includes(id)
        ? s.tagIds.filter((t) => t !== id)
        : [...s.tagIds, id],
    })),
  setDuplicates: (duplicates) => set({ duplicates }),
  setNeedsReview: (needsReview) => set({ needsReview }),
  setGroupBy: (groupBy) => set({ groupBy }),
  setSortBy: (sortBy) => set({ sortBy }),
  setSortOrder: (sortOrder) => set({ sortOrder }),
  toggleSortOrder: () =>
    set((s) => ({ sortOrder: s.sortOrder === 'desc' ? 'asc' : 'desc' })),

  navigateToSection: (sectionId, presetOverrides = {}) =>
    set((s) => {
      // Snapshot the current section's filters before switching.
      const updatedSectionFilters = {
        ...s.sectionFilters,
        [s.currentSection]: snapshotFilters(s),
      }
      // Remember this section's intrinsic preset (last write wins, which
      // is fine — sections are uniquely identified by id).
      const updatedSectionPresets = {
        ...s.sectionPresets,
        [sectionId]: presetOverrides,
      }
      // Restore the destination section's saved state, or apply the
      // preset on top of fresh defaults if it's never been visited.
      const saved = updatedSectionFilters[sectionId]
      const next: FilterState = saved
        ? saved
        : { ...INITIAL_FILTERS, ...presetOverrides }

      return {
        ...next,
        currentSection: sectionId,
        sectionFilters: updatedSectionFilters,
        sectionPresets: updatedSectionPresets,
      }
    }),

  hydrate: (partial) => set(partial),
  clearAll: () =>
    set((s) => {
      const preset = s.sectionPresets[s.currentSection] ?? {}
      return { ...INITIAL_FILTERS, ...preset }
    }),
}))

/** Convert filter state to the query params the backend list endpoint expects.
 *  Empty / default values are omitted so the cache key is stable. */
export function filtersToParams(f: FilterState): Record<string, string | number> {
  const params: Record<string, string | number> = {}
  if (f.q.trim()) params.q = f.q.trim()
  if (f.dateFrom) params.date_from = f.dateFrom
  // Make the upper bound inclusive end-of-day: the backend compares
  // `taken_at <= date_to` as a full datetime, so a bare "2024-01-15"
  // would exclude every photo taken after midnight that day — a
  // single-day pick (from==to) would then match nothing.
  if (f.dateTo) params.date_to = `${f.dateTo}T23:59:59`
  if (f.mediaTypes.length > 0) params.media_type = f.mediaTypes.join(',')
  if (f.ratingMin > 0) params.rating_min = f.ratingMin
  if (f.ratingMax > 0) params.rating_max = f.ratingMax
  if (f.colorLabel) params.color_label = f.colorLabel
  if (f.flag === 'discarded') params.is_discarded = 'true'
  if (f.flag === 'date_warning') params.has_date_warning = 'true'
  if (f.heapId) params.heap_id = f.heapId
  if (f.folderId) params.folder_id = f.folderId
  if (f.tagIds.length > 0) params.tag_ids = f.tagIds.join(',')
  if (f.duplicates) params.is_duplicate = 'true'
  if (f.needsReview) params.needs_review = 'true'
  params.sort = f.sortBy
  params.order = f.sortOrder
  return params
}

/** True if any filter (other than the search box) is active. */
export function hasActiveFilters(f: FilterState): boolean {
  return (
    f.q.trim() !== '' ||
    f.dateFrom !== null ||
    f.dateTo !== null ||
    f.mediaTypes.length > 0 ||
    f.ratingMin > 0 ||
    f.ratingMax > 0 ||
    f.colorLabel !== null ||
    f.flag !== 'any' ||
    f.heapId !== null ||
    f.folderId !== null ||
    f.tagIds.length > 0 ||
    f.duplicates ||
    f.needsReview
  )
}
