function createTodayTimer({ storage, getLogin, now = () => Date.now(), onChange = () => {} }) {
  const key = () => {
    const login = String(getLogin?.() || '').trim().toLowerCase();
    return login ? 'stackchain.today-timer.v1.' + encodeURIComponent(login) : '';
  };
  const empty = () => ({
    version:1, active_identity:'', entries:{}, away_at:null,
    pending_interruption:null, attention_interruption:null, capture_interruption:null, search_interruption:null, detour_interruption:null, timed_break:null,
  });
  const read = () => {
    const ownerKey = key();
    if (!ownerKey || !storage) return empty();
    try {
      const value = JSON.parse(storage.getItem(ownerKey) || 'null');
      if (value?.version !== 1 || typeof value.active_identity !== 'string' ||
          !value.entries || typeof value.entries !== 'object') return empty();
      return value;
    } catch (_error) {
      return empty();
    }
  };
  const write = state => {
    const ownerKey = key();
    if (!ownerKey || !storage) return false;
    try {
      storage.setItem(ownerKey, JSON.stringify(state));
      onChange(snapshot());
      return true;
    } catch (_error) {
      return false;
    }
  };
  const validPending = state => {
    const pending = state.pending_interruption;
    return pending && typeof pending.identity === 'string' && pending.identity &&
      Number.isFinite(pending.away_ms) && pending.away_ms >= 0 ?
      { identity:pending.identity, away_ms:pending.away_ms } : null;
  };
  const validAttention = state => {
    const pending = state.attention_interruption;
    return pending && typeof pending.identity === 'string' && pending.identity &&
      typeof pending.resume === 'boolean' ?
      { identity:pending.identity, resume:pending.resume } : null;
  };
  const validCapture = state => {
    const pending = state.capture_interruption;
    return pending && typeof pending.identity === 'string' && pending.identity &&
      typeof pending.resume === 'boolean' ?
      { identity:pending.identity, resume:pending.resume } : null;
  };
  const validSearch = state => {
    const pending = state.search_interruption;
    return pending && typeof pending.identity === 'string' && pending.identity &&
      typeof pending.resume === 'boolean' ?
      { identity:pending.identity, resume:pending.resume } : null;
  };
  const validDetour = state => {
    const pending = state.detour_interruption;
    return pending && typeof pending.identity === 'string' && pending.identity &&
      typeof pending.resume === 'boolean' && ['find', 'queues', 'insights', 'device-setup', 'security-center', 'live-data-status'].includes(pending.reason) ?
      { identity:pending.identity, resume:pending.resume, reason:pending.reason } : null;
  };
  const validBreak = state => {
    const value = state.timed_break;
    return value && typeof value.identity === 'string' && value.identity &&
      Number.isFinite(value.deadline_at) && value.deadline_at >= 0 ?
      { identity:value.identity, deadline_at:value.deadline_at, expired:now() >= value.deadline_at } : null;
  };
  const settle = (state, at = now()) => {
    const entry = state.entries[state.active_identity];
    if (!entry?.running) return state;
    entry.elapsed_ms = Math.max(0, Number(entry.elapsed_ms) || 0) +
      Math.max(0, at - Number(entry.started_at ?? at));
    entry.started_at = null;
    entry.running = false;
    return state;
  };
  const snapshot = (identity = '') => {
    const state = read();
    const selected = identity || state.active_identity;
    const entry = state.entries[selected];
    const result = !selected || !entry ?
      { identity:selected, elapsed_ms:0, running:false } :
      {
        identity:selected,
        elapsed_ms:Math.max(0, Number(entry.elapsed_ms) || 0) + (entry.running ?
          Math.max(0, now() - Number(entry.started_at ?? now())) : 0),
        running:Boolean(entry.running),
      };
    const timedBreak = validBreak(state);
    if (timedBreak?.identity === selected) result.break_deadline_at = timedBreak.deadline_at;
    return result;
  };
  return {
    adopt(identity, elapsedMs, running) {
      if (!key() || typeof identity !== 'string' || !identity ||
          !Number.isFinite(Number(elapsedMs)) || Number(elapsedMs) < 0) return false;
      const state = read();
      settle(state);
      state.active_identity = identity;
      state.entries[identity] = {
        elapsed_ms:Math.floor(Number(elapsedMs)),
        started_at:running ? now() : null,
        running:Boolean(running),
      };
      state.away_at = null;
      state.pending_interruption = null;
      state.attention_interruption = null;
      state.search_interruption = null;
      state.detour_interruption = null;
      state.timed_break = null;
      return write(state);
    },
    activate(identity) {
      if (!key() || typeof identity !== 'string' || !identity) return false;
      const state = read();
      if (state.active_identity === identity &&
          (state.entries[identity]?.running || validBreak(state)?.identity === identity)) return true;
      settle(state);
      state.active_identity = identity;
      const entry = state.entries[identity] || { elapsed_ms:0, started_at:null, running:false };
      entry.started_at = now();
      entry.running = true;
      state.entries[identity] = entry;
      state.away_at = null;
      state.pending_interruption = null;
      state.attention_interruption = null;
      state.search_interruption = null;
      state.detour_interruption = null;
      state.timed_break = null;
      return write(state);
    },
    pause() {
      const state = read();
      settle(state);
      state.away_at = null;
      return write(state);
    },
    resume() {
      const state = read();
      const entry = state.entries[state.active_identity];
      if (!entry) return false;
      if (!entry.running) {
        entry.started_at = now();
        entry.running = true;
      }
      state.timed_break = null;
      return write(state);
    },
    startBreak(minutes) {
      const duration = Number(minutes);
      if (!Number.isInteger(duration) || duration < 1 || duration > 120) return false;
      const state = read();
      const identity = state.active_identity;
      if (!identity || !state.entries[identity]) return false;
      settle(state);
      state.away_at = null;
      state.pending_interruption = null;
      state.timed_break = { identity, deadline_at:now() + duration * 60000 };
      return write(state) ? validBreak(state) : false;
    },
    breakSnapshot() {
      return validBreak(read());
    },
    resumeBreak() {
      const state = read();
      const pending = validBreak(state);
      const entry = pending && state.entries[pending.identity];
      if (!pending || !entry || state.active_identity !== pending.identity || entry.running) return false;
      state.timed_break = null;
      entry.started_at = now();
      entry.running = true;
      return write(state);
    },
    stop() {
      const state = read();
      settle(state);
      state.away_at = null;
      state.pending_interruption = null;
      state.attention_interruption = null;
      state.search_interruption = null;
      state.detour_interruption = null;
      state.timed_break = null;
      return write(state);
    },
    beginAttention() {
      const state = read();
      const existing = validAttention(state);
      if (existing) return existing;
      const identity = state.active_identity;
      const entry = state.entries[identity];
      if (!identity || !entry) return null;
      const resume = Boolean(entry.running);
      if (resume) settle(state);
      state.away_at = null;
      state.attention_interruption = { identity, resume };
      return write(state) ? { ...state.attention_interruption } : null;
    },
    attentionInterruption() {
      return validAttention(read());
    },
    returnFromAttention() {
      const state = read();
      const pending = validAttention(state);
      const entry = pending && state.entries[pending.identity];
      if (!pending || !entry) return null;
      state.active_identity = pending.identity;
      if (pending.resume && !entry.running) {
        entry.started_at = now();
        entry.running = true;
      }
      state.attention_interruption = null;
      state.away_at = null;
      return write(state) ? { identity:pending.identity, resumed:pending.resume } : null;
    },
    beginCapture() {
      const state = read();
      const existing = validCapture(state);
      if (existing) return existing;
      const identity = state.active_identity;
      const entry = state.entries[identity];
      if (!identity || !entry) return null;
      const resume = Boolean(entry.running);
      if (resume) settle(state);
      state.away_at = null;
      state.capture_interruption = { identity, resume };
      return write(state) ? { ...state.capture_interruption } : null;
    },
    captureInterruption() {
      return validCapture(read());
    },
    abandonCapture() {
      const state = read();
      const pending = validCapture(state);
      if (!pending) return null;
      state.capture_interruption = null;
      state.away_at = null;
      return write(state) ? { identity:pending.identity, resumed:false } : null;
    },
    returnFromCapture() {
      const state = read();
      const pending = validCapture(state);
      const entry = pending && state.entries[pending.identity];
      if (!pending || !entry) return null;
      const resumed = pending.resume && state.active_identity === pending.identity;
      if (resumed && !entry.running) {
        entry.started_at = now();
        entry.running = true;
      }
      state.capture_interruption = null;
      state.away_at = null;
      return write(state) ? { identity:pending.identity, resumed } : null;
    },
    beginSearch() {
      const state = read();
      const existing = validSearch(state);
      if (existing) return existing;
      const identity = state.active_identity;
      const entry = state.entries[identity];
      if (!identity || !entry?.running) return null;
      const resume = true;
      settle(state);
      state.away_at = null;
      state.search_interruption = { identity, resume };
      return write(state) ? { ...state.search_interruption } : null;
    },
    searchInterruption() {
      return validSearch(read());
    },
    abandonSearch() {
      const state = read();
      const pending = validSearch(state);
      if (!pending) return null;
      state.search_interruption = null;
      state.away_at = null;
      return write(state) ? { identity:pending.identity, resumed:false } : null;
    },
    returnFromSearch() {
      const state = read();
      const pending = validSearch(state);
      const entry = pending && state.entries[pending.identity];
      if (!pending || !entry) return null;
      const resumed = pending.resume && state.active_identity === pending.identity;
      if (resumed && !entry.running) {
        entry.started_at = now();
        entry.running = true;
      }
      state.search_interruption = null;
      state.away_at = null;
      return write(state) ? { identity:pending.identity, resumed } : null;
    },
    beginDetour(reason) {
      if (!['find', 'queues', 'insights', 'device-setup', 'security-center', 'live-data-status'].includes(reason)) return null;
      const state = read();
      const existing = validDetour(state);
      if (existing) return existing;
      const identity = state.active_identity;
      const entry = state.entries[identity];
      if (!identity || !entry?.running) return null;
      settle(state);
      state.away_at = null;
      state.detour_interruption = { identity, resume:true, reason };
      return write(state) ? { ...state.detour_interruption } : null;
    },
    detourInterruption() {
      return validDetour(read());
    },
    returnFromDetour(reason = '') {
      const state = read();
      const pending = validDetour(state);
      if (reason && pending?.reason !== reason) return null;
      const entry = pending && state.entries[pending.identity];
      if (!pending || !entry || state.active_identity !== pending.identity) return null;
      const resumed = pending.resume && !entry.running;
      if (resumed) {
        entry.started_at = now();
        entry.running = true;
      }
      state.detour_interruption = null;
      state.away_at = null;
      return write(state) ? { identity:pending.identity, resumed, reason:pending.reason } : null;
    },
    markAway() {
      const state = read();
      const entry = state.entries[state.active_identity];
      if (!entry?.running || validPending(state)) return false;
      if (Number.isFinite(state.away_at) && state.away_at >= 0) return true;
      state.away_at = now();
      return write(state);
    },
    reconcileInterruption(thresholdMs = 5 * 60 * 1000) {
      const state = read();
      const existing = validPending(state);
      if (existing) return existing;
      state.pending_interruption = null;
      const entry = state.entries[state.active_identity];
      const awayAt = Number(state.away_at);
      const detectedAt = now();
      state.away_at = null;
      if (!entry?.running || !Number.isFinite(awayAt) || awayAt < 0 || detectedAt < awayAt ||
          detectedAt - awayAt < thresholdMs) {
        write(state);
        return null;
      }
      const awayMs = detectedAt - awayAt;
      settle(state, detectedAt);
      state.pending_interruption = { identity:state.active_identity, away_ms:awayMs };
      write(state);
      return { ...state.pending_interruption };
    },
    pendingInterruption() {
      return validPending(read());
    },
    resolveInterruption(decision) {
      if (decision !== 'count' && decision !== 'exclude') return false;
      const state = read();
      const pending = state.pending_interruption;
      const entry = pending && state.entries[pending.identity];
      if (!entry || !Number.isFinite(pending.away_ms) || pending.away_ms < 0) return false;
      if (decision === 'exclude') {
        entry.elapsed_ms = Math.max(0, Number(entry.elapsed_ms) - pending.away_ms);
      }
      state.active_identity = pending.identity;
      entry.started_at = now();
      entry.running = true;
      state.pending_interruption = null;
      state.away_at = null;
      return write(state);
    },
    recapEntries() {
      const state = read();
      settle(state);
      write(state);
      return Object.entries(state.entries).map(([identity, entry]) => ({
        identity,
        elapsed_ms:Math.max(0, Number(entry.elapsed_ms) || 0),
      })).filter(entry => entry.elapsed_ms > 0);
    },
    clearRecap() {
      return write(empty());
    },
    totalElapsed() {
      const state = read();
      return Object.entries(state.entries).reduce((total, [identity, entry]) => {
        const live = identity === state.active_identity && entry.running ?
          Math.max(0, now() - Number(entry.started_at ?? now())) : 0;
        return total + Math.max(0, Number(entry.elapsed_ms) || 0) + live;
      }, 0);
    },
    snapshot,
  };
}

