import { useEffect, useRef, useState } from 'react';
import { X, ZoomIn } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { thumbUrl } from './shared';

// The contents of one map cluster, listed. Clicking a circle marked "712" used to do nothing but zoom, which
// answers "where are they" and never "what are they" — and for a tight cluster of photos taken in one place,
// zooming never separates them at all.
//
// The id list is SNAPSHOT at click time by the caller, not paged out of the cluster on demand. maplibre's
// cluster ids are assigned per zoom level and stop existing the moment the camera moves, so a panel that
// fetched page 2 lazily would fail exactly when someone scrolled it and then nudged the map. A cluster of
// 12,000 assets is 12,000 short strings — cheap to hold, and it makes the panel independent of the map.
//
// Lazy is therefore about DOM and images, not data: `PAGE` tiles at a time, grown by an IntersectionObserver.

/** What the caller snapshots when a cluster is clicked. */
export type ClusterSelection = {
  /** maplibre's cluster id — only valid at the zoom it was read at, so it is used for highlighting and nothing else. */
  clusterId: number;
  /** Every asset in the cluster, in the order the source returned them. */
  ids: string[];
  center: [number, number];
  /** The zoom that would break this cluster apart, resolved at click time while the id was still live. */
  expansionZoom: number;
};

/** How many tiles to add each time the end of the list comes into view. */
const PAGE = 90;

type MapClusterPanelProps = {
  cluster: ClusterSelection;
  /** Human name for the area, derived from the markers' own city/country. */
  place: string | null;
  token: string | null;
  onOpen: (id: string) => void;
  onZoom: () => void;
  onClose: () => void;
};

export const MapClusterPanel = ({ cluster, place, token, onOpen, onZoom, onClose }: MapClusterPanelProps) => {
  const [shown, setShown] = useState(PAGE);
  const sentinelRef = useRef<HTMLDivElement | null>(null);

  const total = cluster.ids.length;

  useEffect(() => setShown(PAGE), [cluster.ids]);

  // The sentinel is always rendered, so the observer never has to be torn down and re-attached as the list
  // grows; once everything is shown the callback just clamps to the same value and React bails out.
  useEffect(() => {
    const node = sentinelRef.current;
    if (!node) return;

    const observer = new IntersectionObserver(
      (entries) => {
        if (entries.some((entry) => entry.isIntersecting)) setShown((count) => Math.min(count + PAGE, total));
      },
      { rootMargin: '400px' },
    );
    observer.observe(node);
    return () => observer.disconnect();
  }, [total]);

  // Safe to own Escape: AssetViewer only registers its own handler while an asset is open, and when one is it
  // sits above this panel, so the two never both fire.
  useEffect(() => {
    const onKey = (ev: KeyboardEvent) => {
      if (ev.key !== 'Escape') return;
      onClose();
      ev.preventDefault();
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  return (
    <aside className="absolute inset-y-0 left-0 z-10 flex w-[min(21rem,72vw)] flex-col border-r bg-background/95 shadow-xl backdrop-blur">
      <header className="flex items-center gap-1 border-b px-3 py-2">
        <div className="min-w-0 flex-1">
          <p className="truncate text-sm font-medium">{place ?? 'Selected area'}</p>
          <p className="text-xs text-muted-foreground">
            {total.toLocaleString()} photo{total === 1 ? '' : 's'}
          </p>
        </div>
        <Button variant="ghost" size="icon" title="Zoom in on this cluster" onClick={onZoom}>
          <ZoomIn className="h-4 w-4" />
        </Button>
        <Button variant="ghost" size="icon" title="Close (Esc)" onClick={onClose}>
          <X className="h-4 w-4" />
        </Button>
      </header>

      <div className="min-h-0 flex-1 overflow-y-auto p-1">
        <div className="grid grid-cols-3 gap-1">
          {cluster.ids.slice(0, shown).map((id) => (
            <button
              key={id}
              type="button"
              title="Open photo"
              onClick={() => onOpen(id)}
              className="relative aspect-square overflow-hidden rounded-sm bg-muted"
            >
              <img
                src={thumbUrl(id, token)}
                alt=""
                loading="lazy"
                decoding="async"
                className="h-full w-full object-cover transition hover:opacity-80"
              />
            </button>
          ))}
        </div>

        <div ref={sentinelRef} className="h-1" />

        {shown < total && (
          <p className="py-2 text-center text-xs text-muted-foreground">{(total - shown).toLocaleString()} more…</p>
        )}
      </div>
    </aside>
  );
};
