function createTodayWeekReschedule({
  week,
  api,
  getToday,
  adoptToday = () => {},
  operationId = () => globalThis.crypto?.randomUUID?.() || String(Date.now()),
  storage = null,
  getLogin = () => '',
  now = () => Date.now(),
} = {}) {
  let current = null;
  let flushing = null;
  const storagePrefix = 'stackchain.today-week-reschedule.v1.';

  function storageKey() {
    const login = String(getLogin?.() || '').trim().toLowerCase();
    return login ? storagePrefix + encodeURIComponent(login) : '';
  }

  function validRecord(value) {
    return typeof value?.body?.operation_id === 'string' &&
      Array.isArray(value?.today?.ids) && Array.isArray(value?.week?.days);
  }

  function readQueue() {
    const key = storageKey();
    if (!key || !storage) return [];
    try {
      const value = JSON.parse(storage.getItem(key) || 'null');
      if (Array.isArray(value?.records)) return value.records.filter(validRecord);
      return validRecord(value) ? [value] : [];
    } catch (_error) { return []; }
  }

  function saveQueue(records) {
    const key = storageKey();
    if (!key || !storage) return false;
    try {
      if (!records.length) storage.removeItem(key);
      else storage.setItem(key, JSON.stringify({version:2, records}));
      return true;
    }
    catch (_error) { return false; }
  }

  function persist(record) { return saveQueue([...readQueue(), record]); }

  function pending() {
    const key = storageKey();
    if (!key || !storage) return null;
    return readQueue()[0] || null;
  }

  function resume() {
    const records = readQueue();
    const record = records[records.length - 1];
    if (!record) return null;
    adoptToday(record.today);
    week.adopt(record.week);
    return {...record, sync_pending:true, pending_count:records.length};
  }

  async function restoreConflict(error) {
    if (error?.status !== 409) throw error;
    const [today, weekState] = await Promise.all([api('api/v1/today'), api('api/v1/week')]);
    adoptToday(today);
    week.adopt(weekState);
    const conflict = new Error('Plans changed on another device. Review and retry this saved move.');
    conflict.status = 409;
    throw conflict;
  }

  function flush(retryConflict = false) {
    if (flushing) return flushing;
    if (!pending()) return Promise.resolve(false);
    flushing = (async () => {
      let result = null;
      let conflictRetried = false;
      while (true) {
        const records = readQueue();
        const record = records[0];
        if (!record) return result ? {...result, sync_pending:false, pending_count:0} : false;
        try {
          result = await api('api/v1/week/reschedule', {
            method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(record.body),
          });
        } catch (error) {
          if (error?.status === 409 && retryConflict && !conflictRetried) {
            const [today, weekState] = await Promise.all([api('api/v1/today'), api('api/v1/week')]);
            adoptToday(today); week.adopt(weekState);
            const latest = readQueue();
            if (today?.ids?.includes(record.body.identity) &&
                latest[0]?.body?.operation_id === record.body.operation_id) {
              latest[0] = {...latest[0], body:{
                ...latest[0].body,
                today_revision:today.revision,
                week_revision:weekState.revision,
              }};
              if (!saveQueue(latest)) throw new Error('Could not update this saved move after plans changed.');
              conflictRetried = true;
              continue;
            }
          }
          await restoreConflict(error);
        }
        adoptToday(result.today);
        week.adopt(result.week);
        const latest = readQueue();
        if (latest[0]?.body?.operation_id !== record.body.operation_id) continue;
        const remaining = latest.slice(1).map((entry, index) => index ? entry : ({
          ...entry,
          body:{...entry.body, today_revision:result.today.revision, week_revision:result.week.revision},
        }));
        if (!saveQueue(remaining)) throw new Error('Move synced, but its local queue could not be updated.');
      }
    })().finally(() => { flushing = null; });
    return flushing;
  }

  function optimisticSnapshots(planDate, estimate) {
    const today = {
      ...current.today,
      ids: current.today.ids.filter(id => id !== current.identity),
      estimates: {...(current.today.estimates || {})},
    };
    delete today.estimates[current.identity];
    const days = (current.week.days || []).map(day => {
      const estimates = {...(day.estimates || {})}; delete estimates[current.identity];
      return {...day, ids:(day.ids || []).filter(id => id !== current.identity), estimates};
    });
    let destination = days.find(day => day.plan_date === planDate);
    if (!destination) {
      destination = {plan_date:planDate, ids:[], capacity_minutes:null, estimates:{}};
      days.push(destination);
    }
    destination.ids.push(current.identity);
    destination.estimates[current.identity] = estimate;
    return {today, week:{revision:current.week_revision, timezone:current.week.timezone || null, days}};
  }

  async function open(identity) {
    let today;
    const queued = readQueue().length;
    let offline = Boolean(queued);
    if (queued) {
      today = getToday?.();
    } else {
      try { today = await api('api/v1/today'); }
      catch (error) {
        if (error?.status === 401 || error?.status === 403) throw error;
        today = getToday?.(); offline = true;
      }
    }
    if (!identity || !today?.ids?.includes(identity)) {
      throw new Error('The active Today item changed. Reopen rescheduling.');
    }
    const loaded = queued ? week.state?.() : await week.load();
    if (!loaded) throw new Error('Reload Today before rescheduling another saved move.');
    if (loaded?.sync_pending) {
      throw new Error('Reconnect before rescheduling Today into Week Ahead.');
    }
    offline = offline || Boolean(loaded?.offline_snapshot);
    const estimate = Number(today.estimates?.[identity]);
    const estimateMinutes = Number.isFinite(estimate) && estimate > 0 ? estimate : null;
    const days = week.review().days.map(day => {
      const ids = (day.ids || []).filter(id => id !== identity);
      const planned = Number(day.planned_minutes) || 0;
      const existing = Number(day.estimates?.[identity]) || 0;
      return {
        ...day,
        ids,
        planned_minutes: Math.max(0, planned - existing),
        load: `${Math.max(0, planned - existing)} / ${Number(day.capacity_minutes) || 0} min`,
        eligible: ids.length < 5,
      };
    });
    current = {
      identity,
      today_revision: today.revision,
      week_revision: loaded.revision,
      operation_id: operationId(),
      estimate_minutes: estimateMinutes,
      days,
      today,
      week: loaded,
      offline,
    };
    return {...current, days:days.map(day => ({...day, ids:[...day.ids]}))};
  }

  async function confirm(planDate, estimateMinutes, {allowOverload = false} = {}) {
    if (!current) throw new Error('Open rescheduling before choosing a day.');
    const day = current.days.find(value => value.plan_date === planDate);
    if (!day) throw new Error('Choose one of the next seven days.');
    const estimate = Number(estimateMinutes);
    if (!Number.isFinite(estimate) || estimate < 5 || estimate > 1440) {
      throw new Error('Estimate must be between 5 and 1440 minutes.');
    }
    if (!day.eligible) throw new Error('That Week Ahead day already has five items.');
    const capacity = Number(day.capacity_minutes) || 0;
    if (capacity && day.planned_minutes + estimate > capacity && !allowOverload) {
      throw new Error("That move exceeds the day's capacity. Confirm overload before rescheduling.");
    }
    const body = {
      operation_id:current.operation_id,
      identity:current.identity,
      estimate_minutes:estimate,
      plan_date:planDate,
      today_revision:current.today_revision,
      week_revision:current.week_revision,
      allow_over_capacity:allowOverload,
    };
    const optimistic = optimisticSnapshots(planDate, estimate);
    const durable = Boolean(storageKey() && storage);
    const admitted = durable && persist({body, ...optimistic, queued_at:now()});
    if (durable && !admitted) {
      throw new Error('Could not save this move on this device. Nothing changed.');
    }
    if (admitted) {
      adoptToday(optimistic.today);
      week.adopt(optimistic.week);
    }
    if (current.offline) {
      current = null;
      return {...optimistic, sync_pending:true, pending_count:readQueue().length};
    }
    let result;
    try {
      if (admitted) {
        result = await flush(true);
      } else {
        try {
          result = await api('api/v1/week/reschedule', {
            method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body),
          });
        } catch (error) {
          if (error?.status !== 409) throw error;
          const [today, weekState] = await Promise.all([api('api/v1/today'), api('api/v1/week')]);
          adoptToday(today); week.adopt(weekState);
          if (!today?.ids?.includes(body.identity)) throw error;
          body.today_revision = today.revision; body.week_revision = weekState.revision;
          result = await api('api/v1/week/reschedule', {
            method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body),
          });
        }
      }
    } catch(error) {
      if (admitted && error?.status === 409) await restoreConflict(error);
      if (admitted && error?.status !== 401 && error?.status !== 403) {
        current = null;
        return {...optimistic, sync_pending:true};
      }
      throw error;
    }
    adoptToday(result.today);
    week.adopt(result.week);
    current = null;
    return result;
  }

  function cancel() { current = null; }
  return {open, confirm, cancel, state:() => current, pending, resume, flush};
}

