#!/usr/bin/env node
import {
  importTypeScriptModule
} from "./chunk-XKCJBIRY.mjs";
import {
  external_exports
} from "./chunk-JZTH3EMV.mjs";

// src/services/project/reducer-bundle-preflight.ts
import path from "path";
import { isPerPlayer, perPlayerSchema } from "@dreamboard-games/sdk/reducer";
import "@dreamboard-games/sdk/reducer-contract";
import { materializeManifestTable } from "@dreamboard-games/sdk/codegen";
globalThis.__DREAMBOARD_AUTHORING_WARNINGS__ = true;
var REDUCER_BUNDLE_ENTRY_PATH = path.join("app", "index.ts");
var PREFLIGHT_RNG_SEED = 1337;
function buildPlayerIds(playerCount) {
  const ids = [];
  for (let index = 1; index <= playerCount; index += 1) {
    ids.push(`player-${index}`);
  }
  return ids;
}
function buildScenarios(manifest) {
  const rawCounts = [
    manifest.players?.minPlayers,
    manifest.players?.maxPlayers
  ].filter(
    (count) => typeof count === "number" && Number.isFinite(count) && count > 0
  );
  const playerCounts = Array.from(new Set(rawCounts));
  if (playerCounts.length === 0) {
    return [];
  }
  const profiles = manifest.setupProfiles ?? [];
  const profileIds = profiles.length > 0 ? profiles.map((profile) => profile.id) : [null];
  const scenarios = [];
  const seen = /* @__PURE__ */ new Set();
  for (const profileId of profileIds) {
    for (const count of playerCounts) {
      const key = `${profileId ?? "<default>"}:${count}`;
      if (seen.has(key)) continue;
      seen.add(key);
      scenarios.push({ setupProfileId: profileId, playerCount: count });
    }
  }
  return scenarios;
}
function describeScenario(scenario) {
  const profileDesc = scenario.setupProfileId ? `setup profile '${scenario.setupProfileId}'` : "no setup profile";
  return `${profileDesc}, ${scenario.playerCount} player${scenario.playerCount === 1 ? "" : "s"}`;
}
function summarizeError(error) {
  if (error instanceof Error) {
    const firstLine = error.message.split("\n").map((line) => line.trim()).find((line) => line.length > 0) ?? "Unknown failure";
    const detail = error.stack && error.stack !== error.message ? error.stack : void 0;
    return { headline: firstLine, detail };
  }
  return { headline: String(error ?? "Unknown failure") };
}
function assertViewPerPlayerSeatsValid(view, expectedPlayerIds, breadcrumb = []) {
  if (view === null || view === void 0) {
    return null;
  }
  if (isPerPlayer(view)) {
    const schema = perPlayerSchema(external_exports.unknown(), {
      players: expectedPlayerIds
    });
    const result = schema.safeParse(view);
    if (!result.success) {
      const firstIssue = result.error.issues[0];
      const location = breadcrumb.length > 0 ? ` at view.${breadcrumb.join(".")}` : "";
      const message = firstIssue?.message ?? "PerPlayer seat mismatch";
      return `PerPlayer seat mismatch${location}: ${message}`;
    }
    return null;
  }
  if (Array.isArray(view)) {
    for (let index = 0; index < view.length; index += 1) {
      const result = assertViewPerPlayerSeatsValid(
        view[index],
        expectedPlayerIds,
        [...breadcrumb, `[${index}]`]
      );
      if (result) return result;
    }
    return null;
  }
  if (typeof view === "object") {
    for (const [key, value] of Object.entries(
      view
    )) {
      const result = assertViewPerPlayerSeatsValid(value, expectedPlayerIds, [
        ...breadcrumb,
        key
      ]);
      if (result) return result;
    }
    return null;
  }
  return null;
}
function identityShuffle(values) {
  return [...values];
}
async function driveReducerBundleThroughScenarios(options) {
  const { manifest, bundle, scenarios } = options;
  const rngSeed = options.rngSeed ?? PREFLIGHT_RNG_SEED;
  const failures = [];
  const tableCache = /* @__PURE__ */ new Map();
  for (const scenario of scenarios) {
    let tableEntry = tableCache.get(scenario.playerCount);
    if (!tableEntry) {
      try {
        const playerIds2 = buildPlayerIds(scenario.playerCount);
        const table2 = JSON.parse(
          JSON.stringify(
            materializeManifestTable({
              manifest,
              playerIds: playerIds2,
              shuffleItems: identityShuffle
            })
          )
        );
        tableEntry = { table: table2, playerIds: playerIds2 };
      } catch (error) {
        tableEntry = { error };
      }
      tableCache.set(scenario.playerCount, tableEntry);
    }
    if ("error" in tableEntry) {
      const summary = summarizeError(tableEntry.error);
      failures.push({
        scenario,
        phase: "BUILD_GAME_STATE",
        headline: summary.headline,
        detail: summary.detail
      });
      continue;
    }
    const { table, playerIds } = tableEntry;
    let sessionState;
    try {
      sessionState = await bundle.initialize({
        table,
        playerIds: [...playerIds],
        rngSeed,
        setup: scenario.setupProfileId ? { profileId: scenario.setupProfileId, optionValues: {} } : null
      });
    } catch (error) {
      const summary = summarizeError(error);
      failures.push({
        scenario,
        phase: "INITIALIZE",
        headline: summary.headline,
        detail: summary.detail
      });
      continue;
    }
    let projection;
    try {
      projection = bundle.projectSeatsDynamic({
        state: sessionState,
        playerIds: [...playerIds]
      });
    } catch (error) {
      const summary = summarizeError(error);
      failures.push({
        scenario,
        phase: "PROJECT_SEATS",
        headline: summary.headline,
        detail: summary.detail
      });
      continue;
    }
    const seats = projection?.seats ?? {};
    for (const playerId of playerIds) {
      const seat = seats[playerId];
      if (!seat) {
        failures.push({
          scenario,
          phase: "PROJECT_SEATS",
          playerId,
          headline: `projectSeatsDynamic() did not return an entry for seat '${playerId}'.`
        });
        continue;
      }
      const mismatch = assertViewPerPlayerSeatsValid(seat.view, playerIds);
      if (mismatch) {
        failures.push({
          scenario,
          phase: "VALIDATE_VIEW",
          playerId,
          headline: mismatch
        });
      }
    }
  }
  return failures;
}
async function runReducerBundleSmoke(options) {
  const { projectRoot, manifest } = options;
  const scenarios = buildScenarios(manifest);
  if (scenarios.length === 0) {
    return [];
  }
  const entryPath = path.join(projectRoot, REDUCER_BUNDLE_ENTRY_PATH);
  let module;
  try {
    module = await importTypeScriptModule(
      entryPath
    );
  } catch (error) {
    const summary = summarizeError(error);
    throw new Error(
      [
        `Dreamboard could not import \`${REDUCER_BUNDLE_ENTRY_PATH}\` during \`dreamboard sync\`.`,
        "Fix the reducer bundle entry so it can be imported locally, then run `dreamboard sync` again.",
        `Original error: ${summary.headline}`
      ].join(" ")
    );
  }
  const bundle = module.default;
  if (!bundle || typeof bundle.initialize !== "function") {
    throw new Error(
      [
        `\`${REDUCER_BUNDLE_ENTRY_PATH}\` does not export a reducer bundle as its default export.`,
        "Export `createReducerBundle(game)` from `app/index.ts` so the compile pipeline can smoke-test it."
      ].join(" ")
    );
  }
  return driveReducerBundleThroughScenarios({ manifest, bundle, scenarios });
}
function formatFailureLines(failures) {
  return failures.map((failure) => {
    const location = failure.playerId ? ` (seat ${failure.playerId})` : "";
    return `\u2022 [${failure.phase}] ${describeScenario(failure.scenario)}${location}: ${failure.headline}`;
  }).join("\n");
}
async function assertReducerBundleSmoke(options) {
  const failures = await runReducerBundleSmoke(options);
  if (failures.length === 0) {
    return;
  }
  throw new Error(
    [
      "Reducer bundle preflight failed during `dreamboard sync`.",
      "Fix the reported scenarios locally before syncing so the backend does not catch them after start-game:",
      formatFailureLines(failures)
    ].join("\n")
  );
}

export {
  buildScenarios,
  assertViewPerPlayerSeatsValid,
  driveReducerBundleThroughScenarios,
  runReducerBundleSmoke,
  assertReducerBundleSmoke
};
//# sourceMappingURL=chunk-Z6OZWUIZ.mjs.map