/**
 * V3.9 — thumbnail-cache hook for `<TransferRow showThumbnail />`.
 *
 * Wires a single shared `ShadeThumbnailCache` into the React tree, lazily
 * created on first use, and exposes a tracked `useThumbnail(streamId)`
 * helper that re-renders consumers when their thumbnail arrives.
 *
 * The cache itself lives outside React state so it survives Strict-Mode
 * double-mounts and unmounted-component thumbnail arrivals without a
 * memory leak — entries evict on LRU + size cap, not on component
 * lifecycle.
 */

import {
  createContext,
  createElement,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactElement,
  type ReactNode,
} from 'react';
import { ShadeThumbnailCache, type ThumbnailHit } from '@shade/sdk';

const ThumbnailCacheContext = createContext<ShadeThumbnailCache | null>(null);

export interface ThumbnailProviderProps {
  /** Optional shared cache. When omitted the provider creates one. */
  cache?: ShadeThumbnailCache;
  children?: ReactNode;
}

/**
 * Provides a shared `ShadeThumbnailCache` to the subtree. Place once near
 * the root of your app (typically just inside `<ShadeRuntimeProvider />`)
 * so every transfer row consults the same cache.
 */
export function ShadeThumbnailProvider({
  cache,
  children,
}: ThumbnailProviderProps): ReactElement {
  const value = useMemo(() => cache ?? new ShadeThumbnailCache(), [cache]);
  return createElement(
    ThumbnailCacheContext.Provider,
    { value },
    children,
  );
}

/**
 * Returns the in-context thumbnail cache, lazily constructing one if no
 * provider is mounted (so widgets don't have to demand the provider).
 */
export function useThumbnailCache(): ShadeThumbnailCache {
  const ctx = useContext(ThumbnailCacheContext);
  // Module-level fallback when no provider is mounted — single instance
  // shared across all `useThumbnail()` calls in that scenario.
  return ctx ?? fallbackCache();
}

let fallback: ShadeThumbnailCache | null = null;
function fallbackCache(): ShadeThumbnailCache {
  if (fallback === null) fallback = new ShadeThumbnailCache();
  return fallback;
}

/**
 * Returns the cached thumbnail bytes + mime for `streamId`, or `null`
 * when nothing is cached yet. Subscribes the calling component to cache
 * changes so it re-renders on arrival.
 */
export function useThumbnail(
  streamId: string | null | undefined,
  expectedHash?: string,
): ThumbnailHit | null {
  const cache = useThumbnailCache();
  const [, setTick] = useState(0);
  useEffect(() => {
    if (streamId === null || streamId === undefined) return;
    if (expectedHash !== undefined) cache.setExpectedHash(streamId, expectedHash);
    return cache.onChange((changed) => {
      if (changed === streamId) setTick((n) => n + 1);
    });
  }, [cache, streamId, expectedHash]);
  if (streamId === null || streamId === undefined) return null;
  return cache.get(streamId, expectedHash);
}
