'use client';

import { useEffect, useRef, useState } from 'react';
import clsx from 'clsx';
import { ArrowDownTrayIcon } from '@heroicons/react/24/outline';
import type { Photo } from '@/lib/api';
import Spinner from '@/components/ui/Spinner';
import { aspectStyle, masonryTileClass } from './GalleryLayout';

interface PhotoTileProps {
  photo: Photo;
  /** Above-the-fold tiles load eagerly, the rest lazily. */
  eager?: boolean;
  onOpen: () => void;
  onDownload: () => void;
  downloading: boolean;
  labels: { download: string; downloading: string };
}

export default function PhotoTile({
  photo,
  eager,
  onOpen,
  onDownload,
  downloading,
  labels,
}: PhotoTileProps) {
  const imgRef = useRef<HTMLImageElement>(null);
  // 'initial' is what the server renders: fully visible, so a public gallery
  // still paints its photos if hydration is slow or JS never arrives. The
  // fade only takes over once we're mounted and know the image is still in
  // flight — a cached image reports `complete` here and skips it entirely
  // (its onLoad already fired before React attached the handler).
  const [state, setState] = useState<'initial' | 'pending' | 'loaded'>('initial');
  const pending = state === 'pending';

  useEffect(() => {
    setState(imgRef.current?.complete ? 'loaded' : 'pending');
  }, []);

  return (
    <div
      role="button"
      tabIndex={0}
      onClick={onOpen}
      onKeyDown={(e) => {
        if (e.key === 'Enter' || e.key === ' ') {
          e.preventDefault();
          onOpen();
        }
      }}
      className={clsx(
        masonryTileClass,
        'group relative bg-gray-100 cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-yellow'
      )}
      /* Sized from the stored dimensions before the bytes arrive, so a slow
         image never pushes its column around. */
      style={aspectStyle(photo.width, photo.height)}
    >
      {/* eslint-disable-next-line @next/next/no-img-element */}
      <img
        ref={imgRef}
        src={photo.urls.thumb}
        alt=""
        loading={eager ? 'eager' : 'lazy'}
        onLoad={() => setState('loaded')}
        onError={() => setState('loaded')}
        className={clsx(
          'w-full h-auto transition-[opacity,transform] duration-300 motion-reduce:transition-none group-hover:scale-[1.03]',
          pending ? 'opacity-0' : 'opacity-100'
        )}
      />
      {pending && (
        <span
          aria-hidden="true"
          className="absolute inset-0 bg-secondary-light-gray/70 animate-pulse motion-reduce:animate-none"
        />
      )}
      <div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors" />
      <button
        type="button"
        onClick={(e) => {
          e.stopPropagation();
          onDownload();
        }}
        disabled={downloading}
        aria-busy={downloading}
        aria-label={downloading ? labels.downloading : labels.download}
        className={clsx(
          'absolute bottom-2 right-2 flex p-2.5 md:p-2 rounded-full bg-black/50 text-white transition-opacity hover:bg-black/80 focus:outline-none focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-primary-yellow',
          // Touch devices have no hover to reveal it, and this is where the
          // button matters most — it is the entry point to the save sheet.
          downloading
            ? 'opacity-100 cursor-wait hover:bg-black/50'
            : 'opacity-100 md:opacity-0 md:group-hover:opacity-100'
        )}
      >
        {downloading ? <Spinner /> : <ArrowDownTrayIcon className="w-5 h-5 md:w-4 md:h-4" />}
      </button>
    </div>
  );
}
