import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { Loader2 } from 'lucide-react'
import {
  photos,
  type MemoriesResponse,
  type MemoryPhoto,
} from '../../services/api'
import type { Photo } from '../../types/photo'
import { usePhotoStore } from '../../store/photoStore'
import { useViewSettingsStore } from '../../store/viewSettingsStore'
import { PhotoThumbnail } from '../timeline/PhotoThumbnail'
import { useActiveHeapMembers } from '../../hooks/useActiveHeapMembersQuery'
import { useGridKeyNav } from '../../hooks/useGridKeyNav'

// GAP matches Timeline so memories cells line up visually with what the
// user sees in the main grid; the cell size is read live from the
// shared view-settings store.
const GAP = 4


/**
 * "On this day" view. Same visual + interaction grid as Timeline —
 * PhotoThumbnail cells wired up to the shared photo store, so selection,
 * heap membership, preview (double-click / Enter), and drag-to-heap all
 * work identically. Just grouped by year instead of month, and sourced
 * from the /memories endpoint.
 */
export function MemoriesView() {
  const { data, isLoading } = useQuery<MemoriesResponse>({
    queryKey: ['memories'],
    queryFn: photos.memories,
    staleTime: 60_000 * 30,
  })

  const memories = data?.memories ?? []

  // Flat ordered sequence of ids across every year section — fed to
  // the store so preview arrow nav walks the same order the user sees.
  const visibleSequence = useMemo(
    () => memories.flatMap((g) => g.photos.map((p) => p.id)),
    [memories],
  )
  const setVisiblePhotoIds = usePhotoStore((s) => s.setVisiblePhotoIds)
  useEffect(() => {
    setVisiblePhotoIds(visibleSequence)
  }, [visibleSequence, setVisiblePhotoIds])

  const selectedPhotos = usePhotoStore((s) => s.selectedPhotos)
  const selectPhoto = usePhotoStore((s) => s.selectPhoto)
  const togglePhotoSelection = usePhotoStore((s) => s.togglePhotoSelection)
  const selectRange = usePhotoStore((s) => s.selectRange)
  const openPreview = usePhotoStore((s) => s.openPreview)
  const viewMode = usePhotoStore((s) => s.viewMode)
  const { memberIds: activeHeapMembers } = useActiveHeapMembers()

  const visibleSequenceRef = useRef<string[]>([])
  visibleSequenceRef.current = visibleSequence

  const handleCellClick = useCallback(
    (photo: Photo, e: React.MouseEvent) => {
      if (e.shiftKey) selectRange(photo.id)
      else if (e.ctrlKey || e.metaKey) togglePhotoSelection(photo.id)
      else selectPhoto(photo.id)
    },
    [selectRange, togglePhotoSelection, selectPhoto],
  )
  const handleCellDoubleClick = useCallback(
    (photo: Photo) => {
      openPreview(photo.id, visibleSequenceRef.current)
    },
    [openPreview],
  )

  // Measure the scroll container directly and subtract its padding to
  // get the real grid width. A ResizeObserver on the container itself
  // always fires on width changes — sentinel elements can miss changes
  // in some edge cases (zero-height, detached during loading states).
  const [containerWidth, setContainerWidth] = useState(0)
  const roRef = useRef<ResizeObserver | null>(null)
  const scrollRef = useCallback((el: HTMLDivElement | null) => {
    roRef.current?.disconnect()
    roRef.current = null
    scrollElRef.current = el
    if (!el) return
    const measure = () => {
      const style = window.getComputedStyle(el)
      const padX =
        parseFloat(style.paddingLeft) + parseFloat(style.paddingRight)
      setContainerWidth(Math.max(0, el.clientWidth - padX))
    }
    measure()
    const ro = new ResizeObserver(measure)
    ro.observe(el)
    roRef.current = ro
  }, [])
  const scrollElRef = useRef<HTMLDivElement | null>(null)
  useEffect(() => () => roRef.current?.disconnect(), [])
  // Fixed-size cells — always exactly the user-configured thumbnail
  // size, regardless of container width. See Timeline's column-math
  // comment for the tradeoff (may leave a small gap on the right edge).
  const thumbnailSize = useViewSettingsStore((s) => s.thumbnailSize)
  const { columns, cellSize } = useMemo(() => {
    if (containerWidth === 0) return { columns: 4, cellSize: thumbnailSize }
    const cols = Math.max(
      1,
      Math.floor((containerWidth + GAP) / (thumbnailSize + GAP)),
    )
    return { columns: cols, cellSize: thumbnailSize }
  }, [containerWidth, thumbnailSize])

  // Break each year's photos into rows of `columns` ids — matches the
  // shape useGridKeyNav expects. Arrow-nav crosses year boundaries
  // naturally because rows flow in visual order across groups.
  const gridNavRows = useMemo(() => {
    const rows: { cells: { id: string }[] }[] = []
    for (const group of memories) {
      const cells = group.photos.map((m) => ({ id: m.id }))
      for (let i = 0; i < cells.length; i += columns) {
        rows.push({ cells: cells.slice(i, i + columns) })
      }
    }
    return rows
  }, [memories, columns])

  const scrollRowIntoView = useCallback((rowIdx: number) => {
    const firstId = gridNavRowsRef.current[rowIdx]?.cells[0]?.id
    if (!firstId) return
    const scrollEl = scrollElRef.current
    if (!scrollEl) return
    const cellEl = scrollEl.querySelector<HTMLElement>(
      `[data-photo-id="${firstId}"]`,
    )
    if (!cellEl) return
    const peek = Math.round(cellSize * 0.35)
    const cellTop = cellEl.offsetTop
    const cellBottom = cellTop + cellEl.offsetHeight
    const viewTop = scrollEl.scrollTop
    const viewBottom = viewTop + scrollEl.clientHeight
    if (cellTop - peek < viewTop) {
      scrollEl.scrollTo({ top: Math.max(0, cellTop - peek) })
    } else if (cellBottom + peek > viewBottom) {
      scrollEl.scrollTo({ top: cellBottom + peek - scrollEl.clientHeight })
    }
  }, [])
  // Rows change on resize / data load — read via ref so the callback
  // identity stays stable.
  const gridNavRowsRef = useRef(gridNavRows)
  gridNavRowsRef.current = gridNavRows

  useGridKeyNav({
    rows: gridNavRows,
    enabled: viewMode === 'grid',
    scrollRowIntoView,
  })

  if (isLoading) {
    return (
      <div className="flex h-full items-center justify-center text-text-muted">
        <Loader2 className="mr-2 h-4 w-4 animate-spin" />
        Loading memories…
      </div>
    )
  }

  if (memories.length === 0) {
    return (
      <div className="flex h-full flex-col items-center justify-center gap-2 px-6 text-center text-text-muted">
        <div className="text-sm font-medium text-text">
          No memories for today
        </div>
        <p className="max-w-sm text-xs">
          Photos taken on this date in previous years will appear here.
        </p>
      </div>
    )
  }

  return (
    <div
      ref={scrollRef}
      className="h-full overflow-auto bg-bg p-4"
      role="grid"
      aria-label="Memories"
    >
      <h2 className="mb-4 text-sm font-semibold uppercase tracking-wide text-text">
        On this day &middot; {data?.date}
      </h2>

      <div className="space-y-6">
        {memories.map((group) => (
          <section key={group.year} className="space-y-2">
            <header className="flex items-baseline gap-2">
              <h3 className="text-sm font-semibold uppercase tracking-wide text-text">
                {group.year}
              </h3>
              <span className="text-xs text-text-muted">
                {group.years_ago} year{group.years_ago === 1 ? '' : 's'} ago
              </span>
            </header>
            <div
              className="grid"
              style={{
                gridTemplateColumns: `repeat(${columns}, ${cellSize}px)`,
                gridAutoRows: `${cellSize}px`,
                gap: `${GAP}px`,
              }}
            >
              {group.photos.map((m) => (
                <div key={m.id} data-photo-id={m.id}>
                  <PhotoThumbnail
                    photo={memoryToPhoto(m)}
                    size={cellSize}
                    fill
                    isSelected={selectedPhotos.includes(m.id)}
                    isInActiveHeap={activeHeapMembers.has(m.id)}
                    onClick={handleCellClick}
                    onDoubleClick={handleCellDoubleClick}
                  />
                </div>
              ))}
            </div>
          </section>
        ))}
      </div>
    </div>
  )
}

/** Widen the slim MemoryPhoto payload to the full Photo shape the
 *  thumbnail expects. Fields the memories endpoint doesn't return
 *  (is_duplicate, file_hash, ...) get sensible defaults — memories
 *  never surface discarded/duplicate photos server-side. */
function memoryToPhoto(m: MemoryPhoto): Photo {
  return {
    id: m.id,
    filepath: m.filename,
    filename: m.filename,
    media_type: m.media_type,
    width: m.width,
    height: m.height,
    taken_at: m.taken_at,
    rating: m.rating,
    color_label: m.color_label,
    is_discarded: m.is_discarded,
    is_duplicate: false,
    file_hash: '',
    folder_id: null,
    added_at: null,
    thumb_small: m.thumb_small ?? undefined,
    thumb_medium: m.thumb_medium ?? undefined,
  }
}
