"use client";

import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";

// --- "Confirm before it's gone" hold ---------------------------------------
//
// Some lists drop an item from view the instant some action makes it no
// longer "belong" there: checking off a compact-view to-do or an Overdue
// occurrence, or -- with nothing to check off yet -- a brand-new empty
// group in compact view, which would otherwise never even flash on screen.
// That underlying change still applies instantly; the item's row just
// sticks around a while longer before actually leaving the list, long
// enough to catch an accidental tap or give a freshly-created group a
// moment to be noticed. Three timed stages, each its own setTimeout so a
// mid-flight cancel (an uncheck, or the group being given real content)
// can stop it cleanly:
//   "visible"    (0s-holdMs)         full opacity, still on screen.
//   "fading"     (+0s-1s)            opacity eases to 0 over 1s.
//   "collapsing" (+1s-1.2s)          height eases to 0 so the row (and
//                                    anything that only existed to contain
//                                    it) contracts around it, then it's gone.
// This is presentation-only bookkeeping -- whatever underlying state a
// caller is reacting to (a to-do's `completed` flag, a group merely
// existing) changes immediately regardless; this only delays the point at
// which a list's own filtering stops counting the item as "still here".
// Ids are opaque strings, so unrelated features (a to-do id, a group id, a
// scheduled occurrence's `${scheduledTodoId}-${occurrenceDate}` key) can
// safely share one instance without knowing about each other.
const HOLD_MS = 6_000;
const FADE_MS = 1_000;
const COLLAPSE_MS = 200;

export type HoldPhase = "visible" | "fading" | "collapsing";

interface HoldOnCompleteContextValue {
  holds: Map<string, HoldPhase>;
  // holdMs defaults to the standard 6s grace period; pass a longer one
  // (e.g. giving a freshly-created group more time to be noticed) to
  // override just the "visible" stage's length.
  beginHold: (id: string, holdMs?: number) => void;
  cancelHold: (id: string) => void;
}

const HoldOnCompleteContext = createContext<HoldOnCompleteContextValue | null>(null);

export function HoldOnCompleteProvider({ children }: { children: React.ReactNode }) {
  const [holds, setHolds] = useState<Map<string, HoldPhase>>(new Map());
  // Per-id pending timeouts, so a cancel (or a fresh re-check restarting
  // the clock) can clear exactly its own stage timers and no one else's.
  const timers = useRef(new Map<string, ReturnType<typeof setTimeout>[]>());

  const clearTimers = useCallback((id: string) => {
    for (const timer of timers.current.get(id) ?? []) clearTimeout(timer);
    timers.current.delete(id);
  }, []);

  const setPhase = useCallback((id: string, phase: HoldPhase | null) => {
    setHolds((prev) => {
      const next = new Map(prev);
      if (phase === null) next.delete(id);
      else next.set(id, phase);
      return next;
    });
  }, []);

  const beginHold = useCallback(
    (id: string, holdMs: number = HOLD_MS) => {
      clearTimers(id);
      setPhase(id, "visible");
      timers.current.set(id, [
        setTimeout(() => setPhase(id, "fading"), holdMs),
        setTimeout(() => setPhase(id, "collapsing"), holdMs + FADE_MS),
        setTimeout(() => setPhase(id, null), holdMs + FADE_MS + COLLAPSE_MS),
      ]);
    },
    [clearTimers, setPhase]
  );

  // Called both to catch an accidental-check undo mid-hold and, harmlessly,
  // on every ordinary uncheck of an already-settled item.
  const cancelHold = useCallback(
    (id: string) => {
      clearTimers(id);
      setPhase(id, null);
    },
    [clearTimers, setPhase]
  );

  // Belt-and-suspenders: drop any still-pending timers on unmount so they
  // don't fire setState against a gone provider.
  useEffect(() => {
    const pending = timers.current;
    return () => {
      for (const list of pending.values()) list.forEach(clearTimeout);
    };
  }, []);

  return (
    <HoldOnCompleteContext.Provider value={{ holds, beginHold, cancelHold }}>
      {children}
    </HoldOnCompleteContext.Provider>
  );
}

export function useHoldOnComplete() {
  const ctx = useContext(HoldOnCompleteContext);
  if (!ctx) throw new Error("useHoldOnComplete must be used within a HoldOnCompleteProvider");
  return ctx;
}