function createTodayTimerView({ timer, isActive, queryAll, formatEstimate, getRunway, getItem, onReopen, onResume, onComplete, onCapture }) {
  let progress = null;
  let runway = null;
  let breakView = null;
  if (typeof createTodayBreak === 'function') {
    breakView = createTodayBreak({timer, qs:selector => queryAll(selector)[0], queryAll, onChange:() => render(), onResume:onResume || onReopen});
  }
  const captureView = createTodayCaptureInterruption({
    timer,
    banner:queryAll('#today-capture-interruption')[0],
    label:queryAll('#today-capture-interruption-label')[0],
    getItem,
    onReturn:() => render(),
  });
  const searchView = createTodaySearchInterruption({
    timer, queryAll, getItemLabel:identity => getItem?.(identity)?.title, onChange:() => render(),
    onReturn:identity => onReopen?.(identity),
  });
  const detourView = createTodayDetourInterruption({
    timer, queryAll, getItemLabel:identity => getItem?.(identity)?.title, onChange:() => render(),
    onReturn:identity => onReopen?.(identity),
  });
  queryAll('[data-mobile-today-open]').forEach(button =>
    button.addEventListener('click', () => {
      const identity = timer.snapshot().identity;
      if (identity) onReopen?.(identity);
    })
  );
  queryAll('[data-mobile-today-toggle]').forEach(button =>
    button.addEventListener('click', () => view.toggle())
  );
  queryAll('[data-mobile-today-complete]').forEach(button =>
    button.addEventListener('click', () => {
      const identity = timer.snapshot().identity;
      if (identity) onComplete?.(identity);
    })
  );
  if (typeof document !== 'undefined') queryAll('.work-session-nav').forEach(nav => {
    const button = document.createElement('button');
    button.type = 'button';
    button.hidden = true;
    button.dataset.workSessionAdjustPlan = '';
    button.textContent = 'Adjust remaining plan';
    nav.insertBefore(button, nav.querySelector('[data-work-session-complete]'));
  });
  if (typeof MutationObserver !== 'undefined') {
    const plan = queryAll('#plan-today')[0];
    const sheet = queryAll('#plan-today-sheet')[0];
    if (plan && sheet) {
      const replan = createTodayBudgetReplan({ timer, openPlan:() => plan.click() });
      queryAll('[data-work-session-adjust-plan]').forEach(button =>
        button.addEventListener('click', () => replan.open())
      );
      new MutationObserver(() => {
        if (sheet.hidden && replan.restore()) render();
      }).observe(sheet, { attributes:true, attributeFilter:['hidden'] });
    }
  }
  const elapsed = milliseconds => {
    const seconds = Math.max(0, Math.floor(Number(milliseconds || 0) / 1000));
    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    const remainder = String(seconds % 60).padStart(2, '0');
    return hours ? hours + ':' + String(minutes).padStart(2, '0') + ':' + remainder : minutes + ':' + remainder;
  };
  const render = () => {
    const snapshot = timer.snapshot();
    breakView?.render();
    const active = Boolean(progress && isActive() && snapshot.identity);
    queryAll('[data-mobile-today-hud]').forEach(element => { element.hidden = !active; });
    queryAll('[data-mobile-today-open]').forEach(element => {
      element.textContent = active ? String(getItem?.(snapshot.identity)?.title || 'Current Today item') : '';
    });
    queryAll('[data-mobile-today-complete]').forEach(button => {
      const itemTitle = String(getItem?.(snapshot.identity)?.title || 'current Today item');
      const finalItem = Boolean(progress && progress.index >= progress.total);
      button.hidden = !active;
      button.textContent = finalItem ? 'Done & recap' : 'Done & next';
      button.setAttribute('aria-label', finalItem ?
        'Complete ' + itemTitle + ' and open Today recap' :
        'Complete ' + itemTitle + ' and open next Today item');
    });
    if (typeof document !== 'undefined') document.body.classList.toggle('mobile-today-active', active);
    if (!progress) return;
    const sourceRunway = getRunway?.(snapshot) || runway;
    const liveRunway = sourceRunway?.future_minutes !== undefined ? (() => {
      const currentElapsed = Math.max(0, Math.ceil(snapshot.elapsed_ms / 60000));
      const currentRemaining = sourceRunway.current_minutes === null ? null :
        Math.max(0, sourceRunway.current_minutes - currentElapsed);
      const remaining = currentRemaining === null || sourceRunway.future_minutes === null ? null :
        currentRemaining + sourceRunway.future_minutes;
      const projected = remaining === null ? null : Math.ceil(timer.totalElapsed() / 60000) + remaining;
      const capacityRemaining = sourceRunway.capacity_minutes === null || projected === null ? null :
        sourceRunway.capacity_minutes - projected;
      return {
        ...sourceRunway,
        remaining_minutes:remaining,
        over_estimate_minutes:sourceRunway.current_minutes === null ? 0 :
          Math.max(0, currentElapsed - sourceRunway.current_minutes),
        over_capacity_minutes:capacityRemaining === null ? 0 : Math.max(0, -capacityRemaining),
      };
    })() : sourceRunway;
    const timing = isActive() && snapshot.identity ? ' · ' + elapsed(snapshot.elapsed_ms) +
      (liveRunway?.current_minutes ? ' / ' + formatEstimate(liveRunway.current_minutes) : '') : '';
    const estimateRisk = liveRunway?.over_estimate_minutes ?
      ' · ' + formatEstimate(liveRunway.over_estimate_minutes) + ' over estimate' : '';
    const capacityRisk = liveRunway?.over_capacity_minutes ?
      ' · Today projected ' + formatEstimate(liveRunway.over_capacity_minutes) + ' over capacity' : '';
    queryAll('[data-work-session-progress]').forEach(element => {
      element.textContent = 'Item ' + progress.index + ' of ' + progress.total + timing + estimateRisk + capacityRisk +
        (!estimateRisk && !capacityRisk && liveRunway?.remaining_minutes ?
          ' · ' + formatEstimate(liveRunway.remaining_minutes) + ' remaining' : '');
    });
    queryAll('[data-work-session-adjust-plan]').forEach(button => {
      button.hidden = !(liveRunway?.over_estimate_minutes || liveRunway?.over_capacity_minutes);
    });
    queryAll('[data-work-session-timer-toggle]').forEach(button => {
      button.hidden = !isActive();
      button.textContent = snapshot.running ? 'Pause timer' : 'Resume timer';
      button.setAttribute('aria-pressed', String(!snapshot.running));
    });
  };
  const view = {
    open(identity, active) {
      const current = timer.snapshot();
      if (active && current.identity !== identity) timer.activate(identity);
      else if (!active) timer.stop();
      render();
    },
    reopen(identity) { onReopen?.(identity); },
    search(action, ...args) { return searchView[action]?.(...args); },
    beginDetour(reason) { return detourView.open(reason); },
    finishDetour(reason) { return detourView.finish(reason); },
    finish() { timer.stop(); progress = null; runway = null; render(); },
    update(nextProgress, nextRunway) { progress = nextProgress; runway = nextRunway; render(); },
    reset() { progress = null; runway = null; render(); },
    toggle() { const state = timer.snapshot(); state.running ? timer.pause() : timer.resume(); render(); },
    beginCapture() {
      const result = captureView.open();
      this.restoreCapture();
      render();
      return result;
    },
    restoreCapture() {
      const result = captureView.restore();
      const button = queryAll('#save-unfiled-issue')[0];
      if (button) button.textContent = result ? 'Save & return to Today' : 'Save to Drafts';
      return result;
    },
    finishCapture() { return captureView.finish(); },
    transferCapture() { return captureView.transfer(); },
    render,
  };
  queryAll('#new-issue').forEach(button => button.addEventListener('click', () => {
    view.beginCapture();
    onCapture?.();
  }));
  if (timer.captureInterruption) view.restoreCapture();
  searchView.restore();
  detourView.restore();
  return view;
}

