// state.jsx — Git engine: a simplified but authentic model of git's data structures.
// Exposes: createInitialState, runCommand(state, input) => {state, output, events}
// Authoritative naming: working dir, staging (index), commits, refs, remote.

const { useState, useEffect, useRef, useCallback, useMemo } = React;

// ---------- ID helpers ----------
let __idCounter = 1;
const sha = () => {
  const chars = "0123456789abcdef";
  let s = "";
  for (let i = 0; i < 7; i++) s += chars[Math.floor(Math.random() * 16)];
  return s;
};

// ---------- Initial state ----------
function createInitialState() {
  return {
    initialized: false,                // git init has been run?
    workingDir: {},                    // { filename: { content, tracked, modified } }
    staging: {},                       // { filename: content }   (the "index")
    commits: {},                       // { sha: {sha, msg, parent, parent2, branch, tree, author, time} }
    branches: { main: null },          // { branchName: sha | null }
    HEAD: "main",                      // current branch name
    remote: {                          // GitHub
      exists: false,
      url: null,
      commits: {},                     // mirror of pushed commits
      branches: {},                    // pushed branch refs
      pullRequests: [],                // PRs
    },
    collaborator: {                    // simulated other dev "Riley"
      active: false,
      pendingCommits: [],              // commits Riley has made on remote
    },
    badges: [],
    log: [],                           // history of commands
  };
}

// ---------- Helpers ----------
const clone = (x) => JSON.parse(JSON.stringify(x));

function computeStatus(state) {
  const { workingDir, staging, commits, branches, HEAD } = state;
  const headSha = branches[HEAD];
  const headTree = headSha ? commits[headSha].tree : {};

  const untracked = [];
  const modified = [];
  const stagedNew = [];
  const stagedModified = [];
  const stagedDeleted = [];

  for (const name of Object.keys(workingDir)) {
    const wContent = workingDir[name].content;
    const sContent = staging[name];
    const cContent = headTree[name];
    if (sContent === undefined && cContent === undefined) {
      untracked.push(name);
    } else if (sContent !== undefined && sContent !== cContent && cContent !== undefined) {
      stagedModified.push(name);
    } else if (sContent !== undefined && cContent === undefined) {
      stagedNew.push(name);
    }
    if (sContent !== undefined && wContent !== sContent) {
      modified.push(name);
    } else if (sContent === undefined && cContent !== undefined && wContent !== cContent) {
      modified.push(name);
    }
  }
  // deletions: in commit/staging but not workdir
  for (const name of Object.keys(staging)) {
    if (!(name in workingDir)) stagedDeleted.push(name);
  }
  return { untracked, modified, stagedNew, stagedModified, stagedDeleted };
}

