(function (global) {
  'use strict';

  var DAYS = ['SUN','MON','TUE','WED','THU','FRI','SAT'];
  var TIMER_SLOTS = ['TIMER1','TIMER2','TIMER3','TIMER4','TIMER5','TIMER6','TIMER7']; // adjust if your firmware supports more

  function ensureB2B() {
    if (typeof b2bapis === 'undefined' || !b2bapis.b2bcontrol) {
      throw new Error('B2B APIs not available (partner cert/privileges required on Signage).');
    }
  }
  function noop() {}
  function okOr(fn){ return typeof fn === 'function' ? fn : noop; }
  function errOr(fn){ return typeof fn === 'function' ? fn : function(e){ try{console.log(e);}catch(_){}}; }

  // "Mon" | "MON" | "Monday" → "MON"
  function dayCode(d) {
    var s = (d + '').toUpperCase();
    if (s.indexOf('MON') === 0) return 'MON';
    if (s.indexOf('TUE') === 0) return 'TUE';
    if (s.indexOf('WED') === 0) return 'WED';
    if (s.indexOf('THU') === 0) return 'THU';
    if (s.indexOf('FRI') === 0) return 'FRI';
    if (s.indexOf('SAT') === 0) return 'SAT';
    if (s.indexOf('SUN') === 0) return 'SUN';
    return null;
  }

  // ['Mon','Tue','Wed'] → "MON:TUE:WED"
  function toDaysString(daysArr) {
    var out = [], i, c;
    for (i = 0; i < daysArr.length; i++) {
      c = dayCode(daysArr[i]);
      if (c) out.push(c);
    }
    return out.join(':');
  }

  // "07:00" strict validation
  function isHHMM(t){ return /^\d{2}:\d{2}$/.test(t); }

  // Core setter for one item on a given slot
  // item = { type:'on'|'off', days:[...], time:'HH:MM' }
  function setOnSlot(slot, item, success, error) {
    var ok = okOr(success), err = errOr(error);
    if (!isHHMM(item.time)) return err(new Error('time must be "HH:MM"'));
    var daysStr = toDaysString(item.days || []);
    if (!daysStr) return err(new Error('days array empty/invalid'));
    try {
      if (!b2bapis.b2bcontrol.setOnTimerRepeat) return err(new Error('setOnTimerRepeat not available'));
      b2bapis.b2bcontrol.setOnTimerRepeat(slot, item.time, 'TIMER_MANUAL', daysStr, ok, err);
    } catch (e) { err(e); }
  }
  
  function unsetOnSlot(slot, item, success, error) {
	    var ok = okOr(success), err = errOr(error);
	    if (!isHHMM(item.time)) return err(new Error('time must be "HH:MM"'));
	    var daysStr = toDaysString(item.days || []);
	    if (!daysStr) return err(new Error('days array empty/invalid'));
	    try {
	      if (!b2bapis.b2bcontrol.setOnTimerRepeat) return err(new Error('setOnTimerRepeat not available'));
	      b2bapis.b2bcontrol.setOnTimerRepeat(slot, item.time, 'TIMER_OFF', daysStr, ok, err);
	    } catch (e) { err(e); }
	  }

  // Set one OFF item on a given slot
  function setOffSlot(slot, item, success, error) {
    var ok = okOr(success), err = errOr(error);
    if (!isHHMM(item.time)) return err(new Error('time must be "HH:MM"'));
    var daysStr = toDaysString(item.days || []);
    if (!daysStr) return err(new Error('days array empty/invalid'));
    try {
      if (!b2bapis.b2bcontrol.setOffTimerRepeat) return err(new Error('setOffTimerRepeat not available'));
      b2bapis.b2bcontrol.setOffTimerRepeat(slot, item.time, 'TIMER_MANUAL', daysStr, ok, err);
    } catch (e) { err(e); }
  }
  
  // Set one OFF item on a given slot
  function unsetOffSlot(slot, item, success, error) {
    var ok = okOr(success), err = errOr(error);
    if (!isHHMM(item.time)) return err(new Error('time must be "HH:MM"'));
    var daysStr = toDaysString(item.days || []);
    if (!daysStr) return err(new Error('days array empty/invalid'));
    try {
      if (!b2bapis.b2bcontrol.setOffTimerRepeat) return err(new Error('setOffTimerRepeat not available'));
      b2bapis.b2bcontrol.setOffTimerRepeat(slot, item.time, 'TIMER_OFF', daysStr, ok, err);
    } catch (e) { err(e); }
  }

  // Program an array of items, auto-assigning TIMERn slots (one item per slot).
  // opts.startAt = index in TIMER_SLOTS to start from (default 0 => TIMER1)
  function programTimers(items, opts, done, fail) {
	    ensureB2B();
	    var ok = okOr(done), err = errOr(fail);

	    if (!items || !items.length) { ok(); return; }

	    var startAt = (opts && typeof opts.startAt === 'number') ? opts.startAt : 0;
	    var startAtOn  = (opts && typeof opts.startAtOn  === 'number') ? opts.startAtOn  : startAt;
	    var startAtOff = (opts && typeof opts.startAtOff === 'number') ? opts.startAtOff : startAt;

	    // Partition by type, preserve order
	    var ons = [], offs = [], i, it;
	    for (i = 0; i < items.length; i++) {
	      it = items[i];
	      if (!it || !it.type) { err(new Error('Each item needs a type')); return; }
	      if (it.type === 'on')  ons.push(it);
	      else if (it.type === 'off') offs.push(it);
	      else { err(new Error('Unknown type "' + it.type + '"')); return; }
	    }

	    // Build job list: all ONs (with their own slot index), then all OFFs
	    var jobs = [];
	    for (i = 0; i < ons.length; i++) {
	      (function(idx){
	        var slotIdx = startAtOn + idx;
	        if (slotIdx >= TIMER_SLOTS.length) { jobs.push(function(n,f){ f(new Error('Out of TIMER slots for ON group')); }); return; }
	        var slot = TIMER_SLOTS[slotIdx];
	        var item = ons[idx];
	        jobs.push(function(next, failCb){ setOnSlot(slot, item, next, failCb); });
	      }(i));
	    }
	    for (i = 0; i < offs.length; i++) {
	      (function(idx){
	        var slotIdx = startAtOff + idx;
	        if (slotIdx >= TIMER_SLOTS.length) { jobs.push(function(n,f){ f(new Error('Out of TIMER slots for OFF group')); }); return; }
	        var slot = TIMER_SLOTS[slotIdx];
	        var item = offs[idx];
	        jobs.push(function(next, failCb){ setOffSlot(slot, item, next, failCb); });
	      }(i));
	    }

	    // Run sequentially
	    var j = 0;
	    (function runNext(){
	      if (j >= jobs.length) { ok(); return; }
	      try { jobs[j++](runNext, function(e){ err(e); runNext(); }); }
	      catch (e) { err(e); runNext(); }
	    }());
	  }

  // Optional: overwrite a slot to a harmless schedule (e.g., Sunday 00:00)
  function resetTimerSlot(slot, done, fail) {
	    ensureB2B();
	    var pending = 2, ok = okOr(done), err = errOr(fail);
	    function stepDone(){ if(--pending===0) ok(); }
	    function stepErr(e){ try{console.log('reset error', e);}catch(_){ } stepDone(); if (fail) err(e); }

	    unsetOffSlot(slot, {type:'off', days:['SUN'], time:'00:00'}, stepDone, stepErr);
	    unsetOnSlot( slot, {type:'on',  days:['SUN'], time:'00:00'}, stepDone, stepErr);
	  }


  var B2BHelper = {
    ready: ensureB2B,

    setOnSlot: setOnSlot,
    setOffSlot: setOffSlot,
    programTimers: programTimers,
    resetTimerSlot: resetTimerSlot,

    // Reboot / powerOff / serial (unchanged, for completeness)
    reboot: function (success, error) {
      ensureB2B(); var cb = cbPair(success, error);
      try { b2bapis.b2bcontrol.rebootDevice(cb.ok, cb.fail); } catch (e) { cb.fail(e); }
    },
    powerOff: function (success, error) {
      ensureB2B(); var cb = cbPair(success, error);
      try {
        if (!b2bapis.b2bpower || !b2bapis.b2bpower.setPowerOff) throw new Error('b2bpower.setPowerOff not available.');
        b2bapis.b2bpower.setPowerOff(cb.ok, cb.fail);
      } catch (e) { cb.fail(e); }
    },
    getSerialNumber: function (success, error) {
      ensureB2B(); var cb = cbPair(success, error);
      try { cb.ok(b2bapis.b2bcontrol.getSerialNumber()); } catch (e) { cb.fail(e); }
    }
  };

  global.B2BHelper = B2BHelper;
}(this));