// m07 io: effects as values. An IO is a plain-data description of a program; nothing runs until interpreted.
// {tag:'Pure', value} - done, with a value
// {tag:'Effect', op, k} - perform `op`, then continue with `k(result)`
/** @import { Op, Shape, Scene, IO, Trace } from './types' */
/** @type {(a: A) => IO} */
export const pure = (value) => ({ tag: 'Pure', value })
/** @type {(op: Op) => IO} */
export const effect = (op) => ({ tag: 'Effect', op, k: pure })
// Pure: apply f now. Effect: keep the op, and extend the continuation so f runs after k.
// Nothing is performed here, we only build a bigger description.
/** @type {(io: IO, f: (a: A) => IO) => IO} */
export const flatMap = (io, f) =>
io.tag === 'Pure'
? f(io.value)
: { tag: 'Effect', op: io.op, k: (result) => flatMap(io.k(result), f) }
/** @type {(io: IO, f: (a: A) => B) => IO} */
export const map = (io, f) => flatMap(io, (a) => pure(f(a)))
/** @type {(ios: IO[]) => IO} */
export const sequence = (ios) =>
ios.reduceRight((rest, io) => flatMap(io, () => rest), pure(undefined))
/** @type {(shape: Shape) => IO} */
export const draw = (shape) => effect({ tag: 'Draw', shape })
/** @type {IO} */
export const randomSeed = effect({ tag: 'RandomSeed' })
/** @type {(scene: Scene) => IO} */
export const drawScene = (scene) => sequence(scene.map(draw))
// m10: the stored best score. Reading it answers a number; saving answers nothing, like a draw.
/** @type {IO} */
export const loadBest = effect({ tag: 'LoadBest' })
/** @type {(score: number) => IO} */
export const saveBest = (score) => effect({ tag: 'SaveBest', score })
// Answer each op with a canned result, collecting the ops as we go (recursion, no mutation).
// `best` (m10) answers LoadBest, the way `seed` answers RandomSeed; it defaults to 0 so m07 calls stay valid.
const answer = (op, seed, best) =>
op.tag === 'RandomSeed' ? seed : op.tag === 'LoadBest' ? best : undefined
/** @type {(io: IO, seed: number, best?: number) => Trace} */
export const trace = (io, seed, best = 0) => {
if (io.tag === 'Pure') return { result: io.value, ops: [] }
const rest = trace(io.k(answer(io.op, seed, best)), seed, best)
return { result: rest.result, ops: [io.op, ...rest.ops] }
}