function createTodayWrapUp({ todayWork, tomorrowPlan, todaySync, limit = 5 }) {
  let items = [];
  const selected = new Set();

  function open(candidates) {
    const current = new Set(todayWork.read());
    const byIdentity = new Map((candidates || []).map(item => [todayWork.identity(item), item]));
    items = todayWork.read().flatMap(identity =>
      current.has(identity) && byIdentity.has(identity) ? [byIdentity.get(identity)] : []
    );
    selected.clear();
    return snapshot();
  }

  function choose(identity, schedule) {
    if (!items.some(item => todayWork.identity(item) === identity)) return false;
    if (schedule) selected.add(identity);
    else selected.delete(identity);
    return true;
  }

  function snapshot() {
    return items.map(item => ({
      item,
      identity:todayWork.identity(item),
      schedule:selected.has(todayWork.identity(item)),
    }));
  }

  async function finish() {
    const loaded = await tomorrowPlan.load();
    const currentIds = Array.isArray(loaded?.ids) ? [...loaded.ids] : [];
    const carried = items.filter(item => selected.has(todayWork.identity(item)) && todayWork.contains(item));
    if (!carried.length) {
      return {
        scheduled:0,
        left:items.filter(item => todayWork.contains(item)).length,
        plan_count:currentIds.length,
        plan_date:loaded?.plan_date,
        sync_pending:Boolean(loaded?.sync_pending),
      };
    }
    const combinedIds = [...currentIds];
    for (const item of carried) {
      const identity = todayWork.identity(item);
      if (!combinedIds.includes(identity)) combinedIds.push(identity);
    }
    if (combinedIds.length > limit) {
      throw new Error(`Tomorrow can hold ${limit} items. Keep ${combinedIds.length - limit} in Today or edit Tomorrow first.`);
    }
    const estimates = {...(loaded?.estimates || {})};
    const capacityMinutes = loaded?.capacity_minutes ?? null;
    const plannedMinutes = combinedIds.reduce((total, identity) => total + (Number(estimates[identity]) || 0), 0);
    if (capacityMinutes !== null && plannedMinutes > capacityMinutes) {
      throw new Error('Tomorrow is over capacity. Edit the plan before finishing wrap-up.');
    }
    const staged = tomorrowPlan.stage({ids:combinedIds, capacity_minutes:capacityMinutes, estimates});
    if (!staged) throw new Error('Tomorrow could not be saved on this device. Your Today plan is unchanged.');
    let scheduled = 0;
    for (const item of carried) {
      const identity = todayWork.identity(item);
      if (!todaySync.enqueue('remove', identity)) {
        throw new Error('Today sync could not be queued. The item remains in Today.');
      }
      if (todayWork.contains(item) && !todayWork.remove(item)) {
        throw new Error('Today could not be updated. Retry wrap-up.');
      }
      scheduled += 1;
    }
    await todaySync.flush();
    return {
      scheduled,
      left:items.length - scheduled,
      plan_count:combinedIds.length,
      plan_date:staged.plan_date,
      sync_pending:Boolean(staged.sync_pending),
    };
  }

  return { open, choose, snapshot, finish };
}

function createTodayWrapUpView({ controller, qs, escapeHtml, onComplete = () => {}, onClose = () => {} }) {
  let actualMinutes = {};
  let workedItems = [];

  function close() {
    qs('#today-wrap-up-sheet').hidden = true;
    document.body.classList.remove('task-overlay-open');
    onClose();
  }

  function render() {
    const rows = controller.snapshot();
    qs('#today-wrap-up-items').innerHTML = rows.map(row => {
      const title = String(row.item.title || row.identity).slice(0, 180);
      const context = String(row.item.key || row.item.repository || 'Work item').slice(0, 180);
      return '<div class="today-wrap-up-item"><span><strong>' + escapeHtml(title) + '</strong>' +
        '<span class="small muted">' + escapeHtml(context) + '</span></span><label><input type="checkbox" ' +
        'data-wrap-up-identity="' + escapeHtml(row.identity) + '"' + (row.schedule ? ' checked' : '') +
        '> Carry to Tomorrow</label></div>';
    }).join('') || '<p class="small">Nothing unfinished remains in Today.</p>';
    qs('#today-wrap-up-items').querySelectorAll('[data-wrap-up-identity]').forEach(input => {
      input.addEventListener('change', () => controller.choose(input.dataset.wrapUpIdentity, input.checked));
    });
  }

  function open(items, recapActualMinutes = {}, recapWorkedItems = []) {
    actualMinutes = { ...recapActualMinutes };
    workedItems = recapWorkedItems.map(item => ({...item}));
    controller.open(items);
    qs('#today-wrap-up-status').textContent = '';
    render();
    qs('#today-wrap-up-sheet').hidden = false;
    document.body.classList.add('task-overlay-open');
    requestAnimationFrame(() => (qs('#today-wrap-up-items input') || qs('#finish-today-wrap-up')).focus());
  }

  async function finish(button) {
    button.disabled = true;
    qs('#today-wrap-up-status').textContent = 'Saving tomorrow’s plan…';
    try {
      const tomorrowItems = controller.snapshot().filter(row => row.schedule).map(row => ({
        identity:row.identity,
        title:String(row.item.title || row.identity).slice(0, 180),
        context:String(row.item.key || row.item.repository || 'Work item').slice(0, 180),
      }));
      const result = await controller.finish();
      qs('#my-work-action-status').textContent = result.scheduled + ' carried to Tomorrow · ' + result.left +
        ' left in Today' + (result.sync_pending ? ' · sync pending.' : '.');
      close();
      onComplete(result, actualMinutes, workedItems, tomorrowItems);
    } catch (error) {
      qs('#today-wrap-up-status').textContent = error.message || 'Wrap-up could not be saved. Retry when ready.';
    } finally {
      button.disabled = false;
    }
  }

  function bind() {
    qs('#close-today-wrap-up').addEventListener('click', close);
    qs('#finish-today-wrap-up').addEventListener('click', event => finish(event.currentTarget));
  }

  return { open, close, finish, render, bind };
}

function setupTodayWrapUp(options) {
  const controller = createTodayWrapUp(options);
  const view = createTodayWrapUpView({ ...options, controller });
  view.bind();
  return view;
}

if (typeof module !== 'undefined' && module.exports) {
  createTodayWrapUp.createView = createTodayWrapUpView;
  createTodayWrapUp.setup = setupTodayWrapUp;
  module.exports = createTodayWrapUp;
}
