import { useEffect, useState } from 'react';

import { ipcChannels } from '../../shared/ipc/contracts';
import type { AssetId, ProjectId } from '../../shared/types';

import { getDndApi } from './dndApi';

/** Module-level кэш assetId → url; сбрасывается при смене/закрытии проекта. */
const urlCache = new Map<AssetId, string | null>();
const invalidateListeners = new Set<() => void>();
let sessionProjectHooked = false;
let lastSessionProjectId: ProjectId | null | undefined;

function ensureSessionProjectInvalidation(): void {
  if (sessionProjectHooked) return;
  sessionProjectHooked = true;
  try {
    getDndApi().on(ipcChannels.session.stateChanged, ({ state }) => {
      const next = state.project?.id ?? null;
      if (lastSessionProjectId === undefined) {
        lastSessionProjectId = next;
        return;
      }
      if (lastSessionProjectId !== next) {
        lastSessionProjectId = next;
        invalidateAssetUrlCache();
      }
    });
  } catch {
    /* вне Electron / тесты */
  }
}

export function peekAssetUrlCache(assetId: AssetId): string | null | undefined {
  return urlCache.has(assetId) ? (urlCache.get(assetId) ?? null) : undefined;
}

export function invalidateAssetUrlCache(): void {
  urlCache.clear();
  for (const fn of invalidateListeners) {
    try {
      fn();
    } catch {
      /* ignore */
    }
  }
}

/**
 * Возвращает `dnd://` / file URL для превью. Пока загрузка или сменился id — `null`.
 * Повторные запросы того же id не ходят в IPC, пока кэш не инвалидирован.
 */
export function useAssetUrl(assetId: AssetId | null | undefined): string | null {
  ensureSessionProjectInvalidation();
  const id = assetId ?? null;
  const [entry, setEntry] = useState<{ assetId: AssetId; url: string | null } | null>(() => {
    if (id === null) return null;
    const hit = peekAssetUrlCache(id);
    return hit === undefined ? null : { assetId: id, url: hit };
  });
  const [epoch, setEpoch] = useState(0);

  useEffect(() => {
    const onInvalidate = () => setEpoch((n) => n + 1);
    invalidateListeners.add(onInvalidate);
    return () => {
      invalidateListeners.delete(onInvalidate);
    };
  }, []);

  useEffect(() => {
    if (id === null) {
      setEntry(null);
      return undefined;
    }
    const hit = peekAssetUrlCache(id);
    if (hit !== undefined) {
      setEntry({ assetId: id, url: hit });
      return undefined;
    }
    let cancelled = false;
    void getDndApi()
      .invoke(ipcChannels.project.assetFileUrl, { assetId: id })
      .then((r) => {
        urlCache.set(id, r.url);
        if (!cancelled) setEntry({ assetId: id, url: r.url });
      });
    return () => {
      cancelled = true;
    };
  }, [id, epoch]);

  if (id === null) {
    return null;
  }
  if (entry?.assetId !== id) {
    return null;
  }
  return entry.url;
}

/** @deprecated use `useAssetUrl` */
export function useAssetImageUrl(assetId: AssetId | null | undefined): string | null {
  return useAssetUrl(assetId);
}
