const initialState={show:null,currentSlide:0,scores:[0,0],revealState:{},showScoreboard:true,scoreboardPosition:'bottom',showShortcuts:false,showHostPanel:false,muted:true,guests:0,redFlags:[],liveMode:false,showSetlistEditor:false,extrasBypassed:false,bonusSections:[]};

// Slide types hidden when the operator flips BYPASS EXTRAS.
// Matches warm-up (fishbowl + red flags), fluency test, and dead-or-alive triplet.
// tier_title is intentionally NOT here — it's the in-round instructions slide.
const EXTRAS_TYPES={fishbowl:1,red_flag:1,fluency_line:1,fluency_source:1,doa_ref:1,doa_vote:1,doa_verdict_dead:1,doa_verdict_alive:1};
// Slides that are structural/titling only — a section with nothing but these
// after filtering extras is dropped entirely.
const DECORATION_TYPES={round_title:1,tier_title:1,score:1,bonus_marker:1};

function isExtraType(t){return !!EXTRAS_TYPES[t]}
function isDecorationType(t){return !!DECORATION_TYPES[t]}

function sectionSurvivesBypass(sec){
  if(!sec)return false;
  var slides=sec.slides||[];
  // Section-level rule: drop the whole section if it's a fluency or DOA section.
  // Detect by slots.mode when present (template-level metadata) OR by slide content
  // (built decks strip slots.mode, so we look for the characteristic slide types).
  if(sec.slots&&(sec.slots.mode==='fluency_pair'||sec.slots.mode==='doa_triplet'))return false;
  for(var k=0;k<slides.length;k++){
    var kt=slides[k].type;
    if(kt==='fluency_line'||kt==='fluency_source'||kt==='doa_ref'||kt==='doa_vote'||kt==='doa_verdict_dead'||kt==='doa_verdict_alive')return false;
  }
  // Slide-level fallback: a section survives if it contains anything that is
  // neither an extras type nor pure decoration.
  for(var i=0;i<slides.length;i++){
    var t=slides[i].type;
    if(isExtraType(t))continue;
    if(!isDecorationType(t))return true;
  }
  return false;
}

// Returns [{sec, originalIdx}] after applying the extras-bypass rule.
// Each returned sec is a shallow clone with extras filtered from slides.
function activeSections(show,extrasBypassed){
  if(!show||!Array.isArray(show.sections))return[];
  if(!extrasBypassed)return show.sections.map(function(sec,i){return{sec:sec,originalIdx:i}});
  var out=[];
  for(var i=0;i<show.sections.length;i++){
    var sec=show.sections[i];
    if(!sectionSurvivesBypass(sec))continue;
    var filtered={...sec,slides:(sec.slides||[]).filter(function(sl){return!isExtraType(sl.type)})};
    out.push({sec:filtered,originalIdx:i});
  }
  return out;
}

// Expand a section's slide list, injecting an auto-generated answer_sheet if
// the section contains question slides. Mirrored from the main flatMap pass
// so that ephemeral bonus sections get the same treatment as baked-in sections.
function expandSectionSlides(sec,show){
  var slides=(sec&&sec.slides)||[];
  var answers=[];
  slides.forEach(function(sl){
    if(sl.type==='source_q'&&sl.content&&sl.content.answer)answers.push(sl.content.answer);
    else if(sl.type==='acronym_q'&&sl.content&&sl.content.answer)answers.push(sl.content.answer);
  });
  if(answers.length===0||(show&&show.showAnswerSheets===false))return slides.slice();
  var sheet={id:'as-'+(sec.id||sec.name),type:'answer_sheet',content:{primary:(sec.name||'')+' ANSWERS',secondary:answers.join(' | ')}};
  var result=slides.slice();
  var crateIdx=result.findIndex(function(sl){return sl.type==='crate_drop'});
  if(crateIdx>=0)result.splice(crateIdx+1,0,sheet);
  else{var scoreIdx=result.findIndex(function(sl){return sl.type==='score'});if(scoreIdx>=0)result.splice(scoreIdx+1,0,sheet);else result.push(sheet)}
  return result;
}

