/**
 * 最小 cron expression matcher：5 欄位（minute hour dayOfMonth month dayOfWeek）。
 *
 * 用於 cypher-executor scheduled() handler — 把 workflow 註冊的 cron_expr 跟
 * 每分鐘 tick 的 event.scheduledTime 比對，匹配就觸發該 workflow。
 *
 * 支援語法（夠用即可，未來再擴）：
 *   `*`           — 任何值
 *   `5`           — 等於 5
 *   `*​/N`         — 每 N 個（N>0）
 *   `5,10,15`     — 任一
 *   `1-5`         — range（含兩端）
 *
 * 不支援（暫）：
 *   `?` / `L` / `W` / `#` 等延伸語法
 *   month / weekday 用名稱（jan/mon 等）
 *
 * 對應 SDD: arcrun.md 三-A P1 #3。
 */

/** 一個欄位（如 'minute'）的值是否匹配 expr 段 */
function matchField(expr: string, value: number, min: number, max: number): boolean {
  if (expr === '*') return true;
  for (const part of expr.split(',')) {
    if (matchPart(part.trim(), value, min, max)) return true;
  }
  return false;
}

function matchPart(part: string, value: number, min: number, max: number): boolean {
  // `*/N`
  if (part.startsWith('*/')) {
    const step = parseInt(part.slice(2), 10);
    if (!Number.isFinite(step) || step <= 0) return false;
    return (value - min) % step === 0;
  }
  // `X-Y` 或 `X-Y/N`
  if (part.includes('-')) {
    const [rangePart, stepStr] = part.split('/');
    const [aStr, bStr] = rangePart.split('-');
    const a = parseInt(aStr, 10);
    const b = parseInt(bStr, 10);
    if (!Number.isFinite(a) || !Number.isFinite(b)) return false;
    if (value < a || value > b) return false;
    if (stepStr === undefined) return true;
    const step = parseInt(stepStr, 10);
    if (!Number.isFinite(step) || step <= 0) return false;
    return (value - a) % step === 0;
  }
  // `N`
  const n = parseInt(part, 10);
  if (!Number.isFinite(n)) return false;
  if (n < min || n > max) return false;
  return value === n;
}

/**
 * 比對 cron expr 跟某個時間點。
 * @param expr - 5 欄位 cron（minute hour dom month dow）
 * @param date - 要比對的時間（UTC）
 */
export function cronMatch(expr: string, date: Date): boolean {
  const fields = expr.trim().split(/\s+/);
  if (fields.length !== 5) return false;
  const [m, h, dom, mon, dow] = fields;
  // dow: 0=Sun ... 6=Sat (跟 JavaScript 一致；ISO Mon=1 暫不轉)
  return (
    matchField(m, date.getUTCMinutes(), 0, 59) &&
    matchField(h, date.getUTCHours(), 0, 23) &&
    matchField(dom, date.getUTCDate(), 1, 31) &&
    matchField(mon, date.getUTCMonth() + 1, 1, 12) &&
    matchField(dow, date.getUTCDay(), 0, 6)
  );
}

/**
 * 從 workflow YAML 的 config 找出 cron 零件節點的 cron_expr。
 * 找不到回 null（代表此 workflow 不是 cron-triggered）。
 *
 * @param graph - acr push 解析後的 ExecutionGraph
 */
export function extractCronExpr(graph: unknown): string | null {
  if (!graph || typeof graph !== 'object') return null;
  const nodes = (graph as { nodes?: Array<{ id: string; componentId?: string; data?: Record<string, unknown> }> }).nodes;
  if (!Array.isArray(nodes)) return null;
  for (const node of nodes) {
    if (node.componentId !== 'cron') continue;
    const expr = node.data?.cron_expr;
    if (typeof expr === 'string' && expr.trim()) return expr.trim();
  }
  return null;
}
