'use strict';
/**
 * Loop precheck — central entry point for "should this periodic loop spend
 * an LLM call this tick?". Borrowed conceptually from OpenClaw's
 * `isHeartbeatContentEffectivelyEmpty` (src/auto-reply/heartbeat.ts:32-60),
 * generalized for Wall-E's scheduler-driven fleet.
 *
 * Today each loop has its own bespoke early-exit:
 *   think.js: `if (pending.length === 0) return`
 *   reflect.js: `if (existing) return`
 *   initiative.js: `if (isStateEmpty(snapshot)) return`
 *   skills/skill-planner: heuristic per-skill cooldown/backoff
 *
 * The precheck module gives loops a shared shape:
 *   1. Provider available? (skip if not configured)
 *   2. Caller's hasFreshSignal() predicate (skip if no new input)
 *   3. (Optional) workspace contract file content empty? (skip if so)
 *
 * Each step short-circuits with a structured `{run: false, reason}` so
 * telemetry can attribute "why didn't this loop fire" without parsing logs.
 */

let _providerAvailability = null;
function _getProviderAvailability() {
  if (_providerAvailability) return _providerAvailability;
  try {
    _providerAvailability = require('../llm/provider-availability').default;
  } catch {
    _providerAvailability = null;
  }
  return _providerAvailability;
}

/**
 * Decide whether a loop should run this tick.
 *
 * @param {Object} params
 * @param {string} params.loopName       Short loop name, used in skip reason.
 * @param {Function} [params.hasFreshSignal]
 *        () => boolean — return true if the loop has new input since last
 *        run. If false, returns {run:false, reason:'no-fresh-signal'}.
 * @param {boolean} [params.skipProviderCheck=false]
 *        Set true for loops that don't need an LLM (e.g. dedup, backfill).
 * @param {Function} [params.providerAvailabilityForTest]
 *        Override for tests that don't want to bind to the real module.
 * @returns {{run: boolean, reason?: string}}
 */
function shouldRunLoop({
  loopName,
  hasFreshSignal,
  skipProviderCheck = false,
  providerAvailabilityForTest,
} = {}) {
  if (!loopName) throw new Error('shouldRunLoop requires loopName');

  if (!skipProviderCheck) {
    const pa = providerAvailabilityForTest || _getProviderAvailability();
    if (pa) {
      // Only enforce if at least one provider is configured; if none are,
      // local-only operation is fine and provider availability is N/A.
      if (pa.getConfiguredProviders && pa.getConfiguredProviders().length > 0) {
        if (pa.isAnyProviderAvailable && !pa.isAnyProviderAvailable()) {
          return { run: false, reason: 'no-provider' };
        }
      }
    }
  }

  if (typeof hasFreshSignal === 'function') {
    let fresh;
    try {
      fresh = !!hasFreshSignal();
    } catch (e) {
      // A throwing freshness check shouldn't crash the loop — be safe
      // and run anyway, but log the cause so the bug is visible.
      console.warn(`[loop-precheck:${loopName}] hasFreshSignal threw: ${e.message}; running anyway`);
      fresh = true;
    }
    if (!fresh) return { run: false, reason: 'no-fresh-signal' };
  }

  return { run: true };
}

/**
 * Build a skip-result object that loops can return verbatim. Adds the
 * loop name so the scheduler.onResult handler can log + telemetry.
 */
function skipped({ loopName, reason }) {
  return { skipped: true, loop: loopName, reason };
}

module.exports = {
  shouldRunLoop,
  skipped,
};