// ---------- Command parser & executor ----------
function runCommand(state, input) {
  const events = [];
  const log = (txt) => events.push({ type: "log", text: txt });
  const ok = (txt) => events.push({ type: "ok", text: txt });
  const err = (txt) => events.push({ type: "err", text: txt });
  const animate = (anim) => events.push({ type: "anim", ...anim });
  const badge = (id, name) => events.push({ type: "badge", id, name });

  const trimmed = input.trim();
  if (!trimmed) return { state, events };
  const tokens = trimmed.split(/\s+/);
  const next = clone(state);
  next.log.push(trimmed);

  // --- non-git utility commands ---
  if (tokens[0] === "ls") {
    const files = Object.keys(next.workingDir);
    if (files.length === 0) log("(empty directory)");
    else log(files.join("  "));
    return { state: next, events };
  }
  if (tokens[0] === "cat") {
    const f = tokens[1];
    if (!f) { err("usage: cat <file>"); return { state: next, events }; }
    if (!next.workingDir[f]) { err(`cat: ${f}: No such file`); return { state: next, events }; }
    log(next.workingDir[f].content);
    return { state: next, events };
  }
  if (tokens[0] === "echo") {
    // echo "text" > file  OR  echo "text" >> file
    const m = trimmed.match(/^echo\s+"([^"]*)"\s*(>{1,2})\s*(\S+)$/);
    if (m) {
      const [, content, op, fname] = m;
      if (op === ">") {
        next.workingDir[fname] = { content };
      } else {
        const cur = next.workingDir[fname]?.content ?? "";
        next.workingDir[fname] = { content: cur + (cur ? "\n" : "") + content };
      }
      ok(`wrote to ${fname}`);
      animate({ kind: "write", file: fname });
      return { state: next, events };
    }
    log(trimmed.replace(/^echo\s+/, ""));
    return { state: next, events };
  }
  if (tokens[0] === "touch") {
    const f = tokens[1];
    if (!f) { err("usage: touch <file>"); return { state: next, events }; }
    if (!next.workingDir[f]) next.workingDir[f] = { content: "" };
    ok(`created ${f}`);
    animate({ kind: "write", file: f });
    return { state: next, events };
  }
  if (tokens[0] === "rm") {
    const f = tokens[1];
    if (!next.workingDir[f]) { err(`rm: ${f}: No such file`); return { state: next, events }; }
    delete next.workingDir[f];
    ok(`removed ${f}`);
    return { state: next, events };
  }
  if (tokens[0] === "edit") {
    // edit <file> <new content>  -- helper for sandbox
    const f = tokens[1];
    const content = tokens.slice(2).join(" ");
    if (!next.workingDir[f]) next.workingDir[f] = { content: "" };
    next.workingDir[f].content = content;
    ok(`edited ${f}`);
    animate({ kind: "write", file: f });
    return { state: next, events };
  }
  if (tokens[0] === "clear") {
    return { state: next, events: [{ type: "clear" }] };
  }
  if (tokens[0] === "help") {
    log("Commands you can try:");
    log("  shell:  ls / cat <file> / echo \"...\" > file / touch / rm / clear");
    log("  git:    init, status, add, reset, commit -m \"...\", log");
    log("          branch, checkout, switch, merge");
    log("          remote add, push, pull, fetch");
    log("  github: gh pr create [branch] [target]");
    log("          gh pr merge <id>");
    log("          gh pr list");
    return { state: next, events };
  }

  // --- git ---
  if (tokens[0] !== "git") {
    err(`command not found: ${tokens[0]}  (try 'help')`);
    return { state: next, events };
  }

  const sub = tokens[1];
  if (!sub) { err("git: missing subcommand"); return { state: next, events }; }

  if (sub === "init") {
    if (next.initialized) { log("Reinitialized existing repository."); return { state: next, events }; }
    next.initialized = true;
    ok("Initialized empty Git repository.");
    log("Created hidden .git folder. This is where Git tracks everything.");
    animate({ kind: "init" });
    badge("init", "First Repository");
    return { state: next, events };
  }

  if (!next.initialized) {
    err("Not a git repository. Run 'git init' first.");
    return { state: next, events };
  }

  if (sub === "status") {
    const s = computeStatus(next);
    log(`On branch ${next.HEAD}`);
    if (next.remote.exists) {
      const localHead = next.branches[next.HEAD];
      const remoteHead = next.remote.branches[next.HEAD];
      if (localHead && localHead !== remoteHead) log(`Your branch is ahead of 'origin/${next.HEAD}'.`);
    }
    if (s.stagedNew.length || s.stagedModified.length || s.stagedDeleted.length) {
      log("Changes to be committed:");
      s.stagedNew.forEach(f => ok(`  new file:   ${f}`));
      s.stagedModified.forEach(f => ok(`  modified:   ${f}`));
      s.stagedDeleted.forEach(f => ok(`  deleted:    ${f}`));
    }
    if (s.modified.length) {
      log("Changes not staged for commit:");
      s.modified.forEach(f => err(`  modified:   ${f}`));
    }
    if (s.untracked.length) {
      log("Untracked files:");
      s.untracked.forEach(f => err(`  ${f}`));
    }
    if (!s.untracked.length && !s.modified.length && !s.stagedNew.length && !s.stagedModified.length && !s.stagedDeleted.length) {
      ok("nothing to commit, working tree clean");
    }
    return { state: next, events };
  }

  if (sub === "add") {
    const target = tokens[2];
    if (!target) { err("usage: git add <file> | git add ."); return { state: next, events }; }
    const filesToAdd = target === "."
      ? Object.keys(next.workingDir)
      : [target];
    let added = 0;
    for (const f of filesToAdd) {
      if (!next.workingDir[f]) { err(`pathspec '${f}' did not match any files`); continue; }
      next.staging[f] = next.workingDir[f].content;
      animate({ kind: "stage", file: f });
      added++;
    }
    if (added > 0) {
      ok(`Staged ${added} file${added > 1 ? "s" : ""}.`);
      badge("first-stage", "Stager");
    }
    return { state: next, events };
  }

  if (sub === "reset") {
    // git reset <file>  -- unstage
    const f = tokens[2];
    if (!f) {
      next.staging = {};
      ok("Unstaged everything.");
      return { state: next, events };
    }
    delete next.staging[f];
    ok(`Unstaged ${f}.`);
    return { state: next, events };
  }

  if (sub === "commit") {
    // git commit -m "message"
    const m = trimmed.match(/^git\s+commit\s+-m\s+"([^"]+)"$/);
    if (!m) { err('usage: git commit -m "your message"'); return { state: next, events }; }
    const msg = m[1];
    if (Object.keys(next.staging).length === 0) {
      const headSha = next.branches[next.HEAD];
      const headTree = headSha ? next.commits[headSha].tree : {};
      // check if staging matches head exactly = nothing to commit
      err("nothing to commit (no files staged). Try 'git add <file>' first.");
      return { state: next, events };
    }
    // Build new tree from parent tree + staging changes
    const parentSha = next.branches[next.HEAD];
    const parentTree = parentSha ? next.commits[parentSha].tree : {};
    const newTree = { ...parentTree, ...next.staging };
    const id = sha();
    next.commits[id] = {
      sha: id,
      msg,
      parent: parentSha,
      parent2: null,
      branch: next.HEAD,
      tree: newTree,
      author: "you",
      time: Date.now(),
    };
    next.branches[next.HEAD] = id;
    next.staging = {};
    ok(`[${next.HEAD} ${id}] ${msg}`);
    animate({ kind: "commit", sha: id, branch: next.HEAD });
    if (Object.keys(next.commits).length === 1) badge("first-commit", "First Commit!");
    if (Object.keys(next.commits).length === 5) badge("five-commits", "Committed");
    return { state: next, events };
  }

  if (sub === "log") {
    let cur = next.branches[next.HEAD];
    if (!cur) { log("(no commits yet)"); return { state: next, events }; }
    const seen = new Set();
    while (cur && !seen.has(cur)) {
      seen.add(cur);
      const c = next.commits[cur];
      log(`◉ ${c.sha}  ${c.msg}  (${c.branch})`);
      cur = c.parent;
    }
    return { state: next, events };
  }

  if (sub === "branch") {
    const name = tokens[2];
    if (!name) {
      // list
      Object.keys(next.branches).forEach(b => {
        log(`${b === next.HEAD ? "* " : "  "}${b}`);
      });
      return { state: next, events };
    }
    if (next.branches[name] !== undefined) { err(`branch '${name}' already exists`); return { state: next, events }; }
    next.branches[name] = next.branches[next.HEAD];
    ok(`Created branch '${name}'.`);
    badge("first-branch", "Brancher");
    return { state: next, events };
  }

  if (sub === "checkout" || sub === "switch") {
    let name = tokens[2];
    let createNew = false;
    if (name === "-b") {
      createNew = true;
      name = tokens[3];
    }
    if (!name) { err(`usage: git ${sub} <branch>`); return { state: next, events }; }
    if (createNew) {
      if (next.branches[name] !== undefined) { err(`branch '${name}' already exists`); return { state: next, events }; }
      next.branches[name] = next.branches[next.HEAD];
    }
    if (next.branches[name] === undefined) { err(`branch '${name}' does not exist`); return { state: next, events }; }
    next.HEAD = name;
    // Update working dir to match new branch's tree
    const headSha = next.branches[name];
    const tree = headSha ? next.commits[headSha].tree : {};
    next.workingDir = {};
    Object.entries(tree).forEach(([f, content]) => {
      next.workingDir[f] = { content };
    });
    next.staging = {};
    ok(`Switched to ${createNew ? "a new branch '" : "branch '"}${name}'`);
    animate({ kind: "checkout", branch: name });
    return { state: next, events };
  }

  if (sub === "merge") {
    const name = tokens[2];
    if (!name) { err("usage: git merge <branch>"); return { state: next, events }; }
    if (next.branches[name] === undefined) { err(`branch '${name}' does not exist`); return { state: next, events }; }
    if (name === next.HEAD) { err("can't merge a branch into itself"); return { state: next, events }; }
    const ours = next.branches[next.HEAD];
    const theirs = next.branches[name];
    if (!theirs) { err(`branch '${name}' has no commits`); return { state: next, events }; }
    if (!ours) {
      // fast-forward
      next.branches[next.HEAD] = theirs;
      ok(`Fast-forward to '${name}'.`);
      return { state: next, events };
    }
    // detect conflicts: same file modified differently from common ancestor
    const oursTree = next.commits[ours].tree;
    const theirsTree = next.commits[theirs].tree;
    const ancestor = findCommonAncestor(next, ours, theirs);
    const ancestorTree = ancestor ? next.commits[ancestor].tree : {};

    const conflicts = [];
    const merged = { ...ancestorTree };
    const allFiles = new Set([...Object.keys(oursTree), ...Object.keys(theirsTree)]);
    for (const f of allFiles) {
      const a = ancestorTree[f];
      const o = oursTree[f];
      const t = theirsTree[f];
      if (o === t) merged[f] = o;
      else if (a === o) merged[f] = t;       // only theirs changed
      else if (a === t) merged[f] = o;       // only ours changed
      else { conflicts.push(f); merged[f] = `<<<<<<< HEAD\n${o ?? ""}\n=======\n${t ?? ""}\n>>>>>>> ${name}`; }
    }
    if (conflicts.length) {
      // Apply conflict markers to working dir, await resolution
      conflicts.forEach(f => {
        next.workingDir[f] = { content: merged[f], conflict: true };
      });
      // staging gets resolved files (non-conflicts only auto-staged in real git? for simulation, we put them in workdir).
      err(`CONFLICT (content): Merge conflict in ${conflicts.join(", ")}`);
      log("Automatic merge failed; fix conflicts and then commit the result.");
      log("Edit each file to resolve, then 'git add <file>' and 'git commit -m \"merge\"'");
      next.mergeInProgress = { branch: name, ours, theirs };
      animate({ kind: "conflict", files: conflicts });
      return { state: next, events };
    }
    // clean merge — make merge commit
    const id = sha();
    next.commits[id] = {
      sha: id,
      msg: `Merge branch '${name}'`,
      parent: ours,
      parent2: theirs,
      branch: next.HEAD,
      tree: merged,
      author: "you",
      time: Date.now(),
    };
    next.branches[next.HEAD] = id;
    Object.entries(merged).forEach(([f, content]) => {
      next.workingDir[f] = { content };
    });
    ok(`Merge made by recursive strategy.`);
    animate({ kind: "merge", sha: id, branch: next.HEAD, fromBranch: name });
    badge("first-merge", "Merger");
    return { state: next, events };
  }

  if (sub === "remote") {
    if (tokens[2] === "add" && tokens[3] === "origin") {
      const url = tokens[4];
      next.remote.exists = true;
      next.remote.url = url || "https://github.com/you/project.git";
      ok(`Added remote 'origin' → ${next.remote.url}`);
      animate({ kind: "remote-add" });
      badge("remote", "Remote Connected");
      return { state: next, events };
    }
    if (tokens[2] === "-v" || !tokens[2]) {
      if (next.remote.exists) log(`origin\t${next.remote.url} (fetch/push)`);
      else log("(no remote configured)");
      return { state: next, events };
    }
    err(`unknown remote subcommand: ${tokens.slice(2).join(" ")}`);
    return { state: next, events };
  }

  if (sub === "push") {
    if (!next.remote.exists) { err("no remote configured. Try 'git remote add origin <url>'"); return { state: next, events }; }
    const branch = tokens[3] || next.HEAD;
    const localSha = next.branches[branch];
    if (!localSha) { err(`branch '${branch}' has no commits`); return { state: next, events }; }
    // walk down and copy commits not on remote
    const toPush = [];
    let cur = localSha;
    while (cur && !next.remote.commits[cur]) {
      toPush.push(cur);
      cur = next.commits[cur].parent;
    }
    toPush.reverse().forEach(s => {
      next.remote.commits[s] = clone(next.commits[s]);
    });
    next.remote.branches[branch] = localSha;
    ok(`Pushed ${toPush.length} commit${toPush.length === 1 ? "" : "s"} to origin/${branch}.`);
    animate({ kind: "push", count: toPush.length });
    badge("first-push", "Pusher");
    return { state: next, events };
  }

  if (sub === "pull" || sub === "fetch") {
    if (!next.remote.exists) { err("no remote configured"); return { state: next, events }; }
    // bring remote commits into local
    let pulled = 0;
    Object.entries(next.remote.commits).forEach(([s, c]) => {
      if (!next.commits[s]) { next.commits[s] = clone(c); pulled++; }
    });
    Object.entries(next.remote.branches).forEach(([b, s]) => {
      if (next.branches[b] === undefined) next.branches[b] = s;
      else if (b === next.HEAD && next.branches[b] !== s) {
        // need to merge if diverged
        next.branches[b] = s; // simplified: fast-forward
        const tree = next.commits[s].tree;
        next.workingDir = {};
        Object.entries(tree).forEach(([f, content]) => {
          next.workingDir[f] = { content };
        });
      }
    });
    ok(`Fetched ${pulled} new commit${pulled === 1 ? "" : "s"} from origin.`);
    animate({ kind: "pull", count: pulled });
    return { state: next, events };
  }

  if (sub === "clone") {
    log("In this playground you build from scratch \u2014 try 'git init' to begin a new repo.");
    return { state: next, events };
  }

  err(`git: '${sub}' is not a recognized command. Try 'help'.`);
  return { state: next, events };
}

