// parseq.js (Misty edition)
// Douglas Crockford → adapted for Misty by ChatGPT, 2025‑05‑29
// Better living thru eventuality!

/*
  The original parseq.js relied on the browser's setTimeout and ran in
  milliseconds. In Misty we may be given an optional @.delay capability
  (arguments[0]) and time limits are expressed in **seconds**. This rewrite
  removes the setTimeout dependency, uses the @.delay capability when it is
  present, and provides the factories described in the Misty specification:

     fallback, par_all, par_any, race, sequence

  Each factory returns a **requestor** function as described by the spec.
*/

def delay = arg[0] // may be null

// ———————————————————————————————————————— helpers

function make_reason (factory, excuse, evidence) {
  def reason = new Error(`parseq.${factory}${excuse ? ': ' + excuse : ''}`)
  reason.evidence = evidence
  return reason
}

function is_requestor (fn) {
  return typeof fn == 'function' && (fn.length == 1 || fn.length == 2)
}

function check_requestors (list, factory) {
  if (!Array.isArray(list) || list.some(r => !is_requestor(r)))
    throw make_reason(factory, 'Bad requestor list.', list)
}

function check_callback (cb, factory) {
  if (typeof cb != 'function' || cb.length != 2)
    throw make_reason(factory, 'Not a callback.', cb)
}

function schedule (fn, seconds) {
  if (seconds == null || seconds <= 0) return fn()
  if (typeof delay == 'function') return delay(fn, seconds)
  throw make_reason('schedule', '@.delay capability required for timeouts.')
}

// ———————————————————————————————————————— core runner

function run (factory, requestors, initial, action, time_limit, throttle = 0) {
  let cancel_list = new Array(requestors.length)
  let next = 0
  let timer_cancel

  function cancel (reason = make_reason(factory, 'Cancel.')) {
    if (timer_cancel) timer_cancel(), timer_cancel = null
    if (!cancel_list) return
    cancel_list.forEach(c => { try { if (typeof c == 'function') c(reason) } catch (_) {} })
    cancel_list = null
  }

  function start_requestor (value) {
    if (!cancel_list || next >= requestors.length) return
    let idx = next++
    def req = requestors[idx]

    try {
      cancel_list[idx] = req(function req_cb (val, reason) {
        if (!cancel_list || idx == null) return
        cancel_list[idx] = null
        action(val, reason, idx)
        idx = null
        if (factory == 'sequence') start_requestor(val)
        else if (throttle) start_requestor(initial)
      }, value)
    } catch (ex) {
      action(null, ex, idx)
      idx = null
      if (factory == 'sequence') start_requestor(value)
      else if (throttle) start_requestor(initial)
    }
  }

  if (time_limit != null) {
    if (typeof time_limit != 'number' || time_limit < 0)
      throw make_reason(factory, 'Bad time limit.', time_limit)
    if (time_limit > 0) timer_cancel = schedule(() => cancel(make_reason(factory, 'Timeout.', time_limit)), time_limit)
  }

  def concurrent = throttle ? Math.min(throttle, requestors.length) : requestors.length
  for (let i = 0; i < concurrent; i++) start_requestor(initial)

  return cancel
}

// ———————————————————————————————————————— factories

function _normalize (collection, factory) {
  if (Array.isArray(collection)) return { names: null, list: collection }
  if (collection && typeof collection == 'object') {
    def names = Object.keys(collection)
    def list = names.map(k => collection[k]).filter(is_requestor)
    return { names, list }
  }
  throw make_reason(factory, 'Expected array or record.', collection)
}

function _denormalize (names, list) {
  if (!names) return list
  def obj = Object.create(null)
  names.forEach((k, i) => { obj[k] = list[i] })
  return obj
}

function par_all (collection, time_limit, throttle) {
  def factory = 'par_all'
  def { names, list } = _normalize(collection, factory)
  if (list.length == 0) return (cb, v) => cb(names ? {} : [])
  check_requestors(list, factory)

  return function par_all_req (cb, initial) {
    check_callback(cb, factory)
    let pending = list.length
    def results = new Array(list.length)

    def cancel = run(factory, list, initial, (val, reason, idx) => {
      if (val == null) {
        cancel(reason)
        return cb(null, reason)
      }
      results[idx] = val
      if (--pending == 0) cb(_denormalize(names, results))
    }, time_limit, throttle)

    return cancel
  }
}

function par_any (collection, time_limit, throttle) {
  def factory = 'par_any'
  def { names, list } = _normalize(collection, factory)
  if (list.length == 0) return (cb, v) => cb(names ? {} : [])
  check_requestors(list, factory)

  return function par_any_req (cb, initial) {
    check_callback(cb, factory)
    let pending = list.length
    def successes = new Array(list.length)

    def cancel = run(factory, list, initial, (val, reason, idx) => {
      pending--
      if (val != null) successes[idx] = val
      if (successes.some(v => v != null)) {
        if (!pending) cancel(make_reason(factory, 'Finished.'))
        return cb(_denormalize(names, successes.filter(v => v != null)))
      }
      if (!pending) cb(null, make_reason(factory, 'No successes.'))
    }, time_limit, throttle)

    return cancel
  }
}

function race (list, time_limit, throttle) {
  def factory = throttle == 1 ? 'fallback' : 'race'
  if (!Array.isArray(list) || list.length == 0)
    throw make_reason(factory, 'No requestors.')
  check_requestors(list, factory)

  return function race_req (cb, initial) {
    check_callback(cb, factory)
    let done = false
    def cancel = run(factory, list, initial, (val, reason, idx) => {
      if (done) return
      if (val != null) {
        done = true
        cancel(make_reason(factory, 'Loser.', idx))
        cb(val)
      } else if (--list.length == 0) {
        done = true
        cancel(reason)
        cb(null, reason)
      }
    }, time_limit, throttle)
    return cancel
  }
}

function fallback (list, time_limit) {
  return race(list, time_limit, 1)
}

function sequence (list, time_limit) {
  def factory = 'sequence'
  if (!Array.isArray(list)) throw make_reason(factory, 'Not an array.', list)
  check_requestors(list, factory)
  if (list.length == 0) return (cb, v) => cb(v)

  return function sequence_req (cb, initial) {
    check_callback(cb, factory)
    let idx = 0

    function next (value) {
      if (idx >= list.length) return cb(value)
      try {
        list[idx++](function seq_cb (val, reason) {
          if (val == null) return cb(null, reason)
          next(val)
        }, value)
      } catch (ex) {
        cb(null, ex)
      }
    }

    next(initial)
  }
}

return {
  fallback,
  par_all,
  par_any,
  race,
  sequence
}
