const PLANET_SORT_ROUNDS = [
  {
    prompt: "Tap the biggest planet.",
    fact: "Jupiter is the biggest planet in our solar system.",
    choices: ["mercury", "earth", "jupiter"],
    answer: "jupiter",
  },
  {
    prompt: "Tap the smallest planet.",
    fact: "Mercury is the smallest planet.",
    choices: ["venus", "mars", "mercury"],
    answer: "mercury",
  },
  {
    prompt: "Tap the biggest ice giant.",
    fact: "Uranus and Neptune are ice giants. Uranus is a little wider.",
    choices: ["earth", "uranus", "neptune"],
    answer: "uranus",
  },
  {
    prompt: "Tap the planet with the widest rings.",
    fact: "Saturn's bright rings are made of ice and rock.",
    choices: ["mars", "saturn", "venus"],
    answer: "saturn",
  },
  {
    prompt: "Tap the closest planet to the Sun.",
    fact: "Mercury orbits closest to the Sun.",
    choices: ["earth", "mercury", "neptune"],
    answer: "mercury",
  },
  {
    prompt: "Tap the farthest planet from the Sun.",
    fact: "Neptune is the farthest planet in our solar system.",
    choices: ["mars", "jupiter", "neptune"],
    answer: "neptune",
  },
];

const GRAVITY_JUMP_WORLDS = [
  {
    id: "moon",
    label: "Moon",
    short: "Moon",
    x: 50,
    bg: GENERATED_ASSETS.gravityBgMoon,
    bgPosition: "center center",
    jump: 235,
    duration: 1.55,
    pull: 0.17,
    note: "Moon jump! Less gravity makes a slow, high float.",
  },
  {
    id: "earth",
    label: "Earth",
    short: "Earth",
    x: 50,
    bg: GENERATED_ASSETS.gravityBgEarth,
    bgPosition: "left center",
    jump: 105,
    duration: 0.82,
    pull: 1,
    note: "Earth jump! Strong gravity pulls Adam down faster.",
  },
  {
    id: "mars",
    label: "Mars",
    short: "Mars",
    x: 50,
    bg: GENERATED_ASSETS.gravityBgMars,
    bgPosition: "center center",
    jump: 165,
    duration: 1.18,
    pull: 0.38,
    note: "Mars jump! Mars pulls less than Earth, so Adam hops higher.",
  },
  {
    id: "jupiter",
    label: "Big planet",
    short: "Big",
    x: 50,
    bg: GENERATED_ASSETS.gravityBgBig,
    bgPosition: "center center",
    jump: 58,
    duration: 0.7,
    pull: 2.53,
    note: "Big planet jump! Strong gravity makes a tiny hop.",
  },
];

const ASTEROID_DODGE_PATHS = [18, 66, 42, 78, 29, 58, 12, 84];

function clampChoiceIndex(index, length) {
  if (!length) return 0;
  const safeIndex = Number.isFinite(index) ? index : 0;
  return Math.max(0, Math.min(length - 1, safeIndex));
}

function nextChoiceIndex(index, delta, length) {
  return clampChoiceIndex(index + delta, length);
}

function choiceStatusText(index, choices, getLabel) {
  const safeIndex = clampChoiceIndex(index, choices.length);
  const choice = choices[safeIndex];
  if (!choice) return "";
  return `${safeIndex + 1} of ${choices.length}: ${getLabel(choice)}`;
}

function choiceSelectionState(index, choices, getLabel) {
  const safeIndex = clampChoiceIndex(index, choices.length);
  const choice = choices[safeIndex] || null;
  return {
    index: safeIndex,
    choice,
    statusText: choiceStatusText(safeIndex, choices, getLabel),
  };
}

function PlanetSortLevel({ onExit }) {
  const [roundIndex, setRoundIndex] = useState(0);
  const [message, setMessage] = useState({
    tone: "ready",
    text: "Look at the planet sizes.",
  });
  const [pickedId, setPickedId] = useState(null);
  const [missCount, setMissCount] = useState(0);
  const [finished, setFinished] = useState(false);
  const [choiceIndex, setChoiceIndex] = useState(0);
  const feedbackTimerRef = useRef(null);
  const round = PLANET_SORT_ROUNDS[roundIndex];
  const choices = round.choices
    .map((id) => PLANETS.find((planet) => planet.id === id))
    .filter(Boolean);
  const maxRadius = choices.length
    ? Math.max(...choices.map((planet) => planet.radius))
    : 1;
  const answerPlanet = choices.find((planet) => planet.id === round.answer);
  const selectedChoice = choiceSelectionState(
    choiceIndex,
    choices,
    (planet) => planet.name,
  );

  useEffect(() => {
    return () => window.clearTimeout(feedbackTimerRef.current);
  }, []);
  useEffect(() => {
    setChoiceIndex(0);
  }, [roundIndex]);
  useEffect(() => {
    setChoiceIndex((index) => clampChoiceIndex(index, choices.length));
  }, [choices.length]);

  const choose = (planet) => {
    if (!planet) return;
    if (finished) return;
    window.clearTimeout(feedbackTimerRef.current);
    setPickedId(planet.id);
    if (planet.id !== round.answer) {
      const nextMissCount = missCount + 1;
      setMissCount(nextMissCount);
      setMessage({
        tone: "try",
        text:
          nextMissCount >= 1 && answerPlanet
            ? `Good tap. Now try ${answerPlanet.name}.`
            : `${planet.name} is not this one. Try again.`,
      });
      playKidSound("pop");
      feedbackTimerRef.current = window.setTimeout(() => setPickedId(null), 900);
      return;
    }

    playKidSound("chime");
    setMissCount(0);
    const lastRound = roundIndex >= PLANET_SORT_ROUNDS.length - 1;
    setMessage({
      tone: "good",
      text: `${planet.name}! ${round.fact}`,
    });
    if (lastRound) {
      setFinished(true);
      return;
    }
    feedbackTimerRef.current = window.setTimeout(() => {
      setRoundIndex((i) => i + 1);
      setPickedId(null);
      setMissCount(0);
      setMessage({
        tone: "ready",
        text: "Nice! Try the next one.",
      });
    }, 950);
  };

  const reset = () => {
    setRoundIndex(0);
    setFinished(false);
    setPickedId(null);
    setMissCount(0);
    setMessage({ tone: "ready", text: "Look at the planet sizes." });
  };

  useWindowKeyHandler(
    (event) => {
      if (!choices.length || finished) return;
      if (event.key === "ArrowLeft") {
        event.preventDefault();
        setChoiceIndex((index) => nextChoiceIndex(index, -1, choices.length));
      } else if (event.key === "ArrowRight") {
        event.preventDefault();
        setChoiceIndex((index) => nextChoiceIndex(index, 1, choices.length));
      } else if (event.key === " " || event.key === "Enter") {
        event.preventDefault();
        choose(selectedChoice.choice);
      }
    },
    !finished,
  );

  return (
    <section className="planet-sort-level" aria-label="Planet sorting game">
      <Starfield count={220} />
      <button className="planet-sort-back" onClick={onExit}>
        ← Back to planets
      </button>
      <div className="planet-sort-shell">
        <div className="planet-sort-copy">
          <span>Planet Sort</span>
          <h1>{finished ? "You sorted space!" : round.prompt}</h1>
          <p>
            {finished
              ? "Big planets have stronger gravity because they hold more stuff."
              : "Planets are different sizes. Bigger worlds pull with more gravity."}
          </p>
          <div
            className={`planet-sort-message ${message.tone}`}
            role="status"
            aria-live="polite"
          >
            {finished ? (
              <SafeAssetImage
                src={GENERATED_ASSETS.sparkleStar}
                alt=""
                context="PlanetSort:finishSparkle"
                fallbackText=""
              />
            ) : null}
            <strong>{message.text}</strong>
          </div>
          <div className="planet-sort-progress" aria-label="Rounds">
            {PLANET_SORT_ROUNDS.map((item, index) => (
              <span
                key={`${item.answer}-${index}`}
                className={
                  finished ||
                  index < roundIndex ||
                  (index === roundIndex && pickedId === round.answer)
                    ? "on"
                    : ""
                }
              />
            ))}
          </div>
          {finished ? (
            <div className="planet-sort-actions">
              <button onClick={reset}>Play again</button>
              <button onClick={onExit}>Done</button>
            </div>
          ) : null}
        </div>
        <div className="planet-sort-choices">
          <span className="game-keyboard-status" aria-live="polite">
            {selectedChoice.statusText}
          </span>
          {choices.map((planet, index) => {
            const visualSize = 88 + (planet.radius / maxRadius) * 108;
            return (
              <button
                key={planet.id}
                className={
                  "planet-sort-card " +
                  (selectedChoice.index === index ? "selected " : "") +
                  (pickedId === planet.id
                    ? planet.id === round.answer
                      ? "correct"
                      : "wrong"
                    : missCount > 0 && planet.id === round.answer
                      ? "hint"
                    : "")
                }
                onClick={() => choose(planet)}
                onFocus={() => setChoiceIndex(clampChoiceIndex(index, choices.length))}
                aria-current={selectedChoice.index === index ? "true" : undefined}
                disabled={finished}
              >
                <span className="planet-sort-planet-wrap">
                  <Planet3D
                    planet={planet}
                    detail={false}
                    allowOrbit={false}
                    className="planet-sort-planet"
                    style={{
                      width: `${visualSize}px`,
                      height: `${visualSize}px`,
                    }}
                  />
                </span>
                <strong>{planet.name}</strong>
                <small>{planet.type.split(" · ")[0]}</small>
              </button>
            );
          })}
        </div>
      </div>
    </section>
  );
}

