/**
 * Format an ISO timestamp as a relative time string ("2h ago").
 * Returns null when the input is missing or unparseable.
 * @param {string | null | undefined} iso
 * @returns {string | null}
 */
export function formatRelativeTime(iso) {
  if (!iso) return null;
  const then = new Date(iso).getTime();
  if (Number.isNaN(then)) return null;
  const diff = Date.now() - then;
  if (diff < 0) return 'just now';
  const minute = 60_000;
  const hour = 3_600_000;
  const day = 86_400_000;
  if (diff < minute) return 'just now';
  if (diff < hour) return `${Math.floor(diff / minute)}m ago`;
  if (diff < day) return `${Math.floor(diff / hour)}h ago`;
  if (diff < 7 * day) return `${Math.floor(diff / day)}d ago`;
  return iso.slice(0, 10);
}

const DAY_LONG = {
  sun: 'Sunday', sunday: 'Sunday',
  mon: 'Monday', monday: 'Monday',
  tue: 'Tuesday', tues: 'Tuesday', tuesday: 'Tuesday',
  wed: 'Wednesday', wednesday: 'Wednesday',
  thu: 'Thursday', thurs: 'Thursday', thursday: 'Thursday',
  fri: 'Friday', friday: 'Friday',
  sat: 'Saturday', saturday: 'Saturday',
};

function formatTime12(hStr, mStr) {
  const h = Number(hStr);
  const period = h >= 12 ? 'PM' : 'AM';
  const hour12 = ((h + 11) % 12) + 1;
  return `${hour12}:${mStr} ${period}`;
}

function formatOrdinal(n) {
  const j = n % 10;
  const k = n % 100;
  if (j === 1 && k !== 11) return `${n}st`;
  if (j === 2 && k !== 12) return `${n}nd`;
  if (j === 3 && k !== 13) return `${n}rd`;
  return `${n}th`;
}

/**
 * Render an internal schedule spec as a friendly sentence.
 * Supports: on demand, daily HH:MM, weekly <day> HH:MM, monthly <dom|last> HH:MM,
 * every Nm, every Nh, every Nmo, at <ISO>.
 * @param {string | null | undefined} raw
 * @returns {string}
 */
export function humanizeSchedule(raw) {
  const s = String(raw || '').trim().toLowerCase();
  if (!s || s === 'on demand' || s === 'manual' || s === 'event based' || s === 'live') {
    return 'Runs on demand';
  }

  const once = s.match(/^at\s+(.+)$/);
  if (once) {
    const target = new Date(once[1]);
    if (Number.isNaN(target.getTime())) return 'Runs once (date pending)';
    const dateLabel = target.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
    const timeLabel = target.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
    return `Runs once on ${dateLabel} at ${timeLabel}`;
  }

  const daily = s.match(/^daily\s+(\d{1,2}):(\d{2})$/);
  if (daily) return `Runs daily at ${formatTime12(daily[1], daily[2])}`;

  const weekly = s.match(/^weekly\s+(\w+)\s+(\d{1,2}):(\d{2})$/);
  if (weekly) {
    const dayLong = DAY_LONG[weekly[1]];
    if (dayLong) return `Runs every ${dayLong} at ${formatTime12(weekly[2], weekly[3])}`;
  }

  const monthly = s.match(/^monthly\s+(last|\d{1,2})\s+(\d{1,2}):(\d{2})$/);
  if (monthly) {
    const domSpec = monthly[1];
    const time = formatTime12(monthly[2], monthly[3]);
    const dayLabel = domSpec === 'last' ? 'the last day' : `the ${formatOrdinal(Number(domSpec))}`;
    return `Runs on ${dayLabel} of each month at ${time}`;
  }

  const everyMonths = s.match(/^every\s+(\d{1,3})\s*mo$/);
  if (everyMonths) {
    const n = Number(everyMonths[1]);
    if (n === 12) return 'Runs once a year';
    if (n === 1) return 'Runs once a month';
    return `Runs every ${n} months`;
  }

  const everyHours = s.match(/^every\s+(\d{1,3})\s*h$/);
  if (everyHours) {
    const n = Number(everyHours[1]);
    return n === 1 ? 'Runs every hour' : `Runs every ${n} hours`;
  }

  const everyMins = s.match(/^every\s+(\d{1,4})\s*m$/);
  if (everyMins) {
    const n = Number(everyMins[1]);
    return n === 1 ? 'Runs every minute' : `Runs every ${n} minutes`;
  }

  return `Runs ${s}`;
}

/**
 * @typedef {'active' | 'idle' | 'error'} StatusTone
 * @typedef {{ label: string; tone: StatusTone; raw?: string }} HumanStatus
 */

/**
 * Map an internal agent status string to a user-facing label + tone.
 * @param {string | null | undefined} raw
 * @returns {HumanStatus}
 */
export function humanizeStatus(raw) {
  const s = String(raw || '').trim().toLowerCase();
  if (!s) return { label: 'Ready', tone: 'idle' };
  if (s.includes('running') || s.includes('queued') || s.includes('leased')) {
    return { label: 'Working', tone: 'active', raw };
  }
  if (s.includes('ok') || s === 'complete' || s.includes('completed')) {
    return { label: 'Healthy', tone: 'active', raw };
  }
  if (s.includes('waiting') || s.includes('approval')) {
    return { label: 'Waiting for approval', tone: 'idle', raw };
  }
  if (s.includes('fail') || s.includes('error') || s.includes('blocked')) {
    return { label: 'Needs attention', tone: 'error', raw };
  }
  return { label: 'Ready', tone: 'idle', raw };
}

/**
 * Map an internal run status to a one-word user-facing label.
 * @param {string | null | undefined} raw
 * @returns {string}
 */
export function humanizeRunStatus(raw) {
  const s = String(raw || '').trim().toLowerCase();
  if (!s) return 'Unknown';
  if (s === 'complete' || s === 'completed' || s.includes('ok')) return 'Completed';
  if (s === 'running') return 'Running';
  if (s === 'queued' || s === 'leased') return 'Queued';
  if (s === 'failed' || s.includes('error')) return 'Failed';
  if (s.includes('waiting') || s.includes('approval')) return 'Waiting for approval';
  if (s === 'paused') return 'Paused';
  return s.charAt(0).toUpperCase() + s.slice(1);
}