function createTodayBudgetReplan({ timer, openPlan }) {
  let active = false;
  let resume = false;
  return {
    open() {
      if (active) return false;
      const state = timer.snapshot();
      if (!state.identity) return false;
      resume = Boolean(state.running);
      if (resume && timer.pause() === false) return false;
      active = true;
      openPlan?.(state);
      return true;
    },
    restore() {
      if (!active) return false;
      active = false;
      const shouldResume = resume;
      resume = false;
      return !shouldResume || timer.resume() !== false;
    },
  };
}

function createTodayInterruptionPrompt({ timer, sheet, description, getItemLabel, onResolved }) {
  const render = pending => {
    if (!pending) {
      sheet.hidden = true;
      return false;
    }
    const label = String(getItemLabel?.(pending.identity) || 'Current Today item');
    const minutes = Math.max(1, Math.round(pending.away_ms / 60000));
    description.textContent = label + ' · away for ' + minutes + ' minute' + (minutes === 1 ? '' : 's');
    sheet.hidden = false;
    return true;
  };
  return {
    background() { return timer.markAway(); },
    foreground() { return render(timer.reconcileInterruption()); },
    restore() { return render(timer.pendingInterruption()); },
    resolve(decision) {
      if (!timer.resolveInterruption(decision)) return false;
      sheet.hidden = true;
      onResolved?.();
      return true;
    },
  };
}