const ENGINE_MATCH_ROUNDS = [
  {
    prompt: "What holds the fuel?",
    fact: "Fuel tanks carry the stuff rockets burn.",
    answer: "fuelTank",
    choices: ["fuelTank", "window", "badge"],
  },
  {
    prompt: "What lights the fuel?",
    fact: "An igniter starts the engine burn.",
    answer: "igniter",
    choices: ["antenna", "igniter", "fins"],
  },
  {
    prompt: "What pushes fire down?",
    fact: "The engine bell sends hot gas down so the rocket goes up.",
    answer: "engine",
    choices: ["engine", "nose", "fuelPipes"],
  },
];

function EngineMatchLevel({ onExit }) {
  const partsById = React.useMemo(
    () =>
      Object.fromEntries(ROCKET_PARTS.map((part) => [part.id, part])),
    [],
  );
  const [roundIndex, setRoundIndex] = useState(0);
  const [message, setMessage] = useState("Find the matching rocket part.");
  const [pickedId, setPickedId] = useState(null);
  const [needsHint, setNeedsHint] = useState(false);
  const [done, setDone] = useState(false);
  const [choiceIndex, setChoiceIndex] = useState(0);
  const feedbackTimerRef = useRef(null);
  const round = ENGINE_MATCH_ROUNDS[roundIndex];
  const choices = round.choices.map((id) => partsById[id]).filter(Boolean);
  const answerPart = partsById[round.answer];
  const selectedChoice = choiceSelectionState(
    choiceIndex,
    choices,
    (part) => part.label,
  );

  useEffect(() => {
    return () => window.clearTimeout(feedbackTimerRef.current);
  }, []);
  useEffect(() => {
    setChoiceIndex(0);
  }, [roundIndex]);
  useEffect(() => {
    setChoiceIndex((index) => clampChoiceIndex(index, choices.length));
  }, [choices.length]);

  const choose = (part) => {
    if (!part) return;
    if (done) return;
    window.clearTimeout(feedbackTimerRef.current);
    setPickedId(part.id);
    if (part.id !== round.answer) {
      setNeedsHint(true);
      setMessage(
        answerPart
          ? `Good try. Tap ${answerPart.label} for this clue.`
          : `${part.label} is useful. Try another part.`,
      );
      playKidSound("pop");
      feedbackTimerRef.current = window.setTimeout(() => setPickedId(null), 900);
      return;
    }
    playKidSound("chime");
    setNeedsHint(false);
    setMessage(`${part.label}! ${round.fact}`);
    if (roundIndex >= ENGINE_MATCH_ROUNDS.length - 1) {
      setDone(true);
      return;
    }
    feedbackTimerRef.current = window.setTimeout(() => {
      setRoundIndex((i) => i + 1);
      setPickedId(null);
      setNeedsHint(false);
      setMessage("Good match! Try the next clue.");
    }, 950);
  };

  const reset = () => {
    setRoundIndex(0);
    setDone(false);
    setPickedId(null);
    setNeedsHint(false);
    setMessage("Find the matching rocket part.");
  };

  useWindowKeyHandler(
    (event) => {
      if (!choices.length || done) return;
      if (event.key === "ArrowLeft") {
        event.preventDefault();
        setChoiceIndex((index) => nextChoiceIndex(index, -1, choices.length));
      } else if (event.key === "ArrowRight") {
        event.preventDefault();
        setChoiceIndex((index) => nextChoiceIndex(index, 1, choices.length));
      } else if (event.key === " " || event.key === "Enter") {
        event.preventDefault();
        choose(selectedChoice.choice);
      }
    },
    !done,
  );

  return (
    <section className="engine-match-level" aria-label="Rocket engine matching game">
      <Starfield count={220} />
      <button className="planet-sort-back" onClick={onExit}>
        ← Back to planets
      </button>
      <div className="engine-match-shell">
        <div className="engine-match-rocket" aria-hidden="true">
          <SafeAssetImage
            src={GENERATED_ASSETS.rocket}
            alt=""
            context="EngineMatch:rocket"
            fallbackText=""
          />
          <SafeAssetImage
            className="engine-match-fire"
            src={GENERATED_ASSETS.rocketFire}
            alt=""
            context="EngineMatch:rocketFire"
            fallbackText=""
          />
        </div>
        <div className="engine-match-panel">
          <span>Engine Match</span>
          <h1>{done ? "Engine ready!" : round.prompt}</h1>
          <p>Rockets work as a team: fuel, spark, engine, then thrust.</p>
          <div className={`engine-match-message ${done ? "done" : ""}`} role="status">
            <strong>{message}</strong>
          </div>
          <div className="engine-match-progress" aria-label="Matches">
            {ENGINE_MATCH_ROUNDS.map((item, index) => (
              <span
                key={item.answer}
                className={
                  done ||
                  index < roundIndex ||
                  (index === roundIndex && pickedId === round.answer)
                    ? "on"
                    : ""
                }
              />
            ))}
          </div>
          {done ? (
            <div className="planet-sort-actions">
              <button onClick={reset}>Play again</button>
              <button onClick={onExit}>Done</button>
            </div>
          ) : (
            <div className="engine-match-choices">
              <span className="game-keyboard-status" aria-live="polite">
                {selectedChoice.statusText}
              </span>
              {choices.map((part, index) => (
                <button
                  key={part.id}
                  className={
                    (selectedChoice.index === index ? "selected " : "") +
                    (pickedId === part.id
                      ? part.id === round.answer
                        ? "correct"
                        : "wrong"
                      : needsHint && part.id === round.answer
                        ? "hint"
                        : "")
                  }
                  onClick={() => choose(part)}
                  onFocus={() => setChoiceIndex(clampChoiceIndex(index, choices.length))}
                  aria-current={selectedChoice.index === index ? "true" : undefined}
                >
                  <SafeAssetImage
                    src={part.asset}
                    alt=""
                    context={`EngineMatch:part:${part.id}`}
                    fallbackText=""
                  />
                  <strong>{part.label}</strong>
                </button>
              ))}
            </div>
          )}
        </div>
      </div>
    </section>
  );
}

const ROCKET_CAUSE_CONTROLS = [
  {
    id: "fuel",
    label: "Fuel",
    low: "tiny push",
    high: "big push",
    lesson: "More fuel makes a bigger upward push.",
  },
  {
    id: "fins",
    label: "Fins",
    low: "wobbly",
    high: "steady",
    lesson: "Fins help the rocket point straight.",
  },
  {
    id: "cargo",
    label: "Cargo",
    low: "light",
    high: "heavy",
    lesson: "Heavy cargo makes the rocket work harder.",
  },
];

const ROCKET_CAUSE_PROMPTS = [
  {
    text: "Try more fuel. What changes?",
    setup: { fuel: 3, fins: 2, cargo: 1 },
  },
  {
    text: "Try fewer fins. What changes?",
    setup: { fuel: 2, fins: 1, cargo: 1 },
  },
  {
    text: "Try heavy cargo. What changes?",
    setup: { fuel: 2, fins: 2, cargo: 3 },
  },
];

function clampRocketCauseValue(value) {
  return Math.max(1, Math.min(3, value));
}

function rocketCauseResult(settings) {
  const push = settings.fuel * 34 - settings.cargo * 12;
  const height = Math.max(28, Math.min(100, push + 34));
  const steady = settings.fins * 28 - settings.fuel * 4 - settings.cargo * 3;
  const wobble = Math.max(0, Math.min(18, 22 - steady));
  const label =
    height > 78
      ? "High flight"
      : height < 48
        ? "Short hop"
        : "Medium flight";
  const tip =
    wobble > 10
      ? "The rocket wobbled. Add fins to make it steady."
      : settings.cargo >= 3 && settings.fuel <= 1
        ? "Heavy cargo needs more fuel."
        : settings.fuel >= 3
          ? "More fuel gave the rocket a stronger push."
          : "Change one control and launch again.";
  return { height, wobble, label, tip };
}

function RocketCauseLevel({ onExit }) {
  const [settings, setSettings] = useState({ fuel: 2, fins: 2, cargo: 1 });
  const [launchKey, setLaunchKey] = useState(0);
  const [promptIndex, setPromptIndex] = useState(0);
  const result = rocketCauseResult(settings);
  const currentPrompt = ROCKET_CAUSE_PROMPTS[promptIndex];

  const setControl = (id, delta) => {
    setSettings((current) => ({
      ...current,
      [id]: clampRocketCauseValue(current[id] + delta),
    }));
    playKidSound("boop");
  };

  const launch = () => {
    setLaunchKey((key) => key + 1);
    playKidSound(result.wobble > 10 ? "pop" : "whoosh");
  };

  const tryPrompt = () => {
    setSettings(currentPrompt.setup);
    setLaunchKey((key) => key + 1);
    playKidSound("chime");
  };

  const nextPrompt = () => {
    setPromptIndex((index) => (index + 1) % ROCKET_CAUSE_PROMPTS.length);
    playKidSound("boop");
  };

  return (
    <section
      className="rocket-cause-level"
      aria-label="Rocket cause and effect lab"
      style={{
        "--rocket-result-lift": `${Math.round(result.height * 2.6)}px`,
        "--rocket-result-settle": `${Math.round(result.height * 2.1)}px`,
        "--rocket-result-wobble": `${result.wobble}deg`,
        "--rocket-result-counter-wobble": `${-result.wobble}deg`,
      }}
    >
      <Starfield count={240} />
      <button className="planet-sort-back" onClick={onExit}>
        ← Back to planets
      </button>
      <div className="rocket-cause-shell">
        <div className="rocket-cause-copy">
          <span>Rocket Lab</span>
          <h1>Change one thing. Watch what happens.</h1>
          <p>Fuel pushes. Fins steady. Cargo adds weight.</p>
          <div className="rocket-cause-prompt" role="status">
            <strong>{currentPrompt.text}</strong>
            <button onClick={tryPrompt}>Try it</button>
            <button onClick={nextPrompt}>Next idea</button>
          </div>
          <div className="rocket-cause-result" aria-live="polite">
            <strong>{result.label}</strong>
            <span>{result.tip}</span>
          </div>
        </div>

        <div className="rocket-cause-stage" aria-hidden="true">
          <div className="rocket-cause-height">
            <span>high</span>
            <i />
            <span>low</span>
          </div>
          <div key={launchKey} className="rocket-cause-ship">
            <SafeAssetImage
              src={GENERATED_ASSETS.rocket}
              alt=""
              context="RocketCause:rocket"
              fallbackText=""
            />
            <SafeAssetImage
              className="rocket-cause-flame"
              src={GENERATED_ASSETS.rocketFire}
              alt=""
              context="RocketCause:flame"
              fallbackText=""
            />
          </div>
        </div>

        <div className="rocket-cause-controls" aria-label="Rocket controls">
          {ROCKET_CAUSE_CONTROLS.map((control) => (
            <div className="rocket-cause-control" key={control.id}>
              <div>
                <strong>{control.label}</strong>
                <span>{control.lesson}</span>
              </div>
              <div className="rocket-cause-stepper">
                <button
                  onClick={() => setControl(control.id, -1)}
                  aria-label={`Less ${control.label}`}
                >
                  −
                </button>
                <span aria-live="polite">
                  {settings[control.id] === 1
                    ? control.low
                    : settings[control.id] === 3
                      ? control.high
                      : "middle"}
                </span>
                <button
                  onClick={() => setControl(control.id, 1)}
                  aria-label={`More ${control.label}`}
                >
                  +
                </button>
              </div>
            </div>
          ))}
          <button className="rocket-cause-launch" onClick={launch}>
            Launch and observe
          </button>
        </div>
      </div>
    </section>
  );
}

const UFO_BUILDER_GROUPS = [
  {
    id: "body",
    label: "Body",
    options: [
      {
        id: "bronzeHull",
        label: "Bronze saucer",
        icon: "🛸",
        cue: "smooth ellipsoid",
        addText: "A bronze saucer body makes a steady UFO shape.",
      },
      {
        id: "orangeOrb",
        label: "Orange orb",
        icon: "🟠",
        cue: "round glowing craft",
        addText: "An orange orb body makes the craft feel bouncy and gentle.",
      },
      {
        id: "starCraft",
        label: "Star craft",
        icon: "✴",
        cue: "pointy mystery shape",
        addText: "A star body makes a zippy mystery craft.",
      },
    ],
  },
  {
    id: "windows",
    label: "Windows",
    options: [
      {
        id: "blueWindows",
        label: "Blue portholes",
        icon: "🔵",
        cue: "peek outside",
        addText: "Blue portholes help space friends peek out.",
      },
      {
        id: "smileWindows",
        label: "Smile face",
        icon: "😊",
        cue: "friendly eyes",
        addText: "A smile face makes the UFO look friendly.",
      },
      {
        id: "radarWindows",
        label: "Radar dots",
        icon: "🟣",
        cue: "look all around",
        addText: "Radar dots help the UFO notice things around it.",
      },
    ],
  },
  {
    id: "launcher",
    label: "Launcher",
    options: [
      {
        id: "orbPods",
        label: "Baby orbs",
        icon: "🔴",
        cue: "launch helpers",
        addText: "Baby orb pods can launch tiny helper UFOs.",
      },
      {
        id: "sparkTrail",
        label: "Spark tail",
        icon: "✨",
        cue: "leave a trail",
        addText: "A spark tail leaves a bright path behind the UFO.",
      },
      {
        id: "quietBeam",
        label: "Quiet beam",
        icon: "💡",
        cue: "soft landing light",
        addText: "A quiet beam helps the UFO land softly.",
      },
      {
        id: "dropUfos",
        label: "Drop UFOs",
        icon: "💧",
        cue: "tiny upside-down ships",
        addText: "Little upside-down raindrop UFOs can float out and explore.",
      },
    ],
  },
  {
    id: "energy",
    label: "Energy",
    options: [
      {
        id: "starDrive",
        label: "Star drive",
        icon: "✴",
        cue: "eight-point power",
        addText: "The star drive gives the UFO zippy energy.",
      },
      {
        id: "lightBurst",
        label: "Light burst",
        icon: "💥",
        cue: "appear in a flash",
        addText: "A light burst helps the UFO appear in a happy flash.",
      },
      {
        id: "cometGlow",
        label: "Comet glow",
        icon: "☄",
        cue: "purple speed glow",
        addText: "A comet glow makes the UFO feel fast and magical.",
      },
    ],
  },
];

const DEFAULT_UFO_BUILD = {
  body: "bronzeHull",
  windows: "blueWindows",
  launcher: "orbPods",
  energy: "starDrive",
};

function ufoOptionFor(groupId, optionId) {
  const group = UFO_BUILDER_GROUPS.find((item) => item.id === groupId);
  return group?.options.find((option) => option.id === optionId) || group?.options[0];
}

function ufoBuildSummary(build) {
  return UFO_BUILDER_GROUPS
    .map((group) => ufoOptionFor(group.id, build[group.id])?.label)
    .filter(Boolean)
    .join(" + ");
}

