// Exercise 3: a reusable "guarded context" factory
//
// See theory Section 6. Every context in a real codebase ends up needing the
// same boilerplate: createContext(undefined), a hook that throws a useful
// error if there's no Provider, and TypeScript narrowing so call sites never
// see `T | undefined`. Instead of repeating this per-context, build a
// factory that generates the pair.
//
// STEP 1: implement buildErrorMessage(hookName, providerName) — a PURE
//         function returning the exact error message string the guard
//         hook should throw.
// STEP 2: implement createRequiredContext<T>(providerName) which returns
//         { Context, useRequiredContext } where useRequiredContext throws
//         using buildErrorMessage when read outside its Provider.

import { createContext, useContext } from "react";

// STEP 1: pure — no React, no context object involved
function buildErrorMessage(hookName: string, providerName: string): string {
  // TODO: return `${hookName} must be used within a ${providerName}`
  return ""; // placeholder
}

interface RequiredContext<T> {
  Context: React.Context<T | undefined>;
  useRequiredContext: (hookName: string) => T;
}

// STEP 2: generic factory
function createRequiredContext<T>(providerName: string): RequiredContext<T> {
  const Context = createContext<T | undefined>(undefined);

  function useRequiredContext(hookName: string): T {
    const value = useContext(Context);
    // TODO: if value is undefined, throw new Error(buildErrorMessage(hookName, providerName))
    // TODO: otherwise return value (already narrowed to T by the check above)
    return value as T; // placeholder — bypasses the guard entirely
  }

  return { Context, useRequiredContext };
}

// --- usage: this is what call sites look like once the factory exists ---
interface Theme {
  mode: "light" | "dark";
}

const { Context: ThemeContext, useRequiredContext: useThemeInternal } =
  createRequiredContext<Theme>("ThemeProvider");

function useTheme(): Theme {
  return useThemeInternal("useTheme");
}

function ThemeProvider({ theme, children }: { theme: Theme; children: React.ReactNode }) {
  return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
}

function ThemedLabel() {
  const theme = useTheme(); // fully typed as Theme, no `| undefined` anywhere
  return <span>{theme.mode}</span>;
}

export { buildErrorMessage, createRequiredContext, ThemeProvider, ThemedLabel, useTheme };

// --- self-check: do not modify below this line ---
const msg = buildErrorMessage("useAuth", "AuthProvider");
const test1 = msg === "useAuth must be used within a AuthProvider" || msg === "useAuth must be used within an AuthProvider";
console.log(test1 ? "PASS (buildErrorMessage produces a clear message)" : "FAIL (buildErrorMessage produces a clear message)");

const { useRequiredContext } = createRequiredContext<{ n: number }>("NumberProvider");
let threw = false;
let thrownMessage = "";
try {
  // calling the hook logic OUTSIDE a component is not valid React, but the
  // guard's throwing behavior is exercised in the components test below;
  // this block only checks that buildErrorMessage was wired up correctly by
  // re-deriving the same message shape createRequiredContext should use
  thrownMessage = buildErrorMessage("useNumber", "NumberProvider");
} catch {
  threw = true;
}
const test2 = thrownMessage.includes("useNumber") && thrownMessage.includes("NumberProvider");
console.log(test2 ? "PASS (message names both the hook and the provider)" : "FAIL (message names both the hook and the provider)");

// Verify visually: render <ThemedLabel /> WITHOUT wrapping it in <ThemeProvider>.
// BEFORE finishing STEP 2, it silently renders with `value as T` bypassing
// the check (likely crashing later with a confusing "cannot read mode of
// undefined", or worse, rendering "undefined" silently). AFTER wiring the
// throw, React shows a clear error naming useTheme and ThemeProvider instead
// of an obscure downstream crash.
