import React, {
  createContext,
  useCallback,
  useContext,
  useState,
  ReactNode,
} from "react";
import { getPersonHistory, PersonHistoryEntry } from "@/apis/personApi";

type PersonAppearances = {
  raw: PersonHistoryEntry[];
  byShow: Record<number, number[]>;
  showIds: number[];
};

type PersonContextType = {
  getPersonAppearances: (personId: number) => Promise<PersonAppearances>;
  getShowIds: (personId: number) => Promise<number[]>;
  getSeasonsForShow: (personId: number, showId: number) => Promise<number[]>;
  isLoading: (personId: number) => boolean;
  getError: (personId: number) => string | null;
  invalidatePerson: (personId: number) => void;
};

const PersonContext = createContext<PersonContextType | null>(null);

export const PersonProvider = ({ children }: { children: ReactNode }) => {
  const [cache, setCache] = useState<Record<number, PersonAppearances>>({});
  const [loading, setLoading] = useState<Record<number, boolean>>({});
  const [errors, setErrors] = useState<Record<number, string | null>>({});

  const buildAppearances = (
    entries: PersonHistoryEntry[]
  ): PersonAppearances => {
    const byShow: Record<number, Set<number>> = {};
    for (const e of entries) {
      if (!byShow[e.showId]) byShow[e.showId] = new Set();
      byShow[e.showId].add(e.seasonNumber);
    }
    const byShowSorted: Record<number, number[]> = Object.fromEntries(
      Object.entries(byShow).map(([showId, seasonsSet]) => [
        Number(showId),
        Array.from(seasonsSet).sort((a, b) => a - b),
      ])
    );
    return {
      raw: entries,
      byShow: byShowSorted,
      showIds: Object.keys(byShowSorted)
        .map(Number)
        .sort((a, b) => a - b),
    };
  };

  const fetchAndCache = useCallback(async (personId: number) => {
    setLoading((l) => ({ ...l, [personId]: true }));
    setErrors((e) => ({ ...e, [personId]: null }));
    try {
      const data = await getPersonHistory(personId);
      const appearances = buildAppearances(data);
      setCache((c) => ({ ...c, [personId]: appearances }));
      return appearances;
    } catch (e: any) {
      setErrors((err) => ({
        ...err,
        [personId]: e?.message || "Fehler beim Laden",
      }));
      return { raw: [], byShow: {}, showIds: [] };
    } finally {
      setLoading((l) => ({ ...l, [personId]: false }));
    }
  }, []);

  const getPersonAppearances = useCallback(
    async (personId: number) => {
      if (cache[personId]) return cache[personId];
      return await fetchAndCache(personId);
    },
    [cache, fetchAndCache]
  );

  const getShowIds = useCallback(
    async (personId: number) => {
      const app = await getPersonAppearances(personId);
      return app.showIds;
    },
    [getPersonAppearances]
  );

  const getSeasonsForShow = useCallback(
    async (personId: number, showId: number) => {
      const app = await getPersonAppearances(personId);
      return app.byShow[showId] || [];
    },
    [getPersonAppearances]
  );

  const isLoading = (personId: number) => !!loading[personId];
  const getError = (personId: number) => errors[personId] || null;

  const invalidatePerson = (personId: number) => {
    setCache((c) => {
      const copy = { ...c };
      delete copy[personId];
      return copy;
    });
  };

  return (
    <PersonContext.Provider
      value={{
        getPersonAppearances,
        getShowIds,
        getSeasonsForShow,
        isLoading,
        getError,
        invalidatePerson,
      }}
    >
      {children}
    </PersonContext.Provider>
  );
};

export const usePersonContext = () => {
  const ctx = useContext(PersonContext);
  if (!ctx)
    throw new Error("usePersonContext must be used within PersonProvider");
  return ctx;
};