function mountTodayWeekReschedule({
  qs, document=globalThis.document, window=globalThis.window, week, api, getToday, adoptToday, currentTarget, closeActions,
  refresh, warm, continueToday, announce, storage=null, getLogin=()=>'', schedule=callback=>requestAnimationFrame(callback),
}={}) {
  const dialog=qs('#today-week-reschedule'),daysRoot=qs('#today-week-reschedule-days');
  const estimate=qs('#today-week-reschedule-estimate'),status=qs('#today-week-reschedule-status');
  const confirm=qs('#confirm-today-week-reschedule'),launcher=qs('[data-work-session-reschedule-week]');
  let selectedDate=null,allowOverload=false;
  const controller=createTodayWeekReschedule({week,api,getToday,adoptToday,storage,getLogin});
  async function flushPending() {
    if(!controller.pending())return false;
    try {
      const result=await controller.flush();
      announce('Today-to-Week move synced to your account.');
      await refresh();warm();
      return result;
    } catch(error) {
      announce(`${error.message||'Sync unavailable.'} Move saved on this device · sync pending.`);
      return false;
    }
  }
  function resumePending() {
    const restored=controller.resume();
    if(!restored)return false;
    announce('Today-to-Week move saved on this device · sync pending.');
    flushPending();
    return restored;
  }
  window?.addEventListener('online', flushPending);
  document?.addEventListener('visibilitychange',()=>document.hidden?false:flushPending());
  let restoreAttempts=0;
  function restoreWhenOwned(){
    if(resumePending()||getLogin()||restoreAttempts++>=20)return;
    window?.setTimeout(restoreWhenOwned,250);
  }
  restoreWhenOwned();
  function close() {
    controller.cancel();selectedDate=null;allowOverload=false;
    if(dialog.open)dialog.close();schedule(()=>{
      qs('[data-mobile-today-more]').click();launcher.focus();
    });
  }
  function render(opened) {
    daysRoot.replaceChildren(...opened.days.map(day=>{
      const button=document.createElement('button');
      button.type='button';button.dataset.planDate=day.plan_date;button.setAttribute('role','radio');
      button.setAttribute('aria-checked','false');button.disabled=!day.eligible;
      const label=document.createElement('strong');label.textContent=day.label;
      const load=document.createElement('span');load.className='small';
      load.textContent=day.eligible?day.load:'5 items · full';button.append(label,load);
      button.addEventListener('click',()=>{
        selectedDate=day.plan_date;allowOverload=false;
        daysRoot.querySelectorAll('button').forEach(choice=>choice.setAttribute(
          'aria-checked',String(choice===button)
        ));
        status.textContent='';confirm.disabled=false;confirm.textContent='Move to Week Ahead & continue';
      });
      return button;
    }));
  }
  launcher.addEventListener('click',async()=>{
    const target=currentTarget();
    if(!target){announce('Start or resume a checkpointed Today item before rescheduling.');return;}
    closeActions();status.textContent='Loading Week Ahead…';confirm.disabled=true;dialog.showModal();
    try{
      const opened=await controller.open(target.identity);render(opened);
      estimate.value=opened.estimate_minutes||'';
      status.textContent=opened.estimate_minutes?'Choose a future day.':'Add an estimate, then choose a future day.';
      schedule(()=>daysRoot.querySelector('button:not(:disabled)')?.focus());
    }catch(error){status.textContent=error.message;}
  });
  qs('#cancel-today-week-reschedule').addEventListener('click',close);
  dialog.addEventListener('cancel',event=>{event.preventDefault();close();});
  confirm.addEventListener('click',async()=>{
    if(!selectedDate)return;
    confirm.disabled=true;status.textContent='Moving Today into Week Ahead…';
    try{
      const result=await controller.confirm(selectedDate,Number(estimate.value),{allowOverload});
      dialog.close();
      if(result.sync_pending){
        announce('Moved locally. Saved on this device · sync pending.');warm();
      }else{
        await refresh();warm();announce('Moved to Week Ahead. Continuing Today.');
      }
      await continueToday();
    }catch(error){
      const overload=error.message.includes('Confirm overload');allowOverload=overload;
      status.textContent=error.message;
      confirm.textContent=overload?'Confirm overload & continue':'Move to Week Ahead & continue';
      confirm.disabled=false;
    }
  });
  return {controller,close,flushPending,resumePending};
}

if (typeof module !== 'undefined' && module.exports) {
  module.exports = createTodayWeekReschedule;
  module.exports.mount = mountTodayWeekReschedule;
}