function UfoBuilderLevel({ onExit }) {
  const [build, setBuild] = useState(DEFAULT_UFO_BUILD);
  const [savedBuilds, setSavedBuilds] = useState([]);
  const [message, setMessage] = useState("Tap one part in each row. Mix things that do not usually belong.");
  const [beamOn, setBeamOn] = useState(false);
  const buildSummary = ufoBuildSummary(build);
  const buildKey = JSON.stringify(build);
  const savedCurrentBuild = savedBuilds.some((item) => item.key === buildKey);

  const selectPart = (group, option) => {
    setBuild((current) => ({ ...current, [group.id]: option.id }));
    setMessage(`${option.addText} Now try it with a different ${group.label.toLowerCase()}.`);
    setBeamOn(false);
    playKidSound("boop");
  };

  const materialize = () => {
    setBeamOn(true);
    setMessage(`Your ${buildSummary} materializes. What could this one do?`);
    playKidSound("chime");
  };

  const surpriseBuild = () => {
    const nextBuild = {};
    UFO_BUILDER_GROUPS.forEach((group, index) => {
      const currentIndex = group.options.findIndex((option) => option.id === build[group.id]);
      const nextIndex = (Math.max(currentIndex, 0) + index + 1) % group.options.length;
      nextBuild[group.id] = group.options[nextIndex].id;
    });
    setBuild(nextBuild);
    setBeamOn(false);
    setMessage(`Surprise mix: ${ufoBuildSummary(nextBuild)}.`);
    playKidSound("pop");
  };

  const saveBuild = () => {
    if (savedCurrentBuild) {
      setMessage("That build is already in your hangar.");
      playKidSound("boop");
      return;
    }
    setSavedBuilds((current) => [...current.slice(-2), { key: buildKey, label: buildSummary }]);
    setMessage(`Saved: ${buildSummary}. Build another one.`);
    playKidSound("chime");
  };

  const reset = () => {
    setBuild(DEFAULT_UFO_BUILD);
    setSavedBuilds([]);
    setBeamOn(false);
    setMessage("Tap one part in each row. Mix things that do not usually belong.");
    playKidSound("pop");
  };

  return (
    <section className="ufo-builder-level" aria-label="UFO builder game">
      <Starfield count={260} />
      <button className="planet-sort-back" onClick={onExit}>
        ← Back to planets
      </button>
      <div className="ufo-builder-shell">
        <div className="ufo-builder-copy">
          <span>UFO Builder</span>
          <h1>Build it piece by piece.</h1>
          <p>Every part changes what the craft can do.</p>
          <div className="ufo-builder-message" role="status" aria-live="polite">
            <strong>{message}</strong>
            <span>{buildSummary}</span>
          </div>
          <div className="ufo-builder-actions">
            <button onClick={materialize}>Materialize</button>
            <button onClick={surpriseBuild}>Surprise mix</button>
            <button onClick={saveBuild}>Save build</button>
            <button onClick={reset}>Start over</button>
          </div>
          {savedBuilds.length ? (
            <div className="ufo-saved-builds" aria-label="Saved UFO builds">
              <span>Hangar</span>
              {savedBuilds.map((item) => (
                <small key={item.key}>{item.label}</small>
              ))}
            </div>
          ) : null}
        </div>

        <div className={`ufo-builder-stage ${beamOn ? "beaming" : ""}`} aria-hidden="true">
          <div className="ufo-light-burst" />
          <div className="ufo-spark-trail" />
          <div className={`ufo-craft generated-preview body-${build.body} windows-${build.windows} launcher-${build.launcher} energy-${build.energy}`}>
            <div className="ufo-generated-preview-art">
              <span className={`ufo-stage-piece stage-body art-${build.body}`} />
              <span className={`ufo-stage-piece stage-windows art-${build.windows}`} />
              {build.launcher === "dropUfos" ? (
                <span className="ufo-stage-piece stage-drop-ufos" />
              ) : (
                <span className={`ufo-stage-piece stage-launcher art-${build.launcher}`} />
              )}
              <span className={`ufo-stage-piece stage-energy art-${build.energy}`} />
            </div>
          </div>
        </div>

        <div className="ufo-part-grid" aria-label="UFO parts">
          {UFO_BUILDER_GROUPS.map((group) => (
            <div className="ufo-part-group" key={group.id}>
              <h2>{group.label}</h2>
              <div className="ufo-part-options">
                {group.options.map((part) => {
                  const selected = build[group.id] === part.id;
                  return (
                    <button
                      key={part.id}
                      className={`ufo-part-card ${selected ? "selected" : ""}`}
                      onClick={() => selectPart(group, part)}
                      aria-pressed={selected ? "true" : "false"}
                    >
                      <span
                        className={`ufo-art-icon art-${part.id}`}
                        aria-hidden="true"
                      />
                      <strong>{part.label}</strong>
                      <small>{part.cue}</small>
                    </button>
                  );
                })}
              </div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

const MASHUP_ITEMS = [
  {
    id: "spoon",
    icon: "🥄",
    label: "Spoon",
    power: "scoops moon dust",
    nameBit: "Scoop",
    trait: "move little things",
  },
  {
    id: "boot",
    icon: "🥾",
    label: "Boot",
    power: "stomps and walks",
    nameBit: "Stomp",
    trait: "help with moving",
  },
  {
    id: "star",
    icon: "⭐",
    label: "Star",
    power: "glows in the dark",
    nameBit: "Glow",
    trait: "make light",
  },
  {
    id: "blanket",
    icon: "🧣",
    label: "Blanket",
    power: "keeps things cozy",
    nameBit: "Cozy",
    trait: "protect and comfort",
  },
  {
    id: "magnet",
    icon: "🧲",
    label: "Magnet",
    power: "pulls metal closer",
    nameBit: "Pull",
    trait: "pull things",
  },
  {
    id: "wheel",
    icon: "⚙",
    label: "Wheel",
    power: "rolls over rocks",
    nameBit: "Roll",
    trait: "help with moving",
  },
  {
    id: "bubble",
    icon: "🫧",
    label: "Bubble",
    power: "floats softly",
    nameBit: "Float",
    trait: "move gently",
  },
  {
    id: "flashlight",
    icon: "🔦",
    label: "Flashlight",
    power: "finds hidden paths",
    nameBit: "Beam",
    trait: "make light",
  },
];

const MASHUP_HELPERS = [
  "help a sleepy rover",
  "carry snacks on the Moon",
  "find a lost star",
  "make a quiet bedtime rocket",
  "cross a bumpy crater",
];

function mashupName(first, second) {
  if (!first || !second) return "Pick two cards";
  return `${first.nameBit}-${second.nameBit} Helper`;
}

function mashupPurpose(first, second, helper) {
  if (!first || !second) {
    return "Tap any two cards. Weird pairs are welcome.";
  }
  return `It ${first.power} and ${second.power} to ${helper}.`;
}

function MashupMakerLevel({ onExit }) {
  const [selectedIds, setSelectedIds] = useState([]);
  const [helperIndex, setHelperIndex] = useState(0);
  const selectedItems = selectedIds
    .map((id) => MASHUP_ITEMS.find((item) => item.id === id))
    .filter(Boolean);
  const [first, second] = selectedItems;
  const complete = selectedItems.length === 2;
  const helper = MASHUP_HELPERS[helperIndex];

  const toggleItem = (id) => {
    setSelectedIds((current) => {
      if (current.includes(id)) return current.filter((item) => item !== id);
      if (current.length >= 2) return [current[1], id];
      return [...current, id];
    });
    playKidSound("boop");
  };

  const clear = () => {
    setSelectedIds([]);
    playKidSound("pop");
  };

  const celebrate = () => {
    if (!complete) return;
    setHelperIndex((index) => (index + 1) % MASHUP_HELPERS.length);
    playKidSound("chime");
  };

  return (
    <section className="mashup-maker-level" aria-label="Mashup Maker game">
      <Starfield count={240} />
      <button className="planet-sort-back" onClick={onExit}>
        ← Back to planets
      </button>
      <div className="mashup-maker-shell">
        <div className="mashup-maker-copy">
          <span>Mashup Maker</span>
          <h1>Pick two things. Make a new idea.</h1>
          <p>No wrong pairs. The surprising ones are the best ones.</p>
          <div className="mashup-helper-card">
            <span>Today it can...</span>
            <strong>{helper}</strong>
            <button
              onClick={() => {
                setHelperIndex((index) => (index + 1) % MASHUP_HELPERS.length);
                playKidSound("boop");
              }}
            >
              New job
            </button>
          </div>
        </div>

        <div className="mashup-maker-board" aria-live="polite">
          <div className={`mashup-invention ${complete ? "ready" : ""}`}>
            <div className="mashup-orbit">
              <span>{first ? first.icon : "?"}</span>
              <i>+</i>
              <span>{second ? second.icon : "?"}</span>
            </div>
            <h2>{mashupName(first, second)}</h2>
            <p>{mashupPurpose(first, second, helper)}</p>
            <div className="mashup-actions">
              <button onClick={clear}>Clear</button>
              <button onClick={celebrate} disabled={!complete}>
                It works!
              </button>
            </div>
          </div>
        </div>

        <div className="mashup-card-grid" aria-label="Choose two idea cards">
          {MASHUP_ITEMS.map((item) => {
            const selected = selectedIds.includes(item.id);
            return (
              <button
                key={item.id}
                className={`mashup-card ${selected ? "selected" : ""}`}
                onClick={() => toggleItem(item.id)}
                aria-pressed={selected ? "true" : "false"}
              >
                <span>{item.icon}</span>
                <strong>{item.label}</strong>
                <small>{item.power}</small>
              </button>
            );
          })}
        </div>
      </div>
    </section>
  );
}

const CONNECTION_QUESTS = [
  {
    id: "stuck-rover",
    icon: "🛞",
    title: "The rover is stuck.",
    need: "Help it cross bumpy moon rocks.",
  },
  {
    id: "sleepy-alien",
    icon: "😴",
    title: "A little alien cannot sleep.",
    need: "Make a calm bedtime helper.",
  },
  {
    id: "lost-star",
    icon: "🌟",
    title: "A star got lost.",
    need: "Help it find the way home.",
  },
  {
    id: "snack-trip",
    icon: "🍎",
    title: "The astronaut needs a snack trip.",
    need: "Carry food across a crater.",
  },
  {
    id: "dark-cave",
    icon: "🌑",
    title: "The Moon cave is dark.",
    need: "Find a safe path inside.",
  },
];

const CONNECTION_THINKING_PROMPTS = [
  "What is the same?",
  "What is different?",
  "Which helper works first?",
  "What else could this plan help?",
];

function connectionQuestExplanation(quest, first, second) {
  if (!first || !second) {
    return "Pick two helper cards. Any pair can become an idea.";
  }
  return `${first.label} ${first.power}, and ${second.label} ${second.power}. Together they can ${quest.need.toLowerCase()}`;
}

function connectionThinkingCue(first, second) {
  if (!first || !second) return "Choose two helpers and look for a connection.";
  if (first.trait === second.trait) {
    return `Same idea: they both ${first.trait}.`;
  }
  return `Different ideas: one ${first.trait}; one ${second.trait}. That can still work.`;
}

function connectionFlexCue(madeConnections) {
  if (!madeConnections.length) {
    return "Can you solve it one way first?";
  }
  if (madeConnections.length === 1) {
    return "Can you solve the same problem another way?";
  }
  return "Now your brain has more than one path.";
}

function connectionSequenceCue(first, second) {
  if (!first || !second) return "";
  return `First use ${first.label}. Then add ${second.label}.`;
}

function ConnectionQuestLevel({ onExit }) {
  const [questIndex, setQuestIndex] = useState(0);
  const [selectedIds, setSelectedIds] = useState([]);
  const [madeConnections, setMadeConnections] = useState([]);
  const [thinkingPromptIndex, setThinkingPromptIndex] = useState(0);
  const quest = CONNECTION_QUESTS[questIndex];
  const selectedItems = selectedIds
    .map((id) => MASHUP_ITEMS.find((item) => item.id === id))
    .filter(Boolean);
  const [first, second] = selectedItems;
  const complete = selectedItems.length === 2;
  const questComplete = madeConnections.length >= 3;
  const explanation = connectionQuestExplanation(quest, first, second);
  const currentPairKey = complete ? `${quest.id}:${selectedIds.join("+")}` : "";
  const currentSaved = madeConnections.some((item) => item.key === currentPairKey);
  const currentThinkingPrompt =
    CONNECTION_THINKING_PROMPTS[
      thinkingPromptIndex % CONNECTION_THINKING_PROMPTS.length
    ];

  const toggleItem = (id) => {
    setSelectedIds((current) => {
      if (current.includes(id)) return current.filter((item) => item !== id);
      if (current.length >= 2) return [current[1], id];
      return [...current, id];
    });
    playKidSound("boop");
  };

  const nextQuest = () => {
    setQuestIndex((index) => (index + 1) % CONNECTION_QUESTS.length);
    setSelectedIds([]);
    setMadeConnections([]);
    setThinkingPromptIndex(0);
    playKidSound("boop");
  };

  const celebrateConnection = () => {
    if (!complete) return;
    setMadeConnections((current) => {
      if (current.some((item) => item.key === currentPairKey)) return current;
      return [
        ...current.slice(-2),
        {
          key: currentPairKey,
          icons: `${first.icon}${second.icon}`,
          name: `${first.nameBit}-${second.nameBit}`,
        },
      ];
    });
    setThinkingPromptIndex((index) => index + 1);
    playKidSound("chime");
  };

  const tryAnotherPair = () => {
    setSelectedIds([]);
    setThinkingPromptIndex((index) => index + 1);
    playKidSound("boop");
  };

  const surprisePair = () => {
    const firstIndex = (questIndex + thinkingPromptIndex) % MASHUP_ITEMS.length;
    const secondIndex = (firstIndex + 3) % MASHUP_ITEMS.length;
    setSelectedIds([MASHUP_ITEMS[firstIndex].id, MASHUP_ITEMS[secondIndex].id]);
    setThinkingPromptIndex((index) => index + 1);
    playKidSound("chime");
  };

  return (
    <section className="connection-quest-level" aria-label="Connection Quest game">
      <Starfield count={240} />
      <button className="planet-sort-back" onClick={onExit}>
        ← Back to planets
      </button>
      <div className="connection-quest-shell">
        <div className="connection-quest-problem">
          <span>Connection Quest</span>
          <h1>{quest.title}</h1>
          <p>{quest.need}</p>
          <div className="connection-problem-icon" aria-hidden="true">
            {quest.icon}
          </div>
          <button onClick={nextQuest}>New problem</button>
        </div>

        <div className="connection-quest-board" aria-live="polite">
          {questComplete ? (
            <div className="connection-brain-spark">
              <span>Brain stretch!</span>
              <strong>You made three different plans.</strong>
              <button onClick={nextQuest}>Try a new problem</button>
            </div>
          ) : null}
          <div className={`connection-build ${complete ? "ready" : ""}`}>
            <div className="connection-pair">
              <span>{first ? first.icon : "?"}</span>
              <i>+</i>
              <span>{second ? second.icon : "?"}</span>
            </div>
            <h2>
              {complete
                ? `${first.nameBit}-${second.nameBit} Plan`
                : "Choose two helpers"}
            </h2>
            <p>{explanation}</p>
            <div className="connection-thinking-cue">
              {connectionThinkingCue(first, second)}
            </div>
            {complete ? (
              <div className="connection-parent-prompt">
                Ask: {currentThinkingPrompt}
              </div>
            ) : null}
            {complete ? (
              <div className="connection-sequence-cue">
                {connectionSequenceCue(first, second)}
              </div>
            ) : null}
            <div className="connection-actions">
              <button
                onClick={() => {
                  setSelectedIds([]);
                  playKidSound("pop");
                }}
              >
                Clear
              </button>
              <button onClick={celebrateConnection} disabled={!complete || currentSaved}>
                {currentSaved ? "Saved idea" : "That could work"}
              </button>
              <button onClick={tryAnotherPair} disabled={!complete}>
                New pair
              </button>
              <button onClick={surprisePair}>Surprise pair</button>
            </div>
          </div>
          <div className="connection-counter" aria-label="Connections made">
            {[0, 1, 2].map((index) => (
              <span key={index} className={index < madeConnections.length ? "on" : ""} />
            ))}
          </div>
          <div className="connection-flex-cue">
            {connectionFlexCue(madeConnections)}
          </div>
          {madeConnections.length ? (
            <div className="connection-made-list" aria-label="Saved connection ideas">
              {madeConnections.map((item) => (
                <span key={item.key}>
                  <b>{item.icons}</b>
                  {item.name}
                </span>
              ))}
            </div>
          ) : null}
        </div>

        <div className="connection-card-grid" aria-label="Choose helper cards">
          {MASHUP_ITEMS.map((item) => {
            const selected = selectedIds.includes(item.id);
            return (
              <button
                key={item.id}
                className={`connection-card ${selected ? "selected" : ""}`}
                onClick={() => toggleItem(item.id)}
                aria-pressed={selected ? "true" : "false"}
              >
                <span>{item.icon}</span>
                <strong>{item.label}</strong>
                <small>{item.power}</small>
              </button>
            );
          })}
        </div>
      </div>
    </section>
  );
}

const ASTEROID_DODGE_ROUNDS = [
  { lane: 0, label: "left rock", rock: GENERATED_ASSETS.asteroidRock },
  { lane: 2, label: "right ice rock", rock: GENERATED_ASSETS.asteroidIce },
  { lane: 1, label: "middle rock", rock: GENERATED_ASSETS.asteroidRock },
  { lane: 0, label: "left ice rock", rock: GENERATED_ASSETS.asteroidIce },
  { lane: 2, label: "right rock", rock: GENERATED_ASSETS.asteroidRock },
];

function AsteroidDodgeLevel({ onExit }) {
  const ASTEROID_PATHS = ASTEROID_DODGE_PATHS;
  const timerRef = useRef(null);
  const [screen, setScreen] = useState("intro");
  const [roundIndex, setRoundIndex] = useState(0);
  const [dodges, setDodges] = useState(0);
  const [rocketX, setRocketX] = useState(50);
  const [message, setMessage] = useState(
    "Tap Begin, then steer away from falling rocks.",
  );
  const [phase, setPhase] = useState("ready");
  const [won, setWon] = useState(false);
  const asteroid = ASTEROID_DODGE_ROUNDS[roundIndex % ASTEROID_DODGE_ROUNDS.length];
  const asteroidX = ASTEROID_PATHS[roundIndex % ASTEROID_PATHS.length];
  const safeDirection = asteroidX <= 50 ? "right" : "left";

  const moveShip = (step) => {
    if (screen !== "level" || won) return;
    setRocketX((x) => Math.max(10, Math.min(90, x + step)));
    playKidSound("boop");
  };

  const clearAsteroidTimer = () => {
    window.clearTimeout(timerRef.current);
    timerRef.current = null;
  };

  const startRound = () => {
    if (screen !== "level" || won || phase === "falling") return;
    clearAsteroidTimer();
    setPhase("falling");
    setMessage(`Rock falling. Move ${safeDirection} to a safe lane!`);
    timerRef.current = window.setTimeout(() => {
      setRocketX((currentX) => {
        const dodged = Math.abs(currentX - asteroidX) > 15;
        if (!dodged) {
          setPhase("bump");
          setMessage("Soft bump. Try the glowing safe lane next.");
          playKidSound("pop");
          setRoundIndex((index) => index + 1);
          return 50;
        }
        playKidSound("chime");
        const nextDodges = dodges + 1;
        setDodges(nextDodges);
        if (nextDodges >= 6) {
          setWon(true);
          setPhase("done");
          setMessage("Clear flight! You dodged the space rocks.");
          return currentX;
        }
        setRoundIndex((index) => index + 1);
        setPhase("ready");
        setMessage("Nice dodge! Asteroids are rocks in space.");
        return currentX;
      });
    }, 1250);
  };

  const reset = () => {
    clearAsteroidTimer();
    setRoundIndex(0);
    setDodges(0);
    setRocketX(50);
    setMessage("Tap Begin, then steer away from falling rocks.");
    setPhase("ready");
    setWon(false);
    setScreen("intro");
  };

  const beginLevel = () => {
    clearAsteroidTimer();
    setRoundIndex(0);
    setDodges(0);
    setRocketX(50);
    setMessage("Move the rocket before the rock reaches it!");
    setPhase("ready");
    setWon(false);
    setScreen("level");
  };

  useWindowKeyHandler((event) => {
    if (event.key === "ArrowLeft") {
      event.preventDefault();
      moveShip(-1);
    } else if (event.key === "ArrowRight") {
      event.preventDefault();
      moveShip(1);
    } else if (event.key === " " || event.key === "Enter") {
      event.preventDefault();
      if (screen === "intro") {
        beginLevel();
      } else {
        startRound();
      }
    }
  });

  useEffect(() => {
    if (screen !== "level" || phase !== "ready" || won) return undefined;
    clearAsteroidTimer();
    timerRef.current = window.setTimeout(
      startRound,
      roundIndex === 0 ? 1300 : 1050,
    );
    return clearAsteroidTimer;
  }, [screen, phase, won, roundIndex]);

  useEffect(() => {
    if (phase !== "bump") return undefined;
    clearAsteroidTimer();
    timerRef.current = window.setTimeout(() => setPhase("ready"), 520);
    return clearAsteroidTimer;
  }, [phase]);

  useEffect(() => clearAsteroidTimer, []);

  return (
    <section
      className={`asteroid-dodge-level ${screen === "level" ? "playing" : ""}`}
      aria-label="Asteroid dodge game"
    >
      <Starfield count={240} />
      <button className="planet-sort-back" onClick={onExit}>
        ← Back to planets
      </button>
      {screen === "intro" ? (
        <div className="asteroid-dodge-opening">
          <div className="asteroid-dodge-opening-art" aria-hidden="true">
            <SafeAssetImage
              className="asteroid-opening-bg"
              src={GENERATED_ASSETS.asteroidDodgeBg}
              alt=""
              context="AsteroidDodge:openingBg"
              fallbackText=""
            />
            <SafeAssetImage
              className="asteroid-opening-rock rock-one"
              src={GENERATED_ASSETS.asteroidRock}
              alt=""
              context="AsteroidDodge:openingRock"
              fallbackText=""
            />
            <SafeAssetImage
              className="asteroid-opening-rock rock-two"
              src={GENERATED_ASSETS.asteroidIce}
              alt=""
              context="AsteroidDodge:openingIce"
              fallbackText=""
            />
            <SafeAssetImage
              className="asteroid-opening-rocket"
              src={GENERATED_ASSETS.rocket}
              alt=""
              context="AsteroidDodge:openingRocket"
              fallbackText=""
            />
          </div>
          <div className="asteroid-dodge-opening-copy">
            <span>Asteroid Dodge</span>
            <h1>Dodge the rock.</h1>
            <p>
              Asteroids are space rocks. Steer into an empty lane and keep the
              rocket safe.
            </p>
            <button className="asteroid-begin-button" onClick={beginLevel}>
              Begin
            </button>
          </div>
        </div>
      ) : (
        <div
          className={`asteroid-dodge-stage phase-${phase}`}
          style={{ "--rocket-x": `${rocketX}%`, "--asteroid-x": `${asteroidX}%` }}
        >
          <SafeAssetImage
            className="asteroid-stage-bg"
            src={GENERATED_ASSETS.asteroidDodgeBg}
            alt=""
            context="AsteroidDodge:stageBg"
            fallbackText=""
          />
          <span className="asteroid-speed-line line-one" aria-hidden="true" />
          <span className="asteroid-speed-line line-two" aria-hidden="true" />
          <span className="asteroid-speed-line line-three" aria-hidden="true" />
          <div className="asteroid-danger-lane" aria-hidden="true">
            <span>danger lane</span>
          </div>
          <div className={`asteroid-safe-lane ${safeDirection}`} aria-hidden="true">
            <span>safe lane</span>
          </div>
          <div className="asteroid-dodge-hud">
            <div>
              <span>Asteroid Dodge</span>
              <strong>{won ? "Clear flight!" : "Dodge the rock!"}</strong>
            </div>
            <div className="asteroid-dodge-progress" aria-label="Dodges">
              {[0, 1, 2, 3, 4, 5].map((index) => (
                <span key={index} className={index < dodges || won ? "on" : ""} />
              ))}
            </div>
          </div>
          <div className={`asteroid-dodge-message ${phase}`} role="status" aria-live="polite">
            <strong>{message}</strong>
          </div>
          <SafeAssetImage
            key={`asteroid-${roundIndex}-${phase}`}
            className="asteroid-rock"
            src={asteroid.rock}
            alt=""
            aria-hidden="true"
            context={`AsteroidDodge:rock:${roundIndex}`}
            fallbackText=""
          />
          <SafeAssetImage
            className="asteroid-ship"
            src={GENERATED_ASSETS.rocket}
            alt="rocket"
            context="AsteroidDodge:ship"
            fallbackText=""
          />
          <div className="asteroid-dodge-controls" aria-label="Rocket controls">
            <button onClick={() => moveShip(-12)} disabled={rocketX <= 10 || won}>
              ← Left
            </button>
            <button onClick={() => moveShip(12)} disabled={rocketX >= 90 || won}>
              Right →
            </button>
          </div>
          {won ? (
            <div className="asteroid-dodge-win">
              <button onClick={reset}>Play again</button>
              <button onClick={onExit}>Done</button>
            </div>
          ) : null}
        </div>
      )}
    </section>
  );
}

const MISSION_SWITCHES = [
  {
    id: "power",
    label: "Power",
    fact: "Power wakes up the spacecraft computers.",
    color: "#ffd66b",
  },
  {
    id: "radio",
    label: "Radio",
    fact: "Radio signals carry voices between Earth and space.",
    color: "#7ee7ff",
  },
  {
    id: "cooling",
    label: "Cooling",
    fact: "Cooling loops move heat away from busy machines.",
    color: "#8dffb2",
  },
  {
    id: "lights",
    label: "Lights",
    fact: "Docking lights help pilots see where to go.",
    color: "#ffb1d4",
  },
];

function MissionControlLevel({ onExit }) {
  const [step, setStep] = useState(0);
  const [online, setOnline] = useState([]);
  const [pickedId, setPickedId] = useState(null);
  const [message, setMessage] = useState("Turn on the glowing switch.");
  const feedbackTimerRef = useRef(null);
  const switchLockRef = useRef(false);
  const done = online.length === MISSION_SWITCHES.length;
  const target = MISSION_SWITCHES[step] || MISSION_SWITCHES[0];

  useEffect(() => {
    return () => window.clearTimeout(feedbackTimerRef.current);
  }, []);

  const choose = (item) => {
    if (done) return;
    if (switchLockRef.current && item.id === target.id) return;
    window.clearTimeout(feedbackTimerRef.current);
    setPickedId(item.id);
    if (online.includes(item.id)) {
      setMessage(`${item.label} is already glowing. Tap ${target.label} next.`);
      playKidSound("boop");
      feedbackTimerRef.current = window.setTimeout(() => setPickedId(null), 700);
      return;
    }
    if (item.id !== target.id) {
      setMessage(`${item.label} waits its turn. Tap ${target.label}.`);
      playKidSound("pop");
      feedbackTimerRef.current = window.setTimeout(() => setPickedId(null), 850);
      return;
    }
    playKidSound("chime");
    switchLockRef.current = true;
    setOnline((current) =>
      current.includes(item.id) ? current : [...current, item.id],
    );
    setMessage(`${item.label} online! ${item.fact}`);
    feedbackTimerRef.current = window.setTimeout(() => {
      switchLockRef.current = false;
      setPickedId(null);
      setStep((current) => {
        const next = current + 1;
        if (next < MISSION_SWITCHES.length) {
          setMessage("Nice switch! Find the next glow.");
        } else {
          setMessage("Mission control is ready for launch.");
        }
        return next;
      });
    }, 900);
  };

  const reset = () => {
    window.clearTimeout(feedbackTimerRef.current);
    switchLockRef.current = false;
    setStep(0);
    setOnline([]);
    setPickedId(null);
    setMessage("Turn on the glowing switch.");
  };

  return (
    <section className="mission-control-level" aria-label="Mission control switchboard game">
      <Starfield count={230} />
      <button className="planet-sort-back" onClick={onExit}>
        ← Back to planets
      </button>
      <div className="mission-control-shell">
        <div className="mission-control-copy">
          <span>Mission Control</span>
          <h1>{done ? "All systems go!" : `Turn on ${target.label}.`}</h1>
          <p>Spacecraft teams check each system before a safe launch.</p>
          <div className={`mission-control-message ${done ? "done" : ""}`} role="status" aria-live="polite">
            <strong>{message}</strong>
          </div>
          <div className="mission-control-progress" aria-label="Systems online">
            {MISSION_SWITCHES.map((item) => (
              <span key={item.id} className={online.includes(item.id) ? "on" : ""} />
            ))}
          </div>
          {done ? (
            <div className="mission-control-actions">
              <button onClick={reset}>Play again</button>
              <button onClick={onExit}>Done</button>
            </div>
          ) : null}
        </div>
        <div className="mission-switchboard">
          {MISSION_SWITCHES.map((item) => {
            const isOnline = online.includes(item.id);
            const isTarget = item.id === target.id && !done;
            const isWrong = pickedId === item.id && !isTarget;
            return (
              <button
                key={item.id}
                className={
                  "mission-switch " +
                  (isOnline ? "online " : "") +
                  (isTarget ? "target " : "") +
                  (isWrong ? "wrong" : "")
                }
                style={{ "--switch-color": item.color }}
                onClick={() => choose(item)}
              >
                <span className="mission-switch-light" />
                <strong>{item.label}</strong>
                <small>{isOnline ? "online" : isTarget ? "tap me" : "waiting"}</small>
              </button>
            );
          })}
        </div>
      </div>
    </section>
  );
}

function GravityJumpLevel({ onExit }) {
  const worlds = GRAVITY_JUMP_WORLDS;
  const [worldId, setWorldId] = useState("moon");
  const frameTimersRef = useRef([]);
  const [jumpKey, setJumpKey] = useState(0);
  const [frameIndex, setFrameIndex] = useState(0);
  const [playerX, setPlayerX] = useState(50);
  const [lastMove, setLastMove] = useState(0);
  const [jumpArc, setJumpArc] = useState(0);
  const [jumping, setJumping] = useState(false);
  const [jumps, setJumps] = useState(0);
  const [message, setMessage] = useState(
    "Moon has the biggest jump. Use ← →, then Space or ↑ to jump.",
  );
  const [moonJumpSeen, setMoonJumpSeen] = useState(false);
  const world = worlds.find((item) => item.id === worldId) || worlds[0];
  const biggestJumpWorld = worlds.reduce((best, item) =>
    item.jump > best.jump ? item : best,
  );

  const clearFrameTimers = () => {
    frameTimersRef.current.forEach((timer) => window.clearTimeout(timer));
    frameTimersRef.current = [];
  };

  const movePlayer = (direction) => {
    setPlayerX((current) => Math.max(14, Math.min(86, current + direction * 8)));
    setLastMove(direction);
  };

  const jump = () => {
    if (jumping) return;
    clearFrameTimers();
    setJumpKey((key) => key + 1);
    setFrameIndex(0);
    setJumping(true);
    setJumpArc(lastMove * 12);
    setJumps((count) => count + 1);
    if (world.id === "moon" && !moonJumpSeen) {
      setMoonJumpSeen(true);
      setMessage("Yes! Moon makes the biggest float. Try Earth next.");
    } else {
      setMessage(world.note);
    }
    playKidSound("chime");
    GRAVITY_JUMP_FRAMES.forEach((_, index) => {
      const timer = window.setTimeout(() => {
        setFrameIndex(index);
      }, (world.duration * 1000 * index) / (GRAVITY_JUMP_FRAMES.length - 1));
      frameTimersRef.current.push(timer);
    });
    frameTimersRef.current.push(
      window.setTimeout(() => {
        setJumping(false);
        setJumpArc(0);
        setFrameIndex(0);
      }, world.duration * 1000 + 80),
    );
  };

  const chooseWorld = (nextWorld) => {
    clearFrameTimers();
    setWorldId(nextWorld.id);
    setFrameIndex(0);
    setJumping(false);
    setJumpArc(0);
    setPlayerX(50);
    setLastMove(0);
    setMessage(
      nextWorld.id === "earth" && moonJumpSeen
        ? "Earth pulls harder. Tap Jump and watch the tiny hop."
        : `${nextWorld.label}: tap Jump.`,
    );
    playKidSound("boop");
  };

  const reset = () => {
    clearFrameTimers();
    setJumpKey(0);
    setFrameIndex(0);
    setPlayerX(50);
    setLastMove(0);
    setJumpArc(0);
    setJumping(false);
    setJumps(0);
    setWorldId("moon");
    setMoonJumpSeen(false);
    setMessage("Moon has the biggest jump. Use ← →, then Space or ↑ to jump.");
  };

  useWindowKeyHandler((event) => {
    if (event.key === "ArrowLeft") {
      event.preventDefault();
      movePlayer(-1);
    } else if (event.key === "ArrowRight") {
      event.preventDefault();
      movePlayer(1);
    } else if (event.key === "ArrowUp" || event.key === " ") {
      event.preventDefault();
      jump();
    }
  });

  useEffect(() => clearFrameTimers, []);

  return (
    <section
      className="gravity-jump-level full"
      aria-label="Gravity jump game"
      style={{
        "--gravity-x": `${playerX}%`,
        "--gravity-jump": `${world.jump}px`,
        "--gravity-arc": `${jumpArc}vw`,
        "--gravity-arc-start": `${jumpArc * 0.15}vw`,
        "--gravity-arc-fall": `${jumpArc * 0.72}vw`,
        "--gravity-facing": lastMove < 0 ? "-1" : "1",
        "--gravity-bg-position": world.bgPosition,
        "--gravity-time": `${world.duration}s`,
      }}
    >
      <SafeAssetImage
        className="gravity-full-bg"
        src={world.bg}
        alt=""
        context={`GravityJump:bg:${world.id}`}
        fallbackText=""
      />
      <button className="planet-sort-back" onClick={onExit}>
        ← Back to planets
      </button>
      <div className="gravity-jump-shell full">
        <div className="gravity-jump-copy">
          <span>Gravity Jump</span>
          <h1>{jumps >= 6 ? "Gravity explorer!" : "Jump on worlds."}</h1>
          <p>Small pull means a high float. Strong pull means a tiny hop.</p>
          <div
            className="gravity-jump-message"
            role="status"
            aria-live="polite"
          >
            <strong>{message}</strong>
          </div>
          <div className="gravity-best-jump">
            Biggest jump: <strong>{biggestJumpWorld.label}</strong>
          </div>
          <div
            className="gravity-pull-meter"
            aria-label={`${world.label} gravity pull is ${world.pull} times Earth`}
          >
            <div>
              <span>Pull</span>
              <strong>{world.pull}x Earth</strong>
            </div>
            <i>
              <b
                style={{
                  width: `${Math.min(100, (world.pull / 2.53) * 100)}%`,
                }}
              />
            </i>
          </div>
          <div className="gravity-jump-progress" aria-label="Moon jumps">
            {[0, 1, 2, 3, 4, 5].map((index) => (
              <span key={index} className={index < jumps ? "on" : ""} />
            ))}
          </div>
        </div>
        <div
          className={`gravity-jump-stage full ${world.id} ${jumping ? "jumping" : ""}`}
        >
          <div className="gravity-world-label">{world.label} gravity</div>
          <SafeAssetImage
            key={`${world.id}-${jumpKey}`}
            className="gravity-astronaut"
            src={GRAVITY_JUMP_FRAMES[frameIndex] || GRAVITY_JUMP_FRAMES[0]}
            role="img"
            aria-label="astronaut jumping"
            context={`GravityJump:astronaut:${frameIndex}`}
            fallbackText=""
          />
          <div className="gravity-float-trail" aria-hidden="true">
            <i />
            <i />
            <i />
          </div>
          <div
            className="gravity-world-picker"
            aria-label="Choose gravity world"
          >
            {worlds.map((item) => (
              <button
                key={item.id}
                className={
                  (item.id === world.id ? "on " : "") +
                  (moonJumpSeen && item.id === "earth" ? "hint" : "")
                }
                onClick={() => chooseWorld(item)}
                aria-pressed={item.id === world.id ? "true" : "false"}
              >
                {item.short}
              </button>
            ))}
          </div>
          <div className="gravity-key-hint" aria-hidden="true">
            ← → move · Space / ↑ jump
          </div>
          <div className="gravity-jump-controls">
            <button
              className="gravity-move-button"
              onClick={() => movePlayer(-1)}
            >
              ←
            </button>
            <button className="gravity-jump-button" onClick={jump}>
              Jump
            </button>
            <button
              className="gravity-move-button"
              onClick={() => movePlayer(1)}
            >
              →
            </button>
            <button className="gravity-reset-button" onClick={reset}>
              Reset
            </button>
          </div>
        </div>
      </div>
    </section>
  );
}

const ALIEN_LANGUAGE_CASES = [
  {
    id: "zeta-glyph",
    title: "Zeta Reticuli Glyph System",
    civilization: "Zeta Reticuli Archive Keepers",
    kind: "Glyph taxonomy",
    prompt: "A three-mark crest appears beside open airlocks.",
    evidence: ["3 pulse marks", "open crescent", "low blue glow"],
    options: ["safe greeting", "storm warning", "trade request"],
    answer: "safe greeting",
    note: "Repeated open shapes usually mark permission, welcome, or safe passage.",
    difficulty: 2,
    sensitivity: 3,
    success: 88,
    primaryGlyphs: ["<>", "O--", "::", "<O>"],
    alienLabel: "AUR-VEK",
    warning: "Do not mirror the crest upside down; inversion means sealed passage.",
  },
  {
    id: "andromeda-gesture",
    title: "Andromeda Gesture Library: Sequence 47",
    civilization: "Andromeda Collective",
    kind: "Gesture sequence",
    prompt: "A visitor touches heart, visor, then points to the horizon.",
    evidence: ["heart tap", "visor lift", "horizon point"],
    options: ["shared intent", "territory claim", "farewell"],
    answer: "shared intent",
    note: "The gesture begins with self, reveals attention, then extends purpose outward.",
    difficulty: 3,
    sensitivity: 5,
    success: 74,
    primaryGlyphs: ["I", "[]", "->", "I[]->"],
    alienLabel: "SEN-TALA",
    warning: "Skipping the visor-lift step reads as suspicion.",
  },
  {
    id: "orion-pulsar",
    title: "Orion Veil Pulsar Resonance",
    civilization: "Orion Veil Signal Choir",
    kind: "Sound-wave pattern",
    prompt: "The same tone repeats twice, pauses, then returns brighter.",
    evidence: ["two short tones", "one long rest", "brighter return"],
    options: ["question marker", "danger beacon", "gift accepted"],
    answer: "question marker",
    note: "A repeated pulse followed by silence leaves space for a response.",
    difficulty: 4,
    sensitivity: 2,
    success: 69,
    primaryGlyphs: ["beep", "beep", "...", "BEEP"],
    alienLabel: "KO-RI?",
    warning: "Answer before the rest completes and the choir treats it as interruption.",
  },
  {
    id: "triangulum-map",
    title: "Triangulum Forbidden Quadrant Map",
    civilization: "Triangulum Navigators",
    kind: "Star-map syntax",
    prompt: "Three bright stars form a gate, but one route line is broken.",
    evidence: ["tri-star gate", "broken route", "dim red marker"],
    options: ["restricted route", "home system", "celebration path"],
    answer: "restricted route",
    note: "Broken paths are not missing data; they are deliberate diplomatic boundaries.",
    difficulty: 5,
    sensitivity: 4,
    success: 61,
    primaryGlyphs: ["*", "*", "*", "-/-"],
    alienLabel: "NOX-LIM",
    warning: "Crossing a broken route without a request can end first-contact talks.",
  },
];

const ALIEN_MEANING_HELPERS = {
  "safe greeting": { icon: "👋", kid: "Hello friend" },
  "storm warning": { icon: "⛈", kid: "Storm coming" },
  "trade request": { icon: "🎁", kid: "Want to share" },
  "shared intent": { icon: "🤝", kid: "We help together" },
  "territory claim": { icon: "🚩", kid: "This is mine" },
  farewell: { icon: "👋", kid: "Bye bye" },
  "question marker": { icon: "❓", kid: "I have a question" },
  "danger beacon": { icon: "⚠", kid: "Danger" },
  "gift accepted": { icon: "✅", kid: "Gift is okay" },
  "restricted route": { icon: "⛔", kid: "Do not go" },
  "home system": { icon: "🏠", kid: "Home place" },
  "celebration path": { icon: "⭐", kid: "Party path" },
};

function alienMeaningHelper(option) {
  return ALIEN_MEANING_HELPERS[option] || { icon: "✨", kid: option };
}

function AlienGlyphSpecimen({ item, active }) {
  return (
    <div className={`alien-specimen ${active ? "active" : ""}`} aria-hidden="true">
      <div className="alien-orbit-ring" />
      <div className="alien-main-glyph">
        {item.primaryGlyphs.map((glyph, index) => (
          <span key={`${item.id}-${glyph}-${index}`}>{glyph}</span>
        ))}
      </div>
      <div className="alien-waveform">
        {[32, 54, 38, 72, 46, 64, 28, 58].map((height, index) => (
          <i key={index} style={{ height: `${height}%` }} />
        ))}
      </div>
    </div>
  );
}

function AlienFieldGuideCard({ item, unlocked, compact }) {
  const stars = "★★★★★".slice(0, item.difficulty) + "☆☆☆☆☆".slice(item.difficulty);
  return (
    <article className={`alien-guide-card ${unlocked ? "unlocked" : "locked"} ${compact ? "compact" : ""}`}>
      <header>
        <span>{unlocked ? item.kind : "Encrypted entry"}</span>
        <h2>{unlocked ? item.title : "Unknown first-contact record"}</h2>
        <small>{unlocked ? item.alienLabel : "LOCKED"}</small>
      </header>
      <AlienGlyphSpecimen item={item} active={unlocked} />
      <div className="alien-guide-grid">
        <div>
          <strong>Civilization</strong>
          <p>{unlocked ? item.civilization : "Decode a field case to reveal this archive."}</p>
        </div>
        <div>
          <strong>Decoding Difficulty</strong>
          <p>{unlocked ? stars : "?????"}</p>
        </div>
        <div>
          <strong>Cultural Sensitivity</strong>
          <p>{unlocked ? `${item.sensitivity}/5` : "--"}</p>
        </div>
        <div>
          <strong>Success Probability</strong>
          <p>{unlocked ? `${item.success}%` : "--"}</p>
        </div>
      </div>
      <div className="alien-guide-note">
        <strong>{unlocked ? "Critical note" : "Archive note"}</strong>
        <p>{unlocked ? item.warning : "Evidence panels remain hidden until the translation is confirmed."}</p>
      </div>
    </article>
  );
}

function AlienLanguageLevel({ onExit }) {
  const [caseIndex, setCaseIndex] = useState(0);
  const [selected, setSelected] = useState(null);
  const [message, setMessage] = useState("Look at the alien clue. Tap one big picture card.");
  const [unlocked, setUnlocked] = useState([]);
  const [selectedCardId, setSelectedCardId] = useState(ALIEN_LANGUAGE_CASES[0].id);
  const current = ALIEN_LANGUAGE_CASES[caseIndex];
  const solvedCurrent = unlocked.includes(current.id);
  const complete = unlocked.length === ALIEN_LANGUAGE_CASES.length;
  const selectedCard =
    ALIEN_LANGUAGE_CASES.find((item) => item.id === selectedCardId) ||
    ALIEN_LANGUAGE_CASES[0];

  const choose = (option) => {
    if (solvedCurrent) return;
    setSelected(option);
    if (option !== current.answer) {
      setMessage("Try another picture. The alien is giving us a clue.");
      playKidSound("pop");
      return;
    }
    playKidSound("chime");
    setUnlocked((items) => (items.includes(current.id) ? items : [...items, current.id]));
    setSelectedCardId(current.id);
    setMessage(`${current.answer}: ${current.note}`);
  };

  const nextCase = () => {
    const nextIndex = Math.min(caseIndex + 1, ALIEN_LANGUAGE_CASES.length - 1);
    setCaseIndex(nextIndex);
    setSelected(null);
    setSelectedCardId(ALIEN_LANGUAGE_CASES[nextIndex].id);
    setMessage("New alien clue. Tap one big picture card.");
  };

  const reset = () => {
    setCaseIndex(0);
    setSelected(null);
    setUnlocked([]);
    setSelectedCardId(ALIEN_LANGUAGE_CASES[0].id);
    setMessage("Look at the alien clue. Tap one big picture card.");
  };

  return (
    <section className="alien-language-level" aria-label="Alien Language Explorer">
      <Starfield count={260} />
      <button className="planet-sort-back" onClick={onExit}>
        ← Back to planets
      </button>
      <div className="alien-language-shell">
        <div className="alien-decoder-panel">
          <div className="alien-kicker">Alien Language Explorer</div>
          <h1>{complete ? "Alien helper!" : "What is the alien saying?"}</h1>
          <p>
            Look at the clue, then tap the big answer picture.
          </p>
          <div className="alien-kid-steps" aria-label="How to play">
            <span>1 Look</span>
            <span>2 Tap</span>
            <span>3 Next</span>
          </div>
          <div className="alien-decoder-stage">
            <AlienGlyphSpecimen item={current} active />
            <div className="alien-evidence-stack" aria-label="Evidence">
              {current.evidence.map((evidence, index) => (
                <span key={evidence}>
                  <b>{`0${index + 1}`}</b>
                  {evidence}
                </span>
              ))}
            </div>
          </div>
          <div className={`alien-decoder-message ${solvedCurrent ? "solved" : selected ? "miss" : ""}`} role="status">
            <strong>{message}</strong>
          </div>
          <div className="alien-options">
            {current.options.map((option) => {
              const helper = alienMeaningHelper(option);
              return (
                <button
                  key={option}
                  className={
                    selected === option
                      ? option === current.answer
                        ? "correct"
                        : "wrong"
                      : solvedCurrent && option === current.answer
                        ? "correct"
                        : ""
                  }
                  onClick={() => choose(option)}
                  disabled={solvedCurrent}
                >
                  <span className="alien-option-icon" aria-hidden="true">{helper.icon}</span>
                  <strong>{helper.kid}</strong>
                  <small>{option}</small>
                </button>
              );
            })}
          </div>
          <div className="alien-decoder-actions">
            <div className="alien-progress" aria-label="Unlocked field guide cards">
              {ALIEN_LANGUAGE_CASES.map((item) => (
                <span key={item.id} className={unlocked.includes(item.id) ? "on" : ""} />
              ))}
            </div>
            {complete ? (
              <>
                <button onClick={reset}>Play again</button>
                <button onClick={onExit}>Done</button>
              </>
            ) : solvedCurrent ? (
              <button onClick={nextCase}>Next record</button>
            ) : (
              <button onClick={() => choose(current.answer)}>Show me</button>
            )}
          </div>
        </div>

        <div className="alien-archive-panel">
          <div className="alien-archive-tabs" aria-label="Field guide entries">
            {ALIEN_LANGUAGE_CASES.map((item, index) => (
              <button
                key={item.id}
                className={selectedCard.id === item.id ? "on" : ""}
                onClick={() => setSelectedCardId(item.id)}
              >
                {unlocked.includes(item.id) ? `0${index + 1}` : "--"}
              </button>
            ))}
          </div>
          <AlienFieldGuideCard
            item={selectedCard}
            unlocked={unlocked.includes(selectedCard.id)}
          />
        </div>
      </div>
    </section>
  );
}

// ---------- Constellation connect-the-dots ----------
// Drag (click points) on the overview backdrop to draw a custom constellation.
