// PRE — Cron Panel
(() => {
  const esc = PRE.escapeHtml;

  const DOW_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
  const DOW_FULL  = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];

  // ── Helper functions ──

  function fmtTime(h, m) {
    const ampm = h >= 12 ? 'PM' : 'AM';
    const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
    return `${h12}:${String(m).padStart(2, '0')} ${ampm}`;
  }

  function ordSuffix(n) {
    if (n > 3 && n < 21) return 'th';
    switch (n % 10) { case 1: return 'st'; case 2: return 'nd'; case 3: return 'rd'; default: return 'th'; }
  }

  function describeDow(dow) {
    // Expand ranges and lists: "1,3,5" -> "Mon, Wed, Fri"; "1-3" -> "Mon-Wed"; "3" -> "Every Wednesday"
    const parts = dow.split(',');
    const names = [];
    for (const part of parts) {
      if (part.includes('-')) {
        const [a, b] = part.split('-').map(Number);
        if (!isNaN(a) && !isNaN(b) && DOW_NAMES[a] && DOW_NAMES[b]) {
          names.push(`${DOW_NAMES[a]}\u2013${DOW_NAMES[b]}`);
        } else {
          names.push(part);
        }
      } else {
        const n = parseInt(part);
        if (!isNaN(n) && DOW_FULL[n]) {
          names.push(parts.length === 1 ? `Every ${DOW_FULL[n]}` : DOW_NAMES[n]);
        } else {
          names.push(part);
        }
      }
    }
    return names.join(', ');
  }

  function describeCron(expr) {
    const [min, hour, dom, mon, dow] = expr.split(/\s+/);
    if (min.startsWith('*/')) return `Every ${min.slice(2)} minutes`;
    if (hour.startsWith('*/')) return `Every ${hour.slice(2)} hours` + (min !== '0' ? ` at :${min.padStart(2, '0')}` : '');
    const h = parseInt(hour), m = parseInt(min);
    const timeStr = !isNaN(h) ? fmtTime(h, isNaN(m) ? 0 : m) : `${hour}:${min}`;
    if (dow === '1-5') return `Weekdays at ${timeStr}`;
    if (dow === '0,6') return `Weekends at ${timeStr}`;
    if (dow !== '*') {
      const dayStr = describeDow(dow);
      return `${dayStr} at ${timeStr}`;
    }
    if (dom !== '*') return `Day ${dom} of month at ${timeStr}`;
    return `Daily at ${timeStr}`;
  }

  // Natural language -> cron expression parser
  function nlToCron(input) {
    if (!input) return null;
    const s = input.toLowerCase().trim();

    // Already a valid cron expression (5 fields of cron-like chars)
    if (/^[\d*\/,\-]+(\s+[\d*\/,\-]+){4}$/.test(s)) {
      return { cron: s, description: describeCron(s) };
    }

    // Parse time from string: "9am", "9:30pm", "14:00", "3 pm", "noon", "midnight"
    // Prefers am/pm-qualified times over bare numbers (e.g. "1st at 10am" -> 10am, not 1)
    function parseTime(str) {
      if (/\bnoon\b/.test(str)) return { h: 12, m: 0 };
      if (/\bmidnight\b/.test(str)) return { h: 0, m: 0 };
      // Try am/pm-qualified match first
      let match = str.match(/(\d{1,2})(?::(\d{2}))?\s*(am|pm)/i);
      // Fall back to colon-separated time (e.g. "14:00")
      if (!match) match = str.match(/(\d{1,2}):(\d{2})/);
      if (!match) return null;
      let h = parseInt(match[1]);
      const m = match[2] ? parseInt(match[2]) : 0;
      const ampm = match[3]?.toLowerCase();
      if (ampm === 'pm' && h < 12) h += 12;
      if (ampm === 'am' && h === 12) h = 0;
      if (h > 23 || m > 59) return null;
      return { h, m };
    }

    // Infer time from time-of-day words when no explicit time given
    function inferTimeOfDay(str) {
      if (/\bmorning\b/.test(str)) return { h: 9, m: 0 };
      if (/\bafternoon\b/.test(str)) return { h: 14, m: 0 };
      if (/\bevening\b/.test(str)) return { h: 18, m: 0 };
      if (/\bnight\b/.test(str)) return { h: 21, m: 0 };
      if (/\bmidday\b/.test(str)) return { h: 12, m: 0 };
      return null;
    }

    // Day name -> cron day number
    const dayMap = { sun: 0, sunday: 0, mon: 1, monday: 1, tue: 2, tuesday: 2, wed: 3, wednesday: 3, thu: 4, thursday: 4, fri: 5, friday: 5, sat: 6, saturday: 6 };

    // Parse day names from string (word-boundary match to avoid "mon" in "monthly"; handles plurals like "fridays")
    function parseDays(str) {
      const found = [];
      for (const [name, num] of Object.entries(dayMap)) {
        if (new RegExp('\\b' + name + 's?\\b').test(str)) found.push(num);
      }
      return [...new Set(found)].sort();
    }

    // Resolve time: explicit > time-of-day word > default 9am
    const time = parseTime(s) || inferTimeOfDay(s);
    const min = time ? time.m : 0;
    const hour = time ? time.h : 9;

    // "every N minutes" / "every N mins"
    let match = s.match(/every\s+(\d+)\s*min/);
    if (match) return { cron: `*/${match[1]} * * * *`, description: `Every ${match[1]} minutes` };

    // "every minute"
    if (/every\s+minute/.test(s)) return { cron: '* * * * *', description: 'Every minute' };

    // "every half hour" / "every 30 minutes" (covered above but also handle phrasing)
    if (/every\s+half\s*hour|half[\s-]hourly/.test(s)) {
      return { cron: '*/30 * * * *', description: 'Every 30 minutes' };
    }

    // "every N hours"
    match = s.match(/every\s+(\d+)\s*hour/);
    if (match) return { cron: `0 */${match[1]} * * *`, description: `Every ${match[1]} hours` };

    // "every hour" / "hourly"
    if (/\bevery\s+hour\b|\bhourly\b/.test(s)) {
      return { cron: `${min} * * * *`, description: `Every hour${min ? ` at :${String(min).padStart(2,'0')}` : ''}` };
    }

    // "weekdays at TIME" / "every weekday" / "monday through friday"
    if (/weekday|week\s*day|mon(day)?\s*(through|thru|-|to)\s*fri(day)?/.test(s)) {
      return { cron: `${min} ${hour} * * 1-5`, description: `Weekdays at ${fmtTime(hour, min)}` };
    }

    // "weekends at TIME"
    if (/weekend/.test(s)) {
      return { cron: `${min} ${hour} * * 0,6`, description: `Weekends at ${fmtTime(hour, min)}` };
    }

    // "twice a day" / "2x daily" -- must come before the "daily" catch-all
    if (/twice\s+a\s+day|2x?\s+daily|two\s+times?\s+a\s+day/.test(s)) {
      return { cron: `0 9,17 * * *`, description: 'Twice daily at 9:00 AM and 5:00 PM' };
    }

    // "three times a day" / "3x daily"
    if (/three\s+times?\s+a\s+day|3x?\s+daily/.test(s)) {
      return { cron: `0 8,13,18 * * *`, description: 'Three times daily at 8 AM, 1 PM, 6 PM' };
    }

    // "every day" / "daily" / "every morning" / "every afternoon" / "every evening" / "every night"
    if (/every\s*day|daily|every\s+morning|every\s+afternoon|every\s+evening|every\s+night|every\s+midday|each\s+(day|morning|afternoon|evening|night)/.test(s)) {
      return { cron: `${min} ${hour} * * *`, description: `Daily at ${fmtTime(hour, min)}` };
    }

    // Specific days: "monday and wednesday at 3pm", "every tuesday at 10am", "on fridays at noon"
    const days = parseDays(s);
    if (days.length > 0) {
      const dow = days.join(',');
      const dayNames = days.map(d => Object.keys(dayMap).find(k => dayMap[k] === d && k.length > 3) || d);
      const label = dayNames.map(d => typeof d === 'string' ? d.charAt(0).toUpperCase() + d.slice(1) : d).join(', ');
      return { cron: `${min} ${hour} * * ${dow}`, description: `${label} at ${fmtTime(hour, min)}` };
    }

    // "every month on the Nth" / "monthly on the Nth" / "on the 1st of every month"
    match = s.match(/(?:every\s+month|monthly|of\s+every\s+month)\s*(?:on\s+)?(?:the\s+)?(\d{1,2})(?:st|nd|rd|th)?/)
         || s.match(/(?:on\s+)?(?:the\s+)?(\d{1,2})(?:st|nd|rd|th)?\s+(?:of\s+)?(?:every|each)\s+month/);
    if (match) {
      const dom = parseInt(match[1]);
      return { cron: `${min} ${hour} ${dom} * *`, description: `Monthly on the ${dom}${ordSuffix(dom)} at ${fmtTime(hour, min)}` };
    }

    // "every week" / "weekly"
    if (/every\s*week\b|weekly/.test(s)) {
      return { cron: `${min} ${hour} * * 1`, description: `Weekly on Monday at ${fmtTime(hour, min)}` };
    }

    // "at TIME" with no other context -> daily
    if (/^(?:at\s+)/.test(s) && time) {
      return { cron: `${min} ${hour} * * *`, description: `Daily at ${fmtTime(hour, min)}` };
    }

    // Just a bare time like "9am" or "3:30pm" -> daily
    if (time && s.replace(/\d{1,2}(:\d{2})?\s*(am|pm)?/i, '').trim().length < 5) {
      return { cron: `${min} ${hour} * * *`, description: `Daily at ${fmtTime(hour, min)}` };
    }

    // Last resort: if we have any time-of-day word, treat as daily
    if (inferTimeOfDay(s)) {
      return { cron: `${min} ${hour} * * *`, description: `Daily at ${fmtTime(hour, min)}` };
    }

    return null;
  }

  /**
   * Compute the next fire time for a 5-field cron expression.
   * Returns a Date or null if unparseable.
   */
  function nextCronRun(expr) {
    const [minF, hourF, domF, monF, dowF] = expr.split(/\s+/);
    const now = new Date();
    // Brute-force: scan forward up to 400 days in 1-minute increments
    // (optimised: jump by hour/day when possible)
    const candidate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes() + 1, 0, 0);

    function matchesField(val, field, rangeMax) {
      if (field === '*') return true;
      for (const part of field.split(',')) {
        if (part.includes('/')) {
          const [base, step] = part.split('/');
          const s = parseInt(step);
          const b = base === '*' ? 0 : parseInt(base);
          if (!isNaN(s) && (val - b) % s === 0 && val >= b) return true;
        } else if (part.includes('-')) {
          const [a, b] = part.split('-').map(Number);
          if (val >= a && val <= b) return true;
        } else {
          if (parseInt(part) === val) return true;
        }
      }
      return false;
    }

    for (let i = 0; i < 525600; i++) { // up to 365 days of minutes
      const d = new Date(candidate.getTime() + i * 60000);
      if (!matchesField(d.getMonth() + 1, monF)) continue;
      // DOM and DOW: if both are restricted (not *), either can match (OR logic per cron spec)
      const domMatch = matchesField(d.getDate(), domF);
      const dowMatch = matchesField(d.getDay(), dowF);
      if (domF !== '*' && dowF !== '*') {
        if (!domMatch && !dowMatch) continue;
      } else {
        if (!domMatch || !dowMatch) continue;
      }
      if (!matchesField(d.getHours(), hourF)) {
        // Skip ahead to next hour
        i += 59 - d.getMinutes();
        continue;
      }
      if (!matchesField(d.getMinutes(), minF)) continue;
      return d;
    }
    return null;
  }

  // ── Panel rendering ──

  async function openCronPanel() {
    const content = PRE.openPanel('Scheduled Jobs');

    try {
      const res = await fetch('/api/cron');
      const jobs = await res.json();
      renderCronPanel(content, jobs);
    } catch {
      content.innerHTML = '<p style="color:var(--danger)">Failed to load cron jobs</p>';
    }
  }

  function renderCronPanel(container, jobs) {
    let html = '<div class="settings-section">';
    html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">';
    html += '<div class="settings-section-title" style="margin:0">Jobs</div>';
    html += '<button class="btn btn-primary btn-sm" onclick="window.Cron.showAdd()">+ New Job</button>';
    html += '</div>';

    if (jobs.length === 0) {
      html += '<p style="color:var(--text-muted);font-size:0.85rem">No scheduled jobs yet. Create one to run prompts on a recurring schedule.</p>';
    } else {
      for (const job of jobs) {
        const statusDot = job.enabled
          ? '<span style="color:var(--success)">&#9679;</span>'
          : '<span style="color:var(--text-muted)">&#9679;</span>';
        const lastRun = job.last_run_at
          ? new Date(job.last_run_at).toLocaleString()
          : 'Never';
        const nextRun = job.enabled ? nextCronRun(job.schedule) : null;
        const nextRunStr = nextRun
          ? nextRun.toLocaleString(undefined, { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })
          : 'N/A';
        html += `<div class="connection-card" style="margin-bottom:10px">
          <div class="connection-card-header" style="padding:12px 14px">
            <div style="display:flex;align-items:center;gap:8px;flex:1;min-width:0">
              ${statusDot}
              <div style="flex:1;min-width:0">
                <div style="font-weight:600;font-size:0.9rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${esc(job.description)}</div>
                <div style="font-size:0.75rem;color:var(--text-muted);margin-top:2px">${esc(describeCron(job.schedule))} &middot; <code style="font-family:'SF Mono',monospace;font-size:0.7rem">${esc(job.schedule)}</code></div>
                <div style="font-size:0.75rem;color:var(--text-muted);margin-top:1px">Next: <strong style="color:var(--text-secondary)">${nextRunStr}</strong> &middot; Runs: ${job.run_count || 0} &middot; Last: ${lastRun}</div>
              </div>
            </div>
            <div style="display:flex;gap:4px;flex-shrink:0">
              ${job.last_session_id ? `<button class="btn btn-ghost btn-sm" onclick="window.Cron.viewResult('${job.last_session_id}')" title="View last result">Result</button>` : ''}
              <button class="btn btn-ghost btn-sm" onclick="window.Cron.toggle('${job.id}', ${!job.enabled})" title="${job.enabled ? 'Disable' : 'Enable'}">${job.enabled ? 'Disable' : 'Enable'}</button>
              <button class="btn btn-ghost btn-sm" onclick="window.Cron.run('${job.id}')" title="Run now">Run</button>
              <button class="btn btn-ghost btn-sm" onclick="window.Cron.del('${job.id}')" style="color:var(--danger)" title="Delete">&times;</button>
            </div>
          </div>
          <div style="padding:8px 14px;font-size:0.8rem;color:var(--text-secondary);border-top:1px solid var(--border)">${esc(job.prompt.length > 200 ? job.prompt.slice(0, 200) + '...' : job.prompt)}</div>
        </div>`;
      }
    }

    // Add job form (hidden by default)
    html += `<div id="cron-add-form" style="display:none;margin-top:16px">
      <div class="settings-section-title">New Scheduled Job</div>
      <div style="display:flex;flex-direction:column;gap:10px">
        <div>
          <label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:4px">When should it run?</label>
          <input id="cron-schedule" type="text" placeholder="every weekday at 9am" oninput="window.Cron.parseSchedule()" style="width:100%;padding:7px 10px;background:var(--surface);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:0.85rem">
          <div id="cron-parse-preview" style="font-size:0.75rem;margin-top:4px;min-height:1.2em"></div>
          <div style="font-size:0.7rem;color:var(--text-muted);margin-top:2px">Examples: "every day at 9am" &middot; "weekdays at 8:30am" &middot; "every 15 minutes" &middot; "monday and friday at 3pm" &middot; or raw cron: "0 9 * * 1-5"</div>
        </div>
        <div>
          <label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:4px">Description</label>
          <input id="cron-description" type="text" placeholder="Morning briefing" style="width:100%;padding:7px 10px;background:var(--surface);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:0.85rem">
        </div>
        <div>
          <label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:4px">Prompt (sent to the model when triggered)</label>
          <textarea id="cron-prompt" rows="3" placeholder="Give me a summary of today's calendar and top 3 priorities" style="width:100%;padding:7px 10px;background:var(--surface);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:0.85rem;font-family:inherit;resize:vertical"></textarea>
        </div>
        <div style="display:flex;gap:8px">
          <button class="btn btn-primary btn-sm" onclick="window.Cron.save()">Create Job</button>
          <button class="btn btn-ghost btn-sm" onclick="window.Cron.hideAdd()">Cancel</button>
        </div>
      </div>
    </div>`;

    // Notification delivery info
    html += '<div class="settings-section" style="margin-top:16px">';
    html += '<div class="settings-section-title">Result Delivery</div>';
    html += '<p style="font-size:0.82rem;color:var(--text-muted);line-height:1.5;margin:0 0 8px 0">Cron jobs run server-side — no browser needed. Each run creates its own chat session. Results are delivered via:</p>';
    html += '<ul style="font-size:0.82rem;color:var(--text-secondary);line-height:1.8;margin:0;padding-left:20px">';
    html += '<li><strong>macOS notification</strong> — always (click to open result)</li>';
    html += '<li><strong>Telegram</strong> — configure bot token in Settings to enable</li>';
    html += '<li><strong>GUI toast</strong> — shown if browser is open</li>';
    html += '</ul></div>';

    html += '</div>';
    container.innerHTML = html;
  }

  // ── Register button handler ──
  const btn = document.getElementById('cron-btn');
  if (btn) btn.addEventListener('click', openCronPanel);

  // ── Export manager ──
  window.Cron = {
    refresh: openCronPanel,
    showAdd() {
      const form = document.getElementById('cron-add-form');
      if (form) form.style.display = 'block';
    },
    hideAdd() {
      const form = document.getElementById('cron-add-form');
      if (form) form.style.display = 'none';
    },
    parseSchedule() {
      const input = document.getElementById('cron-schedule')?.value || '';
      const preview = document.getElementById('cron-parse-preview');
      if (!preview) return;
      if (!input.trim()) { preview.innerHTML = ''; return; }
      const result = nlToCron(input);
      if (result) {
        preview.innerHTML = `<span style="color:var(--success)">&#10003;</span> <code style="font-family:'SF Mono',monospace">${esc(result.cron)}</code> &mdash; ${esc(result.description)}`;
      } else {
        preview.innerHTML = `<span style="color:var(--warning)">Could not parse. Try "every day at 9am" or "weekdays at 8:30am"</span>`;
      }
    },
    async save() {
      const rawSchedule = document.getElementById('cron-schedule')?.value.trim();
      const prompt = document.getElementById('cron-prompt')?.value.trim();
      const description = document.getElementById('cron-description')?.value.trim();
      if (!rawSchedule || !prompt) return alert('Schedule and prompt are required.');
      // Parse natural language to cron
      const parsed = nlToCron(rawSchedule);
      if (!parsed) return alert('Could not understand the schedule. Try something like "every day at 9am" or "weekdays at 8:30am".');
      const schedule = parsed.cron;
      try {
        const res = await fetch('/api/cron', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ schedule, prompt, description: description || parsed.description }),
        });
        if (!res.ok) {
          const err = await res.json();
          return alert(err.error || 'Failed to create job');
        }
        openCronPanel();
      } catch (e) { alert('Error: ' + e.message); }
    },
    async toggle(id, enabled) {
      await fetch(`/api/cron/${id}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ enabled }),
      });
      openCronPanel();
    },
    async run(id) {
      await fetch(`/api/cron/${id}/run`, { method: 'POST' });
    },
    async del(id) {
      if (!confirm('Delete this scheduled job?')) return;
      await fetch(`/api/cron/${id}`, { method: 'DELETE' });
      openCronPanel();
    },
    async viewResult(sessionId) {
      // Close the cron panel and switch to the cron result session
      PRE.closePanel();
      PRE.emit('navigateSession', sessionId);
    },
  };
})();
