(function(root){
  'use strict';
  let availabilityProvider=()=>null;
  function escapeText(value){
    return String(value||'').replace(/\\/g,'\\\\').replace(/\r?\n/g,'\\n').replace(/,/g,'\\,').replace(/;/g,'\\;');
  }
  function compact(value){return String(value||'').replace(/[-:]/g,'');}
  function addMinutes(value,minutes){
    const [hours,mins]=String(value).split(':').map(Number),total=hours*60+mins+Number(minutes);
    return String(Math.floor(total/60)%24).padStart(2,'0')+':'+String(total%60).padStart(2,'0');
  }
  function clockMinutes(value){const [hours,minutes]=String(value).split(':').map(Number);return hours*60+minutes;}
  function stableId(value){return String(value||'work').replace(/[^a-z0-9]+/gi,'-').replace(/^-|-$/g,'').toLowerCase();}
  function foldLine(line){
    const encoder=new TextEncoder(),chunks=[];let chunk='',bytes=0,limit=75;
    for(const character of String(line)){
      const width=encoder.encode(character).length;
      if(chunk&&bytes+width>limit){chunks.push(chunk);chunk=character;bytes=width;limit=74;}
      else {chunk+=character;bytes+=width;}
    }
    chunks.push(chunk);return chunks.join('\r\n ');
  }
  function buildWeekSchedule(plan,startTimes,getItem,selected,availability){
    const blocks=[],blockers=[],availableByDate=new Map((availability||[]).map(day=>[day.plan_date,day.free_windows||[]]));
    (plan?.days||[]).slice().sort((a,b)=>a.plan_date.localeCompare(b.plan_date)).forEach(day=>{
      let cursor=startTimes?.[day.plan_date]||'09:00';
      const windows=availableByDate.has(day.plan_date)?availableByDate.get(day.plan_date):(day.free_windows||null);
      const candidates=[];
      (day.ids||[]).forEach(id=>{
        if(selected&&!selected.has(id))return;
        const minutes=Number(day.estimates?.[id]),item=getItem?.(id);
        if(!item||!Number.isFinite(minutes)||minutes<=0)return;
        const exact=startTimes?.[id]||day.start_times?.[id];
        let start=exact||cursor;
        if(!exact&&windows){
          const fit=windows.find(window=>{
            const candidate=Math.max(clockMinutes(cursor),clockMinutes(window.start_time));
            if(candidate+minutes>clockMinutes(window.end_time))return false;
            start=addMinutes('00:00',candidate);return true;
          });
          if(!fit){blockers.push({id,title:item.title||'Untitled work',plan_date:day.plan_date,minutes});return;}
        }
        const startMinute=clockMinutes(start),endMinute=startMinute+minutes;
        candidates.push({item,id,minutes,start,startMinute,endMinute,exact:Boolean(exact)});
        if(!exact)cursor=addMinutes('00:00',endMinute);
      });
      const invalid=new Set();
      candidates.forEach(candidate=>{
        if(candidate.endMinute>1440||windows&&!windows.some(window=>clockMinutes(window.start_time)<=candidate.startMinute&&candidate.endMinute<=clockMinutes(window.end_time)))invalid.add(candidate.id);
      });
      candidates.forEach((left,index)=>candidates.slice(index+1).forEach(right=>{
        if(left.startMinute<right.endMinute&&left.endMinute>right.startMinute){invalid.add(left.id);invalid.add(right.id);}
      }));
      candidates.forEach(candidate=>{
        if(invalid.has(candidate.id)){
          blockers.push({id:candidate.id,title:candidate.item.title||'Untitled work',plan_date:day.plan_date,minutes:candidate.minutes,reason:'invalid-exact-time'});return;
        }
        blocks.push({...candidate.item,id:candidate.id,plan_date:day.plan_date,start_time:candidate.start,
          end_time:addMinutes('00:00',candidate.endMinute),minutes:candidate.minutes});
      });
    });
    return {blocks,blockers};
  }
  function buildWeekBlocks(plan,startTimes,getItem,selected,availability){
    return buildWeekSchedule(plan,startTimes,getItem,selected,availability).blocks;
  }
  function serializeWeekCalendar(blocks,{timezone='UTC',revision=0,generatedAt}={}){
    const stamp=generatedAt||new Date().toISOString().replace(/[-:]/g,'').replace(/\.\d{3}/,'');
    const lines=['BEGIN:VCALENDAR','VERSION:2.0','PRODID:-//Stackchain//Week Ahead//EN','CALSCALE:GREGORIAN','METHOD:PUBLISH','X-WR-CALNAME:Stackchain Week Ahead',`X-WR-TIMEZONE:${timezone}`,'BEGIN:VTIMEZONE',`TZID:${timezone}`,'END:VTIMEZONE'];
    (blocks||[]).forEach(block=>{
      const day=compact(block.plan_date),reference=block.repository+(block.number!=null?' #'+block.number:'');
      lines.push('BEGIN:VEVENT',`UID:week-${stableId(block.id)}@stackchain`,`SEQUENCE:${Math.max(0,Math.floor(Number(revision)||0))}`,`DTSTAMP:${stamp}`,
        `DTSTART;TZID=${timezone}:${day}T${compact(block.start_time)}00`,`DTEND;TZID=${timezone}:${day}T${compact(block.end_time)}00`,
        `SUMMARY:${escapeText(block.title||'Untitled work')}`,`DESCRIPTION:${escapeText(reference+' · Stackchain Week Ahead')}`,
        `URL:${String(block.url||'')}`,'TRANSP:OPAQUE','END:VEVENT');
    });
    lines.push('END:VCALENDAR');return lines.map(foldLine).join('\r\n')+'\r\n';
  }
  async function deliverWeekCalendar({text,filename,navigator,document,urlApi,FileCtor}){
    const file=new FileCtor([text],filename,{type:'text/calendar;charset=utf-8'});
    const payload={files:[file],title:'Stackchain Week Ahead',text:'Timed Week Ahead calendar blocks'};
    if(typeof navigator?.share==='function'&&typeof navigator?.canShare==='function'&&navigator.canShare(payload)){
      await navigator.share(payload);return 'shared';
    }
    const href=urlApi.createObjectURL(file);
    try{const anchor=document.createElement('a');anchor.href=href;anchor.download=filename;anchor.click();}
    finally{urlApi.revokeObjectURL(href);}
    return 'downloaded';
  }
  function mountWeekCalendarHandoff({qs,getItem,getAvailability=availabilityProvider,escapeHtml,escapeAttribute,onSave,onDone,windowObject=root,navigatorObject=root.navigator,
    documentObject=root.document,urlApi=root.URL,FileCtor=root.File}={}){
    const handoff=qs('#week-calendar-handoff'),review=qs('#week-review'),daysRoot=qs('#week-calendar-days');
    let plan=null,focusReturn=null;
    const selected=()=>new Set(Array.from(daysRoot.querySelectorAll('[data-week-calendar-item]')).filter(input=>input.checked).map(input=>input.value));
    const starts=()=>Object.fromEntries([
      ...Array.from(daysRoot.querySelectorAll('[data-week-calendar-start]')).map(input=>[input.dataset.weekCalendarStart,input.value]),
      ...Array.from(daysRoot.querySelectorAll('[data-week-calendar-exact]')).filter(input=>input.value).map(input=>[input.dataset.weekCalendarExact,input.value]),
    ]);
    const schedule=()=>buildWeekSchedule(plan,starts(),getItem,selected(),getAvailability?.());
    const blocks=()=>schedule().blocks;
    function update(){
      const current=schedule(),byId=new Map(current.blocks.map(block=>[block.id,block])),blockedById=new Map(current.blockers.map(blocker=>[blocker.id,blocker]));
      daysRoot.querySelectorAll('[data-week-calendar-preview]').forEach(node=>{
        const id=node.dataset.weekCalendarPreview,block=byId.get(id),blocker=blockedById.get(id);
        node.textContent=block?block.start_time+'–'+block.end_time+' · '+block.minutes+' min':
          (blocker?'Does not fit imported free time':'Excluded from calendar');
      });
      qs('#share-week-calendar').disabled=!current.blocks.length||current.blockers.length>0;
      qs('#save-week-calendar-times').disabled=!current.blocks.length||current.blockers.length>0;
      if(current.blockers.length){
        const blocker=current.blockers[0];
        qs('#week-calendar-status').textContent=blocker.title+' does not fit free time on '+blocker.plan_date+'. Adjust the start, estimate, or calendar import.';
      }else qs('#week-calendar-status').textContent=(getAvailability?.()?'Planning around imported busy time · ':'Manual timing · ')+
        current.blocks.length+' calendar block'+(current.blocks.length===1?'':'s')+' selected.';
    }
    function close({back=false}={}){
      handoff.hidden=true;
      if(back){review.hidden=false;const target=focusReturn||qs('#confirm-week-plan');focusReturn=null;target.focus?.();}
    }
    function open(value,{returnFocus=null}={}){
      plan=value;if(returnFocus)focusReturn=returnFocus;review.hidden=true;handoff.hidden=false;
      const labels=new Map((value.days||[]).map(day=>[day.plan_date,new Intl.DateTimeFormat('en',{weekday:'short',month:'short',day:'numeric',timeZone:'UTC'}).format(new Date(day.plan_date+'T12:00:00Z'))]));
      daysRoot.innerHTML=(value.days||[]).filter(day=>day.ids?.length).map(day=>'<article class="week-calendar-day"><header><h3>'+escapeHtml(labels.get(day.plan_date)||day.plan_date)+
        '</h3><label>Start <input type="time" value="09:00" data-week-calendar-start="'+escapeAttribute(day.plan_date)+'"></label></header>'+
        day.ids.map(id=>{const item=getItem(id),reference=item?(item.repository+' #'+item.number):String(id).slice(0,96),saved=day.start_times?.[id]||'';
          return '<div class="week-calendar-item"><label class="week-calendar-choice"><input type="checkbox" data-week-calendar-item value="'+escapeAttribute(id)+'" checked><span><strong>'+escapeHtml(item?.title||'Work details unavailable')+
            '</strong><small>'+escapeHtml(reference)+'</small><small data-week-calendar-preview="'+escapeAttribute(id)+'"></small></span></label><label class="week-calendar-time">Start<input type="time" value="'+escapeAttribute(saved)+'" data-week-calendar-exact="'+escapeAttribute(id)+'" aria-label="Exact start for '+escapeAttribute(item?.title||'work')+'"></label></div>';}).join('')+'</article>').join('');
      daysRoot.querySelectorAll('input').forEach(input=>input.addEventListener('change',update));
      update();qs('#back-to-week-review-from-calendar').focus?.();return true;
    }
    qs('#back-to-week-review-from-calendar').addEventListener('click',()=>close({back:true}));
    qs('#save-week-calendar-times').addEventListener('click',async()=>{
      const button=qs('#save-week-calendar-times'),current=schedule();if(!current.blocks.length||current.blockers.length)return;
      button.disabled=true;qs('#week-calendar-status').textContent='Saving exact task times…';
      const byDate={};current.blocks.forEach(block=>(byDate[block.plan_date]||(byDate[block.plan_date]={}))[block.id]=block.start_time);
      try{await onSave?.(byDate);plan={...plan,days:(plan.days||[]).map(day=>({...day,start_times:{...(byDate[day.plan_date]||{})}}))};
        qs('#week-calendar-status').textContent='Exact task times saved to Week Ahead.';}
      catch(error){qs('#week-calendar-status').textContent=(error?.message||'Exact times could not be saved.')+' Retry when connected.';}
      finally{button.disabled=false;}
    });
    qs('#export-saved-week-calendar')?.addEventListener('click',event=>{focusReturn=event.currentTarget;qs('#confirm-week-plan').click();});
    qs('#share-week-calendar').addEventListener('click',async()=>{
      const button=qs('#share-week-calendar'),current=blocks();if(!current.length)return;
      button.disabled=true;qs('#week-calendar-status').textContent='Preparing calendar blocks…';
      const day=new Date().toISOString().slice(0,10);
      try{
        const text=serializeWeekCalendar(current,{timezone:plan.timezone||Intl.DateTimeFormat().resolvedOptions().timeZone,revision:plan.revision});
        const result=await deliverWeekCalendar({text,filename:'stackchain-week-ahead-'+day+'.ics',navigator:navigatorObject,
          document:documentObject,urlApi,FileCtor});
        close();onDone?.(result);
      }catch(error){
        qs('#week-calendar-status').textContent=error?.name==='AbortError'?'Share cancelled. Your Week Ahead is still ready.':'Calendar export failed. Retry without leaving Week Ahead.';
        button.disabled=false;
      }
    });
    return {open,close,blocks};
  }
  function setAvailabilityProvider(value){availabilityProvider=typeof value==='function'?value:()=>null;}
  const api={buildWeekBlocks,buildWeekSchedule,deliverWeekCalendar,escapeText,foldLine,mountWeekCalendarHandoff,serializeWeekCalendar,setAvailabilityProvider};
  if(typeof module!=='undefined'&&module.exports)module.exports=api;else root.StackchainWeekCalendar=api;
})(typeof window!=='undefined'?window:globalThis);