// flattenSlides builds the linear slide array consumed by every view.
// bonusSections is an ephemeral list of {section, anchorSlideId} — each bonus
// is spliced in immediately after its anchor slide, or appended if the anchor
// no longer exists (e.g. the operator bypassed extras that contained it).
function flattenSlides(show,redFlags,extrasBypassed,bonusSections){
  if(!show||!Array.isArray(show.sections))return[];
  var secs=activeSections(show,extrasBypassed);
  var flat=secs.flatMap(function(entry){return expandSectionSlides(entry.sec,show)});
  if(redFlags&&redFlags.length){
    var fi=flat.findIndex(function(s){return s.type==='fishbowl'});
    if(fi>=0){
      var rfSlides=redFlags.map(function(text,i){return{id:'rf-'+i,type:'red_flag',content:{primary:text,secondary:(i+1)+' / '+redFlags.length},reveal_state:'hidden',team:'neutral',timer_seconds:null,host_notes:null,points:null}});
      flat=flat.slice();
      flat.splice(fi+1,0,...rfSlides);
    }
  }
  if(bonusSections&&bonusSections.length){
    for(var bi=0;bi<bonusSections.length;bi++){
      var entry=bonusSections[bi];
      if(!entry||!entry.section)continue;
      var expanded=expandSectionSlides(entry.section,show);
      var ai=entry.anchorSlideId?flat.findIndex(function(s){return s.id===entry.anchorSlideId}):-1;
      if(ai<0)flat=flat.concat(expanded);
      else{flat=flat.slice();flat.splice(ai+1,0,...expanded)}
    }
  }
  return flat;
}
function sectionHasAnswerSheet(sec){
  var types=(sec.slides||[]).map(function(s){return s.type});
  return types.includes('source_q')||types.includes('acronym_q');
}
function redFlagSectionOffset(show,redFlags,extrasBypassed){
  if(!show||!redFlags||!redFlags.length)return{sectionIdx:-1,offset:0};
  // When extras are bypassed, fishbowl is filtered out and red flags don't inject.
  if(extrasBypassed)return{sectionIdx:-1,offset:0};
  var count=0;
  for(var i=0;i<show.sections.length;i++){
    for(var j=0;j<show.sections[i].slides.length;j++){
      if(show.sections[i].slides[j].type==='fishbowl')return{sectionIdx:i,offset:redFlags.length};
      count++;
    }
  }
  return{sectionIdx:-1,offset:0};
}
// slideToSection returns the ORIGINAL show.sections index for a given flat index.
// Injected slides (answer_sheet, red_flag) and bonus slides are attributed to the
// preceding base-section slide. Walks flattenSlides output so bonuses/red-flags
// are accounted for consistently.
function slideToSection(show,flatIndex,redFlags,extrasBypassed,bonusSections){
  if(!show||!Array.isArray(show.sections))return-1;
  var flat=flattenSlides(show,redFlags,extrasBypassed,bonusSections);
  if(!flat.length)return-1;
  var secs=show.sections;
  function findOwner(slideId){
    for(var i=0;i<secs.length;i++){
      var sl=secs[i].slides||[];
      for(var j=0;j<sl.length;j++){if(sl[j].id===slideId)return i;}
    }
    return-1;
  }
  var target=flat[Math.max(0,Math.min(flatIndex,flat.length-1))];
  if(!target)return-1;
  var owner=findOwner(target.id);
  if(owner>=0)return owner;
  // Injected/bonus slide — walk backwards through flat until we find a base-section slide.
  for(var k=flatIndex-1;k>=0;k--){
    var prev=flat[k];
    if(!prev)continue;
    var o=findOwner(prev.id);
    if(o>=0)return o;
  }
  // Fallback (shouldn't normally hit): last active section.
  var secsActive=activeSections(show,extrasBypassed);
  return secsActive.length?secsActive[secsActive.length-1].originalIdx:-1;
}
// getSectionStartIndex returns the flat index of the first slide of a given
// ORIGINAL show.sections index, in the current flat layout. Walks flattenSlides
// output to stay consistent with bonuses + red flags.
function getSectionStartIndex(show,sectionIndex,redFlags,extrasBypassed,bonusSections){
  if(!show||!Array.isArray(show.sections))return 0;
  var sec=show.sections[sectionIndex];
  if(!sec||!sec.slides||!sec.slides.length)return 0;
  var firstId=sec.slides[0].id;
  var flat=flattenSlides(show,redFlags,extrasBypassed,bonusSections);
  var idx=flat.findIndex(function(s){return s.id===firstId});
  return idx>=0?idx:0;
}
// ═══ ACTIVE SIDE — V2 collective mechanic ═══
// Only applies to version>=4 Standard shows. Derives active side from question position within section.
// Odd questions → cyan, even → pink, bonus questions (Q5/Q10/Q15) → open (both sides can claim)
function getActiveSide(show,flatSlides,slideIndex){
  if(!show||show.version<4)return null;
  // Applies to ALL version-4+ decks regardless of template (house_rules,
  // the-alternate-*, scorecard, etc). Formats that explicitly don't want
  // the alternating-side mechanic can opt out by setting show.disableActiveSide.
  if(show.disableActiveSide)return null;
  var slide=flatSlides[slideIndex];
  if(!slide)return null;
  // Only question slides
  if(slide.type!=='source_q'&&slide.type!=='acronym_q')return null;
  // Find which section this slide belongs to and count question position within it
  var secStart=0;var qPos=0;var inSection=false;
  for(var si=0;si<show.sections.length;si++){
    var sec=show.sections[si];
    if(!sec.slots||sec.slots.mode==='doa_triplet'||sec.slots.mode==='fluency_pair'){
      // Skip non-question sections — just advance flat index
      for(var j=0;j<flatSlides.length;j++){
        // We need to count flat slides per section differently
      }
    }
  }
  // Walk backwards from slideIndex to find section boundary and count questions.
  // Bonus questions (preceded by bonus_marker) are skipped from the count — they're
  // "open" (either side can claim), so they shouldn't flip the alternation parity
  // of the surrounding non-bonus questions. Otherwise the sequence goes
  // pink -> bonus(open) -> pink (wrong), instead of pink -> bonus(open) -> cyan.
  var qCount=0;
  for(var i=slideIndex;i>=0;i--){
    var s=flatSlides[i];
    if(s.type==='round_title'||s.type==='tier_title'){break}
    if(s.type==='source_q'||s.type==='acronym_q'){
      var p=flatSlides[i-1];
      if(!(p&&p.type==='bonus_marker'))qCount++;
    }
  }
  // Check if the current slide is itself a bonus question.
  var prevSlide=flatSlides[slideIndex-1];
  if(prevSlide&&prevSlide.type==='bonus_marker')return 'open';
  // Odd → cyan, even → pink
  return qCount%2===1?'cyan':'pink';
}
function reducer(state,action){
  if(!action||!action.type)return state;
  const flat=state.show?flattenSlides(state.show,state.redFlags,state.extrasBypassed,state.bonusSections):[];const maxSlide=Math.max(0,flat.length-1);
  switch(action.type){
    case'NEXT_SLIDE':{const ns=Math.min(state.currentSlide+1,maxSlide);const dst=flat[ns];return{...state,currentSlide:ns,revealState:dst?{...state.revealState,[dst.id]:'hidden'}:state.revealState,showScoreboard:dst&&dst.type==='round_title'?true:state.showScoreboard}}
    case'PREV_SLIDE':{const ps=Math.max(state.currentSlide-1,0);const dst=flat[ps];return{...state,currentSlide:ps,revealState:dst?{...state.revealState,[dst.id]:'hidden'}:state.revealState,showScoreboard:dst&&dst.type==='round_title'?true:state.showScoreboard}}
    case'JUMP_SECTION':{const idx=getSectionStartIndex(state.show,action.payload,state.redFlags,state.extrasBypassed,state.bonusSections);const ns2=Math.min(idx,maxSlide);const dst=flat[ns2];return{...state,currentSlide:ns2,revealState:dst?{...state.revealState,[dst.id]:'hidden'}:state.revealState,showScoreboard:dst&&dst.type==='round_title'?true:state.showScoreboard}}
    case'GO_TO_SLIDE':{const gs=Math.max(0,Math.min(action.payload,maxSlide));const dst=flat[gs];return{...state,currentSlide:gs,revealState:dst?{...state.revealState,[dst.id]:'hidden'}:state.revealState,showScoreboard:dst&&dst.type==='round_title'?true:state.showScoreboard}}
    case'SCORE_CYAN':{const[cy,pk]=state.scores;return{...state,scores:[Math.max(0,cy+action.payload),pk]}}
    case'SCORE_PINK':{const[cy,pk]=state.scores;return{...state,scores:[cy,Math.max(0,pk+action.payload)]}}
    case'TOGGLE_REVEAL':{const slide=flat[state.currentSlide];if(!slide)return state;const cur=state.revealState[slide.id]||'hidden';return{...state,revealState:{...state.revealState,[slide.id]:cur==='hidden'?'revealed':'hidden'}}}
    case'TOGGLE_SCOREBOARD':return{...state,showScoreboard:!state.showScoreboard};
    case'TOGGLE_SHORTCUTS':return{...state,showShortcuts:!state.showShortcuts};
    case'TOGGLE_MUTE':return{...state,muted:!state.muted};
    case'CLOSE_OVERLAYS':return{...state,showShortcuts:false,showHostPanel:false,showSetlistEditor:false};
    case'SET_SCORES':return{...state,scores:action.payload};
    case'SET_REVEAL_STATE':return{...state,revealState:action.payload};
    case'SET_SCOREBOARD':return{...state,showScoreboard:action.payload};
    case'TOGGLE_SCOREBOARD_POS':return{...state,scoreboardPosition:state.scoreboardPosition==='bottom'?'top':'bottom'};
    case'SET_SCOREBOARD_POS':return{...state,scoreboardPosition:action.payload||'bottom'};
    case'SET_MUTED':return{...state,muted:action.payload};
    case'SET_SHOW':return{...state,show:action.payload,currentSlide:0,bonusSections:[]};
    case'SET_RED_FLAGS':{var newFlags=action.payload.slice(0,20);if(action.fromSync)return{...state,redFlags:newFlags};var oldFlat=flattenSlides(state.show,state.redFlags,state.extrasBypassed,state.bonusSections);var newFlat=flattenSlides(state.show,newFlags,state.extrasBypassed,state.bonusSections);var curSlide=oldFlat[state.currentSlide];var ci=state.currentSlide;if(curSlide){var ni=newFlat.findIndex(function(s){return s.id===curSlide.id});if(ni>=0)ci=ni}return{...state,redFlags:newFlags,currentSlide:ci}}
    case'SET_EXTRAS_BYPASSED':{
      var nextVal=!!action.payload;
      if(action.fromSync)return{...state,extrasBypassed:nextVal};
      var oldFlatEb=flattenSlides(state.show,state.redFlags,state.extrasBypassed,state.bonusSections);
      var probeFlat=flattenSlides(state.show,state.redFlags,nextVal,state.bonusSections);
      // Re-link any bonus whose anchor would disappear under the new bypass state.
      // Walk the OLD flat backwards from the lost anchor to find the nearest surviving id.
      var rewrittenBonuses=(state.bonusSections||[]).map(function(entry){
        if(!entry||!entry.anchorSlideId)return entry;
        if(probeFlat.findIndex(function(s){return s.id===entry.anchorSlideId})>=0)return entry;
        var oldIdx=oldFlatEb.findIndex(function(s){return s.id===entry.anchorSlideId});
        if(oldIdx<0)return entry;
        for(var k=oldIdx-1;k>=0;k--){
          var cand=oldFlatEb[k];if(!cand)continue;
          if(probeFlat.findIndex(function(s){return s.id===cand.id})>=0)return {...entry,anchorSlideId:cand.id};
        }
        return entry;
      });
      var newFlatEb=flattenSlides(state.show,state.redFlags,nextVal,rewrittenBonuses);
      var curSlideEb=oldFlatEb[state.currentSlide];
      var ciEb=state.currentSlide;
      if(curSlideEb){
        var niEb=newFlatEb.findIndex(function(s){return s.id===curSlideEb.id});
        if(niEb>=0){ciEb=niEb}
        else{
          ciEb=0;
          for(var i=state.currentSlide-1;i>=0;i--){
            var c=oldFlatEb[i];if(!c)continue;
            var idx=newFlatEb.findIndex(function(s){return s.id===c.id});
            if(idx>=0){ciEb=idx;break}
          }
        }
      }
      ciEb=Math.max(0,Math.min(ciEb,newFlatEb.length-1));
      return{...state,extrasBypassed:nextVal,bonusSections:rewrittenBonuses,currentSlide:ciEb};
    }
    case'TOGGLE_SETLIST_EDITOR':if(state.liveMode)return state;return{...state,showSetlistEditor:!state.showSetlistEditor};
    case'SET_LIVE_MODE':return{...state,liveMode:true,showSetlistEditor:false};
    case'REORDER_SLIDES':{if(state.liveMode)return state;var rsi=action.payload.sectionIndex;var ns=state.show.sections.map(function(sec,i){return i!==rsi?sec:{...sec,slides:action.payload.newOrder}});return{...state,show:{...state.show,sections:ns}}}
    case'SWAP_SLIDE':{if(state.liveMode)return state;var swSi=action.payload.sectionIndex;var swSli=action.payload.slideIndex;var repl=action.payload.newSlide;var swSecs=state.show.sections.map(function(sec,i){if(i!==swSi)return sec;var swSlides=sec.slides.map(function(slide,j){if(j!==swSli)return slide;var alts=(repl.alternates||[]).filter(function(a){return a.id!==slide.id});var orig={id:slide.id,type:slide.type,content:slide.content};if(slide.alternates)orig.alternates=slide.alternates;alts.push(orig);return{...repl,alternates:alts}});return{...sec,slides:swSlides}});return{...state,show:{...state.show,sections:swSecs}}}
    case'IMPORT_SHOW':return{...state,show:action.payload,currentSlide:0,revealState:{},scores:[0,0],liveMode:false,showSetlistEditor:false,showScoreboard:true,bonusSections:[]};
    case'ADD_BONUS_SECTION':{
      // Ephemeral bonus: does NOT mutate state.show (so it never persists to localStorage
      // or Firebase shows/{id}). Lives in state.bonusSections and is spliced into the flat
      // array at render time. Auto-advances currentSlide to the first slide of the new bonus.
      var _section=action.payload&&action.payload.section;
      if(!_section||!Array.isArray(_section.slides))return state;
      var _anchorId=action.payload&&action.payload.anchorSlideId||null;
      var _entry={section:_section,anchorSlideId:_anchorId};
      var _nextBonuses=(state.bonusSections||[]).concat([_entry]);
      if(action.fromSync)return{...state,bonusSections:_nextBonuses};
      var _newFlat=flattenSlides(state.show,state.redFlags,state.extrasBypassed,_nextBonuses);
      var _firstId=_section.slides[0]&&_section.slides[0].id;
      var _ci=state.currentSlide;
      if(_firstId){var _ni=_newFlat.findIndex(function(s){return s.id===_firstId});if(_ni>=0)_ci=_ni}
      return{...state,bonusSections:_nextBonuses,currentSlide:_ci};
    }
    case'SET_BONUS_SECTIONS':{
      // Full-array replace (used by receivers applying a sync payload). Does not reposition.
      return{...state,bonusSections:Array.isArray(action.payload)?action.payload:[]};
    }
    case'CLEAR_BONUS_SECTIONS':return{...state,bonusSections:[]};
    case'REMOVE_BONUS_SECTION':{
      // Payload: { sectionId } removes that bonus. If omitted, removes the bonus
      // currently containing currentSlide (if any), else the most recent one.
      var bs=state.bonusSections||[];
      if(!bs.length)return state;
      var rmId=action.payload&&action.payload.sectionId||null;
      if(!rmId){
        var cur=flat[state.currentSlide];
        if(cur){
          for(var bi=0;bi<bs.length;bi++){
            var bsec=bs[bi].section;
            if(bsec&&(bsec.slides||[]).some(function(sl){return sl.id===cur.id})){rmId=bsec.id;break}
          }
        }
        if(!rmId){var last=bs[bs.length-1];rmId=last&&last.section&&last.section.id||null}
      }
      if(!rmId)return state;
      var nextBs=bs.filter(function(e){return!(e&&e.section&&e.section.id===rmId)});
      if(action.fromSync)return{...state,bonusSections:nextBs};
      var newFlatRm=flattenSlides(state.show,state.redFlags,state.extrasBypassed,nextBs);
      // Anchor currentSlide: prefer current id; else walk back through old flat.
      var curRm=flat[state.currentSlide];
      var ciRm=state.currentSlide;
      if(curRm){
        var niRm=newFlatRm.findIndex(function(s){return s.id===curRm.id});
        if(niRm>=0)ciRm=niRm;
        else{
          ciRm=0;
          for(var ri=state.currentSlide-1;ri>=0;ri--){
            var rc=flat[ri];if(!rc)continue;
            var rIdx=newFlatRm.findIndex(function(s){return s.id===rc.id});
            if(rIdx>=0){ciRm=rIdx;break}
          }
        }
      }
      ciRm=Math.max(0,Math.min(ciRm,newFlatRm.length-1));
      return{...state,bonusSections:nextBs,currentSlide:ciRm};
    }
    default:return state;
  }
}
