import { Invoke } from './utils.mjs';
import { Pattern, noteToMidi } from '@strudel/core';

const ON_MESSAGE = 0x90;
const OFF_MESSAGE = 0x80;
const CC_MESSAGE = 0xb0;

Pattern.prototype.midi = function (output) {
  return this.onTrigger((time, hap, currentTime, cps) => {
    let { note, nrpnn, nrpv, ccn, ccv, velocity = 0.9, gain = 1 } = hap.value;
    const offset = (time - currentTime) * 1000;
    velocity = Math.floor(gain * velocity * 100);
    const duration = Math.floor((hap.duration.valueOf() / cps) * 1000 - 10);
    const roundedOffset = Math.round(offset);
    const midichan = (hap.value.midichan ?? 1) - 1;
    const requestedport = output ?? 'IAC';
    const messagesfromjs = [];
    if (note != null) {
      const midiNumber = typeof note === 'number' ? note : noteToMidi(note);
      messagesfromjs.push({
        requestedport,
        message: [ON_MESSAGE + midichan, midiNumber, velocity],
        offset: roundedOffset,
      });
      messagesfromjs.push({
        requestedport,
        message: [OFF_MESSAGE + midichan, midiNumber, velocity],
        offset: roundedOffset + duration,
      });
    }
    if (ccv && ccn) {
      if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) {
        throw new Error('expected ccv to be a number between 0 and 1');
      }
      if (!['string', 'number'].includes(typeof ccn)) {
        throw new Error('expected ccn to be a number or a string');
      }
      const scaled = Math.round(ccv * 127);
      messagesfromjs.push({
        requestedport,
        message: [CC_MESSAGE + midichan, ccn, scaled],
        offset: roundedOffset,
      });
    }
    // invoke is temporarily blocking, run in an async process
    if (messagesfromjs.length) {
      setTimeout(() => {
        Invoke('sendmidi', { messagesfromjs });
      });
    }
  });
};
