import { useCallback, useEffect, useMemo, useState } from 'react';
import { createAssetView } from '../asset_manager';

type Identifier = string | number;

interface DocumentLike {
  id?: Identifier;
  [key: string]: unknown;
}

interface AssetObject {
  ordinal?: number;
  url?: string | null;
  metadata?: Record<string, unknown> | null;
  [key: string]: unknown;
}

interface AssetLike {
  id?: Identifier;
  cardinality?: number;
  url?: string | null;
  metadata?: Record<string, unknown> | null;
  objects?: AssetObject[];
  [key: string]: unknown;
}

type EnsureAssetUrl = (
  documentId: Identifier,
  asset: AssetLike,
  options?: { start?: number; limit?: number; [key: string]: unknown },
) => Promise<unknown>;

type GetAsset = (document: DocumentLike, assetType: string) => AssetLike | null | undefined;

interface AssetViewLike {
  getCardinality: () => number;
  getObjects: () => AssetObject[];
  getObject: (ordinal?: number) => AssetObject | null;
  getPrimaryUrl: () => string | null;
  getPrimaryMetadata: () => Record<string, unknown> | null;
}

interface UseAssetNavigatorOptions {
  document: DocumentLike | null | undefined;
  assetType: string;
  ensureAssetUrl?: EnsureAssetUrl;
  getAsset?: GetAsset;
  prefetch?: number;
  defaultOrdinal?: number;
}

type SetOrdinalArg = number | ((prev: number) => number);

interface AssetNavigatorReturn {
  document: DocumentLike | null | undefined;
  documentId: Identifier | null;
  asset: AssetLike | null;
  assetType: string;
  ordinal: number;
  setOrdinal: (next: SetOrdinalArg) => void;
  goPrev: () => void;
  goNext: () => void;
  canGoPrev: boolean;
  canGoNext: boolean;
  cardinality: number;
  currentObject: AssetObject | null;
  currentUrl: string | null;
  currentMetadata: AssetObject['metadata'];
  objects: AssetObject[];
  isLoading: boolean;
}

const clampOrdinalValue = (value: number, cardinality: number, defaultOrdinal: number) => {
  const raw = Number.isFinite(value) ? value : defaultOrdinal;
  let next = Math.max(1, Math.floor(raw));
  if (cardinality && cardinality > 0) {
    next = Math.min(next, cardinality);
  }
  return next;
};

export const useAssetNavigator = ({
  document,
  assetType,
  ensureAssetUrl,
  getAsset,
  prefetch = 2,
  defaultOrdinal = 1,
}: UseAssetNavigatorOptions): AssetNavigatorReturn => {
  const documentId = (document?.id ?? null) as Identifier | null;

  const asset = useMemo<AssetLike | null>(() => {
    if (!document || typeof getAsset !== 'function') {
      return null;
    }
    return getAsset(document, assetType) || null;
  }, [document, assetType, getAsset]);

  const view = useMemo<AssetViewLike>(
    () => createAssetView(asset) as unknown as AssetViewLike,
    [asset],
  );
  const cardinality = view.getCardinality();

  const [ordinal, setOrdinalInternal] = useState(defaultOrdinal);

  useEffect(() => {
    setOrdinalInternal(defaultOrdinal);
  }, [documentId, assetType, defaultOrdinal]);

  const setOrdinal = useCallback(
    (next: SetOrdinalArg) => {
      setOrdinalInternal((prev) => {
        const target = typeof next === 'function' ? next(prev) : next;
        return clampOrdinalValue(target, cardinality, defaultOrdinal);
      });
    },
    [cardinality, defaultOrdinal],
  );

  const goPrev = useCallback(() => setOrdinal((value) => value - 1), [setOrdinal]);
  const goNext = useCallback(() => setOrdinal((value) => value + 1), [setOrdinal]);

  const objects = view.getObjects();
  const currentObject = view.getObject(ordinal);
  const currentUrl = currentObject?.url ?? view.getPrimaryUrl() ?? null;
  const currentMetadata = currentObject?.metadata ?? view.getPrimaryMetadata() ?? null;

  const canGoPrev = ordinal > 1;
  const canGoNext = cardinality ? ordinal < cardinality : true;

  const ordinalsNeedingLoad = useMemo<number[]>(() => {
    const missing: number[] = [];
    if (!asset) {
      return missing;
    }
    const safePrefetch = Math.max(1, prefetch);
    const maxOrdinal = cardinality && cardinality > 0
      ? Math.min(cardinality, ordinal + safePrefetch - 1)
      : ordinal + safePrefetch - 1;

    for (let ord = ordinal; ord <= maxOrdinal; ord += 1) {
      const object = view.getObject(ord);
      if (!object?.url) {
        missing.push(ord);
      }
    }

    return missing;
  }, [asset, view, ordinal, prefetch, cardinality]);

  const fetchStart = ordinalsNeedingLoad.length ? ordinalsNeedingLoad[0] : null;
  const fetchEnd = ordinalsNeedingLoad.length
    ? ordinalsNeedingLoad[ordinalsNeedingLoad.length - 1]
    : null;
  const fetchLimit = fetchStart !== null && fetchEnd !== null ? fetchEnd - fetchStart + 1 : null;

  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (!documentId || !asset || !ensureAssetUrl) {
      setLoading(false);
      return;
    }
    if (fetchStart === null || fetchLimit === null) {
      setLoading(false);
      return;
    }

    let cancelled = false;
    setLoading(true);
    ensureAssetUrl(documentId, asset, {
      start: fetchStart,
      limit: fetchLimit,
    })
      .catch(() => {})
      .finally(() => {
        if (!cancelled) {
          setLoading(false);
        }
      });

    return () => {
      cancelled = true;
    };
  }, [documentId, asset, ensureAssetUrl, fetchStart, fetchLimit]);

  return {
    document,
    documentId,
    asset,
    assetType,
    ordinal,
    setOrdinal,
    goPrev,
    goNext,
    canGoPrev,
    canGoNext,
    cardinality,
    currentObject,
    currentUrl,
    currentMetadata,
    objects,
    isLoading: loading,
  };
};

export default useAssetNavigator;
