'use strict';
  // Visual cron editor + pure cron helpers (5-field: min hour day-of-month month day-of-week).
  // Classic script sharing the global scope (uses $, state, updateAutoSyncHelp from other modules).

  const CRON_WEEKDAY_NAMES = ['日', '一', '二', '三', '四', '五', '六'];
  // Editor UI state: which frequency preset is active, and the weekly day picks.
  const cronUI = { mode: 'daily', weeklyDays: new Set() };
  let rangeUnitDropdown = null; // custom .selectish for the auto-sync range unit

  // Parse one cron field into a sorted array of allowed values. Mirrors the server matcher.
  function cronParseField(field, min, max){
    const out = new Set();
    for(const part of String(field).split(',')){
      const m = part.trim().match(/^(\*|\d+)(?:-(\d+))?(?:\/(\d+))?$/);
      if(!m) return null; // any invalid token => whole field invalid
      const step = m[3] ? Number(m[3]) : 1;
      if(step < 1) return null;
      let lo, hi;
      if(m[1] === '*'){ lo = min; hi = max; }
      else { lo = Number(m[1]); hi = m[2] !== undefined ? Number(m[2]) : (m[3] ? max : lo); }
      if(lo < min || hi > max || lo > hi) return null;
      for(let v = lo; v <= hi; v += step) out.add(v);
    }
    return [...out].sort((a,b)=>a-b);
  }

  function cronValidate(expr){
    const parts = String(expr || '').trim().split(/\s+/);
    if(parts.length !== 5) return false;
    const ranges = [[0,59],[0,23],[1,31],[1,12],[0,6]];
    return parts.every((p,i)=>cronParseField(p, ranges[i][0], ranges[i][1]) !== null);
  }

  function cronMatchesClient(expr, date){
    const parts = String(expr).trim().split(/\s+/);
    if(parts.length !== 5) return false;
    const [mi, ho, dom, mon, dow] = parts;
    const miS = cronParseField(mi,0,59), hoS = cronParseField(ho,0,23), monS = cronParseField(mon,1,12);
    const domS = cronParseField(dom,1,31), dowS = cronParseField(dow,0,6);
    if(!miS || !hoS || !monS || !domS || !dowS) return false;
    if(!miS.includes(date.getMinutes())) return false;
    if(!hoS.includes(date.getHours())) return false;
    if(!monS.includes(date.getMonth()+1)) return false;
    const domR = dom.trim() !== '*', dowR = dow.trim() !== '*';
    const domOk = domS.includes(date.getDate()), dowOk = dowS.includes(date.getDay());
    if(domR && dowR) return domOk || dowOk;
    if(domR) return domOk;
    if(dowR) return dowOk;
    return true;
  }

  // Next n run times after `from`, scanning minute by minute up to ~366 days.
  function cronNextRuns(expr, n = 3, from = new Date()){
    if(!cronValidate(expr)) return [];
    const out = [];
    const d = new Date(from.getTime());
    d.setSeconds(0, 0);
    d.setMinutes(d.getMinutes() + 1); // start from the next minute
    const limit = 366 * 24 * 60;
    for(let i = 0; i < limit && out.length < n; i++){
      if(cronMatchesClient(expr, d)) out.push(new Date(d.getTime()));
      d.setMinutes(d.getMinutes() + 1);
    }
    return out;
  }

  // Human-readable Chinese description for common cron shapes; falls back to echoing the expression.
  function cronDescribe(expr){
    if(!cronValidate(expr)) return '表达式无效';
    const [mi, ho, dom, mon, dow] = expr.trim().split(/\s+/);
    const everyMin = mi.match(/^\*\/(\d+)$/);
    if(everyMin && ho === '*' && dom === '*' && mon === '*' && dow === '*') return `每隔 ${everyMin[1]} 分钟`;
    if(mi === '*' && ho === '*' && dom === '*' && mon === '*' && dow === '*') return '每分钟';
    const everyHour = ho.match(/^\*\/(\d+)$/);
    if(/^\d+$/.test(mi) && everyHour && dom === '*' && mon === '*' && dow === '*') return `每隔 ${everyHour[1]} 小时（第 ${mi} 分）`;
    const hhmm = (/^\d+$/.test(mi) && /^\d+$/.test(ho)) ? `${String(ho).padStart(2,'0')}:${String(mi).padStart(2,'0')}` : null;
    if(hhmm && dom === '*' && mon === '*' && dow === '*') return `每天 ${hhmm}`;
    if(hhmm && dom === '*' && mon === '*' && dow !== '*'){
      const days = cronParseField(dow,0,6) || [];
      return `每周${days.map(d=>CRON_WEEKDAY_NAMES[d]).join('、')} ${hhmm}`;
    }
    if(hhmm && dom !== '*' && mon === '*' && dow === '*'){
      const days = cronParseField(dom,1,31) || [];
      return `每月 ${days.join('、')} 日 ${hhmm}`;
    }
    return `cron：${expr.trim()}`;
  }

  // --- editor wiring ---

  function clampInt(v, lo, hi, dflt){ const n = Math.round(Number(v)); return Number.isFinite(n) ? Math.max(lo, Math.min(hi, n)) : dflt; }

  function selectCronFreq(mode){
    cronUI.mode = mode;
    document.querySelectorAll('#cronFreqRow button[data-freq]').forEach(b=>b.classList.toggle('active', b.dataset.freq === mode));
    const panes = { minutes:'cronFreqMinutes', hours:'cronFreqHours', daily:'cronFreqDaily', weekly:'cronFreqWeekly', custom:'cronFreqCustom' };
    // minutes/hours/daily are inline rows (need flex so their gap/align-items apply); weekly/custom are blocks.
    const flexRow = { minutes:1, hours:1, daily:1 };
    for(const [m, id] of Object.entries(panes)){
      const el = $(id); if(!el) continue;
      el.style.display = (m === mode) ? (flexRow[m] ? 'flex' : 'block') : 'none';
    }
    refreshCronUI();
  }

  // Collapse the auto-sync options when auto-sync is off (values stay in state and are still saved).
  function refreshAutoSyncCollapse(){
    const wrap = $('autoSyncOptions'); if(!wrap) return;
    wrap.style.display = (String(state.settings.autoSyncEnabled) === 'true') ? 'grid' : 'none';
  }

  // A rough "约 N 小时/天/分钟" estimate of the scheduling interval (gap between the next two fires).
  function cronIntervalText(){
    const runs = cronNextRuns(state.settings.autoSyncCron || '0 2 * * *', 2);
    if(runs.length < 2) return '';
    const mins = Math.round((runs[1] - runs[0]) / 60000);
    if(mins < 60) return `（约 ${mins} 分钟）`;
    if(mins % 1440 === 0) return `（约 ${mins/1440} 天）`;
    if(mins % 60 === 0) return `（约 ${mins/60} 小时）`;
    return `（约 ${Math.round(mins/60*10)/10} 小时）`;
  }

  const CRON_UNIT_LABEL = { minute:'分钟', hour:'小时', day:'天', month:'个月' };
  // Convert the scheduling interval (gap between the next two fires) into a {value, unit} pair.
  function cronIntervalParts(){
    const runs = cronNextRuns(state.settings.autoSyncCron || '0 2 * * *', 2);
    if(runs.length < 2) return null;
    const mins = Math.round((runs[1] - runs[0]) / 60000);
    if(mins <= 0) return null;
    if(mins % 1440 === 0) return { value: mins/1440, unit:'day' };
    if(mins % 60 === 0) return { value: mins/60, unit:'hour' };
    return { value: mins, unit:'minute' };
  }
  // Update only the help line (so live typing in fixed mode isn't reset).
  function updateAutoSyncRangeHelp(){
    const help = $('autoSyncRangeHelp'); if(!help) return;
    const interval = String(state.settings.autoSyncRangeMode) === 'interval';
    const num = $('recentMonthsInput'), unit = $('autoSyncRangeUnit');
    const v = num ? (num.value || 3) : 3;
    const u = CRON_UNIT_LABEL[unit ? unit.value : 'month'] || '个月';
    help.textContent = interval
      ? `自动同步跟随调度频率，只回看一个调度周期（≈ ${v} ${u}）；手动“同步最近”不受影响。`
      : `自动同步回看最近 ${v} ${u}内更新的条目。手动“同步最近”的范围在它的弹窗里单独设置。`;
  }
  // Full apply: 'interval' fills the inputs with the computed schedule interval and disables them;
  // fixed restores the user's saved value/unit and enables them.
  function refreshAutoSyncRange(){
    const interval = String(state.settings.autoSyncRangeMode) === 'interval';
    const num = $('recentMonthsInput'), unit = $('autoSyncRangeUnit'), btn = $('autoSyncRangeBtn');
    if(btn){ btn.classList.toggle('good', interval); btn.textContent = interval ? '跟随调度频率 ✓' : '跟随调度频率'; }
    const dd = $('autoSyncRangeUnitDropdown');
    const setUnit = (v)=>{ if(rangeUnitDropdown) rangeUnitDropdown.setValue(v, true); else if(unit) unit.value = v; };
    if(interval){
      const p = cronIntervalParts();
      if(num){ if(p) num.value = p.value; num.disabled = true; }
      if(p) setUnit(p.unit);
    }else{
      if(num){ num.value = state.settings.autoSyncRangeValue || 3; num.disabled = false; }
      setUnit(state.settings.autoSyncRangeUnit || 'month');
    }
    if(dd) dd.classList.toggle('disabled', interval);
    updateAutoSyncRangeHelp();
  }

  function refreshCronWeeklyDays(){
    document.querySelectorAll('#cronWeeklyDaysRow button[data-day]').forEach(btn=>{
      btn.classList.toggle('active', cronUI.weeklyDays.has(Number(btn.dataset.day)));
    });
  }

  // Read the active preset controls and produce a cron string.
  function buildCronFromControls(){
    switch(cronUI.mode){
      case 'minutes': {
        const n = clampInt($('cronEveryMinutes')?.value, 1, 59, 30);
        return `*/${n} * * * *`;
      }
      case 'hours': {
        const n = clampInt($('cronEveryHours')?.value, 1, 23, 4);
        const m = clampInt($('cronHourMinute')?.value, 0, 59, 0);
        return `${m} */${n} * * *`;
      }
      case 'daily': {
        const [h, m] = String($('cronDailyTime')?.value || '02:00').split(':').map(Number);
        return `${m || 0} ${h || 0} * * *`;
      }
      case 'weekly': {
        const [h, m] = String($('cronWeeklyTime')?.value || '08:00').split(':').map(Number);
        const days = [...cronUI.weeklyDays].sort((a,b)=>a-b);
        const dow = days.length ? days.join(',') : '*';
        return `${m || 0} ${h || 0} * * ${dow}`;
      }
      case 'custom':
      default:
        return String($('cronCustomInput')?.value || '').trim();
    }
  }

  // Parse a cron string and reflect it back into the preset controls (best-effort; else 'custom').
  function applyCronToControls(cron){
    const expr = cronValidate(cron) ? cron.trim() : '0 2 * * *';
    const [mi, ho, dom, mon, dow] = expr.split(/\s+/);
    if($('cronCustomInput')) $('cronCustomInput').value = expr;
    let mode = 'custom';
    const everyMin = mi.match(/^\*\/(\d+)$/);
    const everyHour = ho.match(/^\*\/(\d+)$/);
    if(everyMin && ho === '*' && dom === '*' && mon === '*' && dow === '*'){
      mode = 'minutes'; if($('cronEveryMinutes')) $('cronEveryMinutes').value = everyMin[1];
    } else if(/^\d+$/.test(mi) && everyHour && dom === '*' && mon === '*' && dow === '*'){
      mode = 'hours'; if($('cronEveryHours')) $('cronEveryHours').value = everyHour[1]; if($('cronHourMinute')) $('cronHourMinute').value = Number(mi);
    } else if(/^\d+$/.test(mi) && /^\d+$/.test(ho) && dom === '*' && mon === '*' && dow === '*'){
      mode = 'daily'; if($('cronDailyTime')) $('cronDailyTime').value = `${String(ho).padStart(2,'0')}:${String(mi).padStart(2,'0')}`;
    } else if(/^\d+$/.test(mi) && /^\d+$/.test(ho) && dom === '*' && mon === '*' && dow !== '*' && cronParseField(dow,0,6)){
      mode = 'weekly';
      if($('cronWeeklyTime')) $('cronWeeklyTime').value = `${String(ho).padStart(2,'0')}:${String(mi).padStart(2,'0')}`;
      cronUI.weeklyDays = new Set(cronParseField(dow,0,6));
    }
    cronUI.mode = mode;
    refreshCronWeeklyDays();
    selectCronFreq(mode);
  }

  function refreshCronUI(){
    const cron = buildCronFromControls();
    state.settings.autoSyncCron = cron;
    const exprEl = $('cronExprText'); if(exprEl) exprEl.textContent = cron || '（空）';
    const nextEl = $('cronNextText');
    if(nextEl){
      if(!cronValidate(cron)){ nextEl.textContent = '表达式无效，请检查'; }
      else {
        const runs = cronNextRuns(cron, 3);
        nextEl.textContent = runs.length ? '下次运行：' + runs.map(d=>d.toLocaleString()).join('；') : '一年内无匹配时间';
      }
    }
    if(typeof updateAutoSyncHelp === 'function') updateAutoSyncHelp();
    // In interval mode the range tracks the cron, so re-fill it when the cron changes.
    if(String(state.settings.autoSyncRangeMode) === 'interval') refreshAutoSyncRange();
  }

  function bindCronEditor(){
    document.querySelectorAll('#cronFreqRow button[data-freq]').forEach(btn=>{ btn.onclick = ()=>selectCronFreq(btn.dataset.freq); });
    const enabledInput = $('autoSyncEnabledInput');
    if(enabledInput) enabledInput.onchange = ()=>{
      state.settings.autoSyncEnabled = enabledInput.checked;
      refreshAutoSyncCollapse();
      if(typeof updateAutoSyncHelp === 'function') updateAutoSyncHelp();
    };
    const rangeBtn = $('autoSyncRangeBtn');
    if(rangeBtn) rangeBtn.onclick = ()=>{
      state.settings.autoSyncRangeMode = (String(state.settings.autoSyncRangeMode) === 'interval') ? 'fixed' : 'interval';
      refreshAutoSyncRange();
    };
    // Live-update only the help when the user edits the value (don't reset the field).
    const rangeNum = $('recentMonthsInput'); if(rangeNum) rangeNum.addEventListener('input', updateAutoSyncRangeHelp);
    // Custom .selectish dropdown for the unit (matches the type/sort dropdowns); onChange refreshes the help.
    if(typeof setupDropdown === 'function') rangeUnitDropdown = setupDropdown('autoSyncRangeUnitDropdown','autoSyncRangeUnit','autoSyncRangeUnitLabel', ()=>updateAutoSyncRangeHelp());
    ['cronEveryMinutes','cronEveryHours','cronHourMinute','cronDailyTime','cronWeeklyTime','cronCustomInput'].forEach(id=>{
      const el = $(id); if(el) el.oninput = refreshCronUI;
    });
    document.querySelectorAll('#cronWeeklyDaysRow button[data-day]').forEach(btn=>{
      btn.onclick = ()=>{
        const d = Number(btn.dataset.day);
        if(cronUI.weeklyDays.has(d)) cronUI.weeklyDays.delete(d); else cronUI.weeklyDays.add(d);
        refreshCronWeeklyDays(); refreshCronUI();
      };
    });
  }
