/**
 * Baseline parity scenarios: mount/render counts, strict-mode ghost
 * invocations, memo caching, effect lifecycles, setState batching, async
 * updates, unmount. Runs in dev and prod via the vitest projects.
 */
/* oxlint-disable react/exhaustive-deps -- intentional missing-dep patterns are part of the scenarios */
import { useEffect, useMemo, useReducer, useRef, useState } from "react";
import { describeParity, type Scenario } from "./describeParity";

const scenarios: Scenario[] = [
  {
    name: "mount: render count, useState initializer ghost-invoked, first result kept",
    use: (log) => {
      const [a] = useState(() => {
        log("init-a");
        return 1;
      });
      const [b] = useState(() => {
        log("init-b");
        return 2;
      });
      log(`render a=${a} b=${b}`);
    },
  },
  {
    name: "useMemo: ghost-invoked when computing, cached across passes and re-renders",
    use: (log) => {
      const [n, setN] = useState(0);
      useMemo(() => {
        log(`memo n=${n}`);
        return n;
      }, [n]);
      log(`render n=${n}`);
      useEffect(() => {
        if (n === 0) setN(1);
      }, [n]);
    },
  },
  {
    name: "useMemo: first returned instance is the one kept",
    use: (log) => {
      let instance = 0;
      const obj = useMemo(() => ({ instance: ++instance }), []);
      const first = useRef(obj);
      log(`instance=${obj.instance} identity-stable=${first.current === obj}`);
    },
  },
  {
    name: "useReducer: initializer ghost-invoked, first result kept",
    use: (log) => {
      let initCount = 0;
      const [state] = useReducer(
        (s: number, a: number) => s + a,
        0,
        (arg: number) => {
          initCount++;
          log(`init-${initCount}`);
          return arg + initCount * 10;
        },
      );
      log(`render state=${state}`);
    },
  },
  {
    name: "useReducer: dispatch reducer ghost-invoked",
    use: (log) => {
      const countRef = useRef(0);
      const [state, dispatch] = useReducer((s: number, _a: number) => {
        countRef.current++;
        const value = countRef.current * 100;
        log(`reducer-${countRef.current} state=${s} -> ${value}`);
        return value;
      }, 0);
      log(`render state=${state}`);
      return { dispatch };
    },
    drive: async ({ api, act }) => {
      await act(() => api().dispatch(1));
    },
  },
  {
    name: "effects cycle mount, strict remount, deps",
    use: (log) => {
      const [n] = useState(0);
      useEffect(() => {
        log("e1-mount");
        return () => log("e1-unmount");
      });
      useEffect(() => {
        log("e2-mount");
        return () => log("e2-unmount");
      }, []);
      useEffect(() => {
        log(`e3-mount n=${n}`);
        return () => log(`e3-unmount n=${n}`);
      }, [n]);
    },
  },
  {
    name: "setState in effect",
    use: (log) => {
      const [count, setCount] = useState(0);
      log(`render ${count}`);
      useEffect(() => {
        log(`effect ${count}`);
        if (count === 0) setCount(1);
        return () => log(`cleanup ${count}`);
      }, [count]);
    },
  },
  {
    name: "event-handler setState: single re-render, updater ghost-invoked",
    use: (log) => {
      const [count, setCount] = useState(0);
      log(`render ${count}`);
      return {
        increment: () =>
          setCount((prev) => {
            log(`updater prev=${prev}`);
            return prev + 1;
          }),
      };
    },
    drive: async ({ api, act }) => {
      await act(() => api().increment());
    },
  },
  {
    name: "event-handler setState: multiple setStates batch into one render",
    use: (log) => {
      const [a, setA] = useState(0);
      const [b, setB] = useState(0);
      log(`render a=${a} b=${b}`);
      return {
        both: () => {
          setA(1);
          setB(2);
        },
      };
    },
    drive: async ({ api, act }) => {
      await act(() => api().both());
    },
  },
  {
    name: "async setState from a promise scheduled in an effect",
    use: (log) => {
      const [count, setCount] = useState(0);
      log(`render ${count}`);
      useEffect(() => {
        if (count === 0) {
          void Promise.resolve().then(() => {
            log("promise");
            setCount(1);
          });
        }
      }, [count]);
    },
    drive: ({ settle }) => settle(),
  },
  {
    name: "async setState from a setTimeout scheduled in an effect",
    use: (log) => {
      const [count, setCount] = useState(0);
      log(`render ${count}`);
      useEffect(() => {
        if (count === 0) {
          // The cleanup matters: without it the strict double effect-mount
          // schedules two timers, and whether their dispatches batch into one
          // render is a race between the timer phase and the schedulers.
          const timer = setTimeout(() => {
            log("timeout");
            setCount(1);
          }, 5);
          return () => clearTimeout(timer);
        }
        return undefined;
      }, [count]);
    },
    drive: ({ settle }) => settle(),
  },
  {
    name: "setState from the first strict effect mount survives its cleanup",
    use: (log) => {
      const [count, setCount] = useState(0);
      const runs = useRef(0);
      log(`render ${count}`);
      useEffect(() => {
        runs.current++;
        const n = runs.current;
        log(`mount#${n} count=${count}`);
        if (n === 1) setCount(1);
        return () => log(`cleanup#${n}`);
      }, []);
    },
  },
  {
    name: "setState from both strict effect mounts: last value wins",
    use: (log) => {
      const [count, setCount] = useState(0);
      const runs = useRef(0);
      log(`render ${count}`);
      useEffect(() => {
        runs.current++;
        const n = runs.current;
        log(`mount#${n} count=${count}`);
        setCount(n === 1 ? 1 : 2);
        return () => log(`cleanup#${n}`);
      }, []);
    },
  },
  {
    name: "updater setState from both strict effect mounts chains",
    use: (log) => {
      const [count, setCount] = useState(0);
      const runs = useRef(0);
      log(`render ${count}`);
      useEffect(() => {
        runs.current++;
        const n = runs.current;
        setCount((prev) => {
          log(`updater#${n} prev=${prev}`);
          return prev + n;
        });
      }, []);
    },
  },
  {
    name: "useReducer: dispatching the same state",
    use: (log) => {
      const [state, dispatch] = useReducer((s: number) => s, 42);
      log(`render ${state}`);
      return { dispatch };
    },
    drive: async ({ api, act }) => {
      await act(() => api().dispatch(0));
    },
  },
  {
    name: "updater returning a different value per invocation",
    divergence: { bridge: "multiset" },
    use: (log) => {
      const [count, setCount] = useState(0);
      const calls = useRef(0);
      log(`render ${count}`);
      useEffect(() => {
        log("effect mount");
        setCount((prev) => {
          calls.current++;
          log(`updater call #${calls.current} with prev=${prev}`);
          return calls.current === 1 ? 100 : 200;
        });
        return () => log("effect cleanup");
      }, []);
    },
  },
  {
    name: "render-phase update: setState during render re-renders before committing",
    use: (log) => {
      const [count, setCount] = useState(0);
      log(`render ${count}`);
      if (count === 0) setCount(1);
    },
  },
  {
    name: "unmount runs cleanups",
    use: (log) => {
      useEffect(() => {
        log("mount-1");
        return () => log("unmount-1");
      }, []);
      useEffect(() => {
        log("mount-2");
        return () => log("unmount-2");
      }, []);
    },
    unmountAtEnd: true,
  },
];

describeParity(scenarios);
