/**
 * TrialEngine - Spreadsheet-driven trial sequencer.
 * Replicates Gorilla's display system: iterates through trial rows,
 * binds variables to UI elements, and manages screen flow.
 */
class TrialEngine {
  /**
   * @param {Object} options
   * @param {Array<Object>} options.trials - Array of trial row objects
   * @param {boolean} options.randomize - Randomize trial order (default: false)
   * @param {function} options.onTrialStart - Called with (trialData, trialIndex, totalTrials)
   * @param {function} options.onTrialEnd - Called with (trialData, response, trialIndex)
   * @param {function} options.onComplete - Called when all trials are done
   * @param {number} options.breakEvery - Show break screen every N trials (0 = no breaks)
   * @param {function} options.onBreak - Called with (trialIndex, totalTrials) for break screen
   */
  constructor(options) {
    this.trials = [...options.trials];
    this.randomize = options.randomize || false;
    this.onTrialStart = options.onTrialStart || (() => {});
    this.onTrialEnd = options.onTrialEnd || (() => {});
    this.onComplete = options.onComplete || (() => {});
    this.breakEvery = options.breakEvery || 0;
    this.onBreak = options.onBreak || null;

    this.currentIndex = 0;
    this.running = false;

    if (this.randomize) {
      this._shuffle(this.trials);
    }
  }

  /** Get total number of trials */
  getTotal() {
    return this.trials.length;
  }

  /** Get current trial index (0-based) */
  getCurrentIndex() {
    return this.currentIndex;
  }

  /** Get current trial data */
  getCurrentTrial() {
    return this.trials[this.currentIndex] || null;
  }

  /** Start the trial sequence */
  start() {
    this.currentIndex = 0;
    this.running = true;
    this._runTrial();
  }

  /** Record response for current trial and advance */
  respond(response) {
    if (!this.running) return;

    const trial = this.trials[this.currentIndex];
    this.onTrialEnd(trial, response, this.currentIndex);

    this.currentIndex++;

    // Check for break
    if (
      this.breakEvery > 0 &&
      this.currentIndex < this.trials.length &&
      this.currentIndex % this.breakEvery === 0 &&
      this.onBreak
    ) {
      this.onBreak(this.currentIndex, this.trials.length);
      return; // Wait for resumeAfterBreak()
    }

    if (this.currentIndex >= this.trials.length) {
      this.running = false;
      this.onComplete();
    } else {
      this._runTrial();
    }
  }

  /** Resume after a break screen */
  resumeAfterBreak() {
    if (this.currentIndex < this.trials.length) {
      this._runTrial();
    }
  }

  /** Skip to a specific trial index */
  skipTo(index) {
    if (index >= 0 && index < this.trials.length) {
      this.currentIndex = index;
      this._runTrial();
    }
  }

  // --- Audio Preloading ---

  /**
   * Preload audio files for upcoming trials.
   * @param {string} column - Spreadsheet column containing audio filenames
   * @param {string} basePath - Base path for stimuli
   * @param {number} ahead - Number of trials ahead to preload
   * @returns {Map<string, HTMLAudioElement>}
   */
  preloadAudio(column, basePath, ahead = 3) {
    const cache = new Map();
    const start = this.currentIndex;
    const end = Math.min(start + ahead, this.trials.length);

    for (let i = start; i < end; i++) {
      const filename = this.trials[i][column];
      if (filename && !cache.has(filename)) {
        const audio = new Audio(`${basePath}/${filename}`);
        audio.preload = 'auto';
        cache.set(filename, audio);
      }
    }
    return cache;
  }

  // Private

  _runTrial() {
    const trial = this.trials[this.currentIndex];
    this.onTrialStart(trial, this.currentIndex, this.trials.length);
  }

  _shuffle(arr) {
    for (let i = arr.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
  }
}