function findCommonAncestor(state, a, b) {
  const ancestorsA = new Set();
  let cur = a;
  while (cur) { ancestorsA.add(cur); cur = state.commits[cur].parent; }
  cur = b;
  while (cur) {
    if (ancestorsA.has(cur)) return cur;
    cur = state.commits[cur].parent;
  }
  return null;
}

// ---------- PR / collaborator commands handled outside git ----------
function runPRCommand(state, input) {
  const events = [];
  const next = clone(state);
  const tokens = input.trim().split(/\s+/);

  // ----- gh CLI alias layer -----
  // `gh pr create [branch] [target]`  -> pr open
  // `gh pr merge <id>`                -> pr merge
  // `gh pr list`                      -> pr list
  if (tokens[0] === "gh" && tokens[1] === "pr") {
    const sub = tokens[2];
    if (sub === "create") {
      return runPRCommand(state, `pr open ${tokens.slice(3).join(" ")}`.trim());
    }
    if (sub === "merge") {
      return runPRCommand(state, `pr merge ${tokens.slice(3).join(" ")}`.trim());
    }
    if (sub === "list" || sub === "ls" || !sub) {
      return runPRCommand(state, "pr list");
    }
    events.push({ type: "err", text: `gh pr: unknown subcommand '${sub}'. Try: create, merge, list.` });
    return { state: next, events };
  }
  if (tokens[0] === "gh") {
    events.push({ type: "err", text: `gh: only 'gh pr create | merge | list' are wired up in the playground.` });
    return { state: next, events };
  }

  if (tokens[0] === "pr") {
    if (tokens[1] === "open") {
      const branch = tokens[2] || next.HEAD;
      if (!next.remote.branches[branch]) {
        events.push({ type: "err", text: `Branch '${branch}' isn't on the remote yet. Push it first: git push origin ${branch}` });
        return { state: next, events };
      }
      const targetBranch = tokens[3] || "main";
      const id = next.remote.pullRequests.length + 1;
      next.remote.pullRequests.push({
        id, branch, target: targetBranch, status: "open",
        title: `Merge ${branch} into ${targetBranch}`,
        author: "you",
      });
      events.push({ type: "ok", text: `Opened PR #${id}: ${branch} → ${targetBranch}` });
      events.push({ type: "anim", kind: "pr-open", id });
      events.push({ type: "badge", id: "first-pr", name: "PR Author" });
      return { state: next, events };
    }
    if (tokens[1] === "merge") {
      const id = parseInt(tokens[2]);
      const pr = next.remote.pullRequests.find(p => p.id === id);
      if (!pr) { events.push({ type: "err", text: `PR #${id} not found` }); return { state: next, events }; }
      if (pr.status !== "open") { events.push({ type: "err", text: `PR #${id} is already ${pr.status}` }); return { state: next, events }; }
      pr.status = "merged";
      // merge remote branch into target
      next.remote.branches[pr.target] = next.remote.branches[pr.branch];
      events.push({ type: "ok", text: `Merged PR #${id}.` });
      events.push({ type: "anim", kind: "pr-merge", id });
      events.push({ type: "badge", id: "pr-merger", name: "PR Merger" });
      return { state: next, events };
    }
    if (tokens[1] === "list") {
      if (!next.remote.pullRequests.length) events.push({ type: "log", text: "(no PRs)" });
      next.remote.pullRequests.forEach(p => {
        events.push({ type: "log", text: `#${p.id} [${p.status}] ${p.title}` });
      });
      return { state: next, events };
    }
  }
  if (tokens[0] === "collab") {
    if (tokens[1] === "on") {
      next.collaborator.active = true;
      events.push({ type: "ok", text: "Collaborator 'Riley' is now active." });
      events.push({ type: "log", text: "Riley can edit files and push commits to the remote." });
      return { state: next, events };
    }
    if (tokens[1] === "edit") {
      const f = tokens[2];
      const content = tokens.slice(3).join(" ");
      if (!f) { events.push({ type: "err", text: "usage: collab edit <file> <content>" }); return { state: next, events }; }
      // Riley edits on remote: create a pending commit on top of remote main
      const pCommit = {
        sha: sha(),
        msg: `Riley: update ${f}`,
        file: f,
        content,
      };
      next.collaborator.pendingCommits.push(pCommit);
      events.push({ type: "ok", text: `Riley edited ${f} (pending push).` });
      return { state: next, events };
    }
    if (tokens[1] === "push") {
      if (!next.remote.exists) { events.push({ type: "err", text: "remote doesn't exist yet" }); return { state: next, events }; }
      // Defensive: if main has never been pushed, Riley seeds it.
      let parent = next.remote.branches.main ?? null;
      let count = 0;
      next.collaborator.pendingCommits.forEach(pc => {
        const parentTree = (parent && next.remote.commits[parent]) ? next.remote.commits[parent].tree : {};
        const tree = { ...parentTree, [pc.file]: pc.content };
        next.remote.commits[pc.sha] = {
          sha: pc.sha, msg: pc.msg, parent, parent2: null,
          branch: "main", tree, author: "Riley", time: Date.now(),
        };
        parent = pc.sha;
        count++;
      });
      next.remote.branches.main = parent;
      next.collaborator.pendingCommits = [];
      events.push({ type: "ok", text: `Riley pushed ${count} commit${count === 1 ? "" : "s"} to origin/main.` });
      events.push({ type: "anim", kind: "collab-push", count });
      return { state: next, events };
    }
  }
  return null; // not a PR/collab command
}