function createTodaySearchInterruption({ timer, queryAll, getItemLabel, onChange, onReturn }) {
  const render = (pending = timer.searchInterruption?.()) => {
    queryAll('[data-search-today-interruption]').forEach(element => { element.hidden = !pending; });
    queryAll('[data-search-today-label]').forEach(element => {
      element.textContent = pending ? 'Today paused · ' +
        String(getItemLabel?.(pending.identity) || 'Current Today item') : '';
    });
    onChange?.();
    return pending;
  };
  const view = {
    open() {
      const wasRunning = timer.snapshot().running;
      const pending = timer.beginSearch?.();
      render(pending || timer.searchInterruption?.());
      if (wasRunning && !pending) {
        const status = queryAll('#cmd-search-action-status')[0];
        if (status) status.textContent = 'Search is open, but Today timing could not be paused on this device.';
      }
      return pending;
    },
    restore() { return render(); },
    finish() {
      const result = timer.returnFromSearch?.();
      render(null);
      return result;
    },
    transfer() {
      const result = timer.abandonSearch?.();
      render(null);
      return result;
    },
  };
  queryAll('#open-palette').forEach(button => button.addEventListener('click', () => view.open()));
  queryAll('[data-return-from-search]').forEach(button => button.addEventListener('click', () => {
    const pending = timer.searchInterruption?.();
    view.finish();
    if (pending) onReturn?.(pending.identity);
  }));
  if (typeof MutationObserver !== 'undefined') {
    const overlays = [...queryAll('#cmd-palette'), ...queryAll('#search-preview')];
    const observer = new MutationObserver(() => {
      if (overlays.every(element => !element.classList.contains('open'))) view.finish();
    });
    overlays.forEach(element => observer.observe(element, {attributes:true, attributeFilter:['class']}));
  }
  return view;
}

