Source: 05-entries.js

/* ── Entry add ── */

/**
 * Creates a new log entry from the capture input's current value.
 * Optionally starts the timer on the new entry (`withTimer = true`), in which
 * case any running timer is stopped first and the matching plan task is
 * auto-promoted to "in progress".
 * @param {boolean} withTimer - If true, start the timer on the new entry.
 */
function addEntry(withTimer) {
  const inp = document.getElementById('captureInput');
  const text = inp.value.trim();
  if (!text) {
    inp.focus();
    return;
  }
  if (withTimer && getActiveTimer()) stopTimer();
  const entry = {
    id: Date.now() + '',
    text,
    tag: getSelectedTag(),
    ts: safeRoundedStart(),
    date: dk(new Date()),
  };
  getEntries().push(entry);
  inp.value = '';
  setViewDate(new Date());
  save();
  if (withTimer) {
    promoteMatchingTaskToInProgress(text);
    startTimer(entry.id);
  }
  render();
  inp.focus();
}

/* ── Restart with timer ── */

/**
 * Finds the most recently created log entry with the same text (case-
 * insensitive, trimmed) as `text`. Used to carry proof-link/note context
 * forward when a task or entry is restarted with a fresh timer.
 * @param {string} text - The entry/task text to match against.
 * @returns {(Object|undefined)} The most recent matching entry, or undefined if none exists.
 */
function findMostRecentEntryForText(text) {
  const key = text.toLowerCase().trim();
  const entries = getEntries();
  for (let i = entries.length - 1; i >= 0; i--) {
    // eslint-disable-next-line security/detect-object-injection -- key is an internal app-state value (app data), not from untrusted external input
    if (entries[i].text.toLowerCase().trim() === key) return entries[i];
  }
  return undefined;
}

/**
 * Builds a fresh log entry that continues previously logged work on `text`,
 * used by every "restart with timer" entry point (the log's ▶ restart button,
 * the kanban board's ▸ track button, and the "+ track recent" chips) so they
 * behave consistently.
 *
 * If a prior entry with the same text exists, its proof link is carried over
 * automatically. Its note is deliberately not copied onto the new entry
 * directly — a note written for an earlier session may no longer describe
 * this one — instead `_entryMetaEditId` and `_pendingNoteConfirm` (both in
 * 04a-render-entry-meta.js) are set so the new entry's proof-link/note editor opens with
 * a "same note as last time?" prompt for the user to confirm or clear.
 * @param {string} text - Entry text.
 * @param {string} tag - Category id.
 * @returns {Object} A new entry object, not yet pushed to `entries`.
 */
function createRestartedEntry(text, tag) {
  const entry = {
    id: Date.now() + '',
    text,
    tag,
    ts: safeRoundedStart(),
    date: dk(new Date()),
  };
  const prior = findMostRecentEntryForText(text);
  if (prior && prior.link && prior.link.trim()) entry.link = prior.link.trim();
  if (prior && prior.note && prior.note.trim()) {
    _entryMetaEditId = entry.id;
    _pendingNoteConfirm = { id: entry.id, note: prior.note.trim() };
  }
  return entry;
}

/* ── Billable rule ── */
// isEntryBillable() has been extracted to src/js/entry-billable.js
// (leaf ES module, issue #336, extraction #19).

/**
 * Returns copies of `entries` each carrying its resolved billable status as a
 * `_billable` flag. Pure helpers such as findGapReportEntries() have no access
 * to the category/task lookups isEntryBillable() needs, so callers resolve the
 * status up front and hand it over on the entry. The input entries are not
 * mutated.
 * @param {Array<Object>} entries - Log entries to annotate.
 * @returns {Array<Object>} New entry objects with `_billable` set.
 */
function annotateBillableStatus(entries) {
  return entries.map((entry) => ({ ...entry, _billable: isEntryBillable(entry) }));
}