// Exercise 1: state/dispatch split — stop dispatch-only consumers from
// re-rendering on every state change
//
// See theory Section 5. TodoApp below currently puts `todos` AND `dispatch`
// in ONE context, so `AddTodoButton` (which only calls dispatch, never reads
// todos) re-renders every time the list changes.
//
// STEP 1: implement todoReducer(state, action) — a PURE reducer function.
//         action shapes: { type: "add", text: string }
//                         { type: "toggle", id: number }
// STEP 2: split SingleTodoContext into TodoStateContext (todos array) and
//         TodoDispatchContext (dispatch function only)
// STEP 3: update useTodoState / useTodoDispatch to read from the split
//         contexts instead of the single combined one
// STEP 4: order the providers so the STABLE one (dispatch) is OUTERMOST,
//         matching theory Section 5

import { createContext, useContext, useReducer } from "react";

interface Todo {
  id: number;
  text: string;
  done: boolean;
}

type Action = { type: "add"; text: string } | { type: "toggle"; id: number };

// STEP 1: pure reducer — no React here at all
function todoReducer(state: Todo[], action: Action): Todo[] {
  // TODO: handle "add" -> append { id: state.length, text, done: false }
  // TODO: handle "toggle" -> flip `done` on the todo whose id matches
  return state; // placeholder — replace
}

// TODO STEP 2: delete this single combined context and replace it with
// TodoStateContext = createContext<Todo[]>([])
// TodoDispatchContext = createContext<React.Dispatch<Action> | undefined>(undefined)
const SingleTodoContext = createContext<
  { todos: Todo[]; dispatch: React.Dispatch<Action> } | undefined
>(undefined);

function TodoProvider({ children }: { children: React.ReactNode }) {
  const [todos, dispatch] = useReducer(todoReducer, []);
  // TODO STEP 4: nest TodoDispatchContext.Provider OUTSIDE TodoStateContext.Provider
  return (
    <SingleTodoContext.Provider value={{ todos, dispatch }}>
      {children}
    </SingleTodoContext.Provider>
  );
}

// TODO STEP 3: split into two hooks reading from the two separate contexts
function useTodoState(): Todo[] {
  const ctx = useContext(SingleTodoContext);
  if (!ctx) throw new Error("useTodoState must be used within TodoProvider");
  return ctx.todos;
}

function useTodoDispatch(): React.Dispatch<Action> {
  const ctx = useContext(SingleTodoContext);
  if (!ctx) throw new Error("useTodoDispatch must be used within TodoProvider");
  return ctx.dispatch;
}

function TodoList() {
  const todos = useTodoState();
  return (
    <ul>
      {todos.map((t) => (
        <li key={t.id}>{t.done ? "[x]" : "[ ]"} {t.text}</li>
      ))}
    </ul>
  );
}

// Once the split is done, this component should NEVER re-render when the
// list changes — it only ever reads the dispatch function.
function AddTodoButton() {
  const dispatch = useTodoDispatch();
  return <button onClick={() => dispatch({ type: "add", text: "new" })}>Add</button>;
}

export { TodoProvider, TodoList, AddTodoButton, todoReducer };

// --- self-check: do not modify below this line ---
const afterAdd = todoReducer([], { type: "add", text: "Buy milk" });
const test1 = afterAdd.length === 1 && afterAdd[0].text === "Buy milk" && afterAdd[0].done === false;
console.log(test1 ? "PASS (add appends an undone todo)" : "FAIL (add appends an undone todo)");

const afterToggle = todoReducer(
  [{ id: 0, text: "Buy milk", done: false }],
  { type: "toggle", id: 0 },
);
const test2 = afterToggle[0].done === true && afterToggle[0].text === "Buy milk";
const test3 = afterToggle !== afterAdd; // reducer must return a NEW array, never mutate in place
console.log(test2 ? "PASS (toggle flips done)" : "FAIL (toggle flips done)");
console.log(test3 ? "PASS (reducer is pure — new reference)" : "FAIL (reducer is pure — new reference)");

// Verify visually: render <TodoProvider><TodoList /><AddTodoButton /></TodoProvider>,
// add a console.log at the top of AddTodoButton's body. BEFORE splitting the
// context, clicking "Add" logs from AddTodoButton too (expected, it triggered
// the click) but ALSO from every OTHER dispatch-only consumer you add. AFTER
// splitting into TodoStateContext/TodoDispatchContext with the dispatch
// provider outermost, a component that calls ONLY useTodoDispatch never
// re-renders no matter how many times the list changes.