function runAny(state, input) {
  const pr = runPRCommand(state, input);
  if (pr) return pr;
  return runCommand(state, input);
}

// ---------- Chapter seeding ----------
// Builds the baseline state a chapter expects, by replaying the prior
// chapters' commands. This lets students jump directly to any chapter via
// the top nav and still have a coherent repo, remote, history, etc.
function seedForChapter(idx) {
  const runSeq = (state, cmds) => cmds.reduce((acc, c) => runAny(acc, c).state, state);
  let s = createInitialState();

  // Chapters 0 (intro) and 1 (init): start from a blank slate.
  if (idx <= 1) return s;

  // Chapter 2 (three areas): initialised repo with one untracked file.
  s = runSeq(s, [
    "git init",
    'echo "# Robotics Club" > README.md',
  ]);
  if (idx === 2) return s;

  // Chapter 3 (push to GitHub): a couple of local commits, no remote yet.
  s = runSeq(s, [
    "git add README.md",
    'git commit -m "first commit"',
    'echo "We build robots." >> README.md',
    "git add README.md",
    'git commit -m "describe the club"',
  ]);
  if (idx === 3) return s;

  // Chapter 4 (branches): remote configured and main pushed.
  s = runSeq(s, [
    "git remote add origin https://github.com/you/robotics.git",
    "git push origin main",
  ]);
  if (idx === 4) return s;

  // Chapter 5 (PRs): same baseline as branches.
  if (idx === 5) return s;

  // Chapter 6 (merge conflicts): same baseline; README is on remote so Riley
  // has something to fork. The auto-fired chapter steps will handle Riley.
  if (idx === 6) return s;

  // Chapter 7 (sandbox): full playground with a merged feature branch.
  s = runSeq(s, [
    "git checkout -b add-team-page",
    'echo "Team: Maya, You" > team.md',
    "git add team.md",
    'git commit -m "add team page"',
    "git checkout main",
    "git merge add-team-page",
    "git push origin main",
  ]);
  return s;
}

// Expose
Object.assign(window, {
  createInitialState,
  runAny,
  computeStatus,
  findCommonAncestor,
  seedForChapter,
});