function createTodayCaptureInterruption({ timer, banner, label, getItem, getItemLabel, onReturn }) {
  banner ||= typeof document === 'undefined' ? null : document.querySelector('#today-capture-interruption');
  label ||= typeof document === 'undefined' ? null : document.querySelector('#today-capture-interruption-label');
  const render = pending => {
    if (!banner || !label) return;
    banner.hidden = !pending;
    label.textContent = pending ? 'Today paused · ' +
      String(getItem?.(pending.identity)?.title || getItemLabel?.(pending.identity) || 'Current Today item') : '';
  };
  return {
    open() {
      const pending = timer.beginCapture();
      render(pending);
      return pending;
    },
    restore() {
      const pending = timer.captureInterruption();
      render(pending);
      return pending;
    },
    finish() {
      const result = timer.returnFromCapture();
      render(null);
      if (result) onReturn?.(result);
      return result;
    },
    transfer() {
      const result = timer.abandonCapture();
      render(null);
      return result;
    },
  };
}

function createTodayDetourInterruption({ timer, queryAll, getItemLabel, onChange, onReturn }) {
  const render = (pending = timer.detourInterruption?.()) => {
    queryAll('[data-today-detour]').forEach(element => { element.hidden = !pending; });
    queryAll('[data-today-detour-label]').forEach(element => {
      element.textContent = pending ? 'Today paused · ' +
        String(getItemLabel?.(pending.identity) || 'Current Today item') : '';
    });
    onChange?.();
    return pending;
  };
  const view = {
    open(reason) {
      const pending = timer.beginDetour?.(reason);
      render(pending || timer.detourInterruption?.());
      return pending;
    },
    restore() { return render(); },
    finish(reason) {
      const result = timer.returnFromDetour?.(reason);
      if (result || !reason) render(null);
      return result;
    },
  };
  queryAll('[data-return-from-detour]').forEach(button => button.addEventListener('click', () => {
    const pending = timer.detourInterruption?.();
    view.finish();
    if (pending) onReturn?.(pending.identity);
  }));
  if (typeof MutationObserver !== 'undefined') {
    queryAll('#find-work-sheet').forEach(sheet => new MutationObserver(() => {
      const pending = timer.detourInterruption?.();
      if (sheet.classList.contains('open')) view.open('find');
      else if (pending?.reason === 'find') view.finish();
    }).observe(sheet, {attributes:true, attributeFilter:['class']}));
  }
  return view;
}

if (typeof module !== 'undefined' && module.exports) {
  createTodayTimer.createView = createTodayTimerView;
  createTodayTimer.createInterruptionPrompt = createTodayInterruptionPrompt;
  createTodayTimer.createBudgetReplan = createTodayBudgetReplan;
  createTodayTimer.createSearchInterruption = createTodaySearchInterruption;
  createTodayTimer.createCaptureInterruption = createTodayCaptureInterruption;
  createTodayTimer.createDetourInterruption = createTodayDetourInterruption;
  module.exports = createTodayTimer;
}
