import { createHash } from "node:crypto";
import { createLogger } from "../logger.js";
import { computeDiffSummary } from "../cache/diff-summary.js";
import { handleFetch } from "../tools/fetch.js";
import { getOverdueJobs, recordCheck, getJob } from "./store.js";
import { guardUrl } from "./ssrf.js";
const log = createLogger("cache");
async function runCheck(job, router) {
  const report = { url: job.url, changed: false };
  const urlGuard = guardUrl(job.url, "url");
  if (!urlGuard.ok) {
    report.error = urlGuard.reason;
    log.warn("watch check rejected by ssrf guard", {
      id: job.id,
      url: job.url,
      reason: urlGuard.reason
    });
    recordCheck(job.id, Date.now(), job.last_content_hash ?? "");
    return report;
  }
  if (job.notification && job.notification !== "inline") {
    const notifGuard = guardUrl(job.notification, "notification");
    if (!notifGuard.ok) {
      report.error = notifGuard.reason;
      log.warn("watch check rejected by ssrf guard", {
        id: job.id,
        notification: job.notification,
        reason: notifGuard.reason
      });
      recordCheck(job.id, Date.now(), job.last_content_hash ?? "");
      return report;
    }
  }
  try {
    const fetched = await handleFetch(
      { url: job.url, include_full_markdown: true, force_refresh: true },
      router
    );
    if (!fetched.ok) {
      report.error = fetched.error_reason ?? fetched.error;
      log.warn("watch check fetch failed", { id: job.id, url: job.url, error: report.error });
      recordCheck(job.id, Date.now(), job.last_content_hash ?? "");
      return report;
    }
    const markdown = fetched.data.markdown ?? "";
    const currentHash = createHash("sha256").update(markdown).digest("hex");
    report.current_hash = currentHash;
    const previousHash = job.last_content_hash;
    if (!previousHash) {
      recordCheck(job.id, Date.now(), currentHash);
      log.info("watch baseline recorded", { id: job.id, url: job.url });
      return report;
    }
    if (previousHash === currentHash) {
      recordCheck(job.id, Date.now(), currentHash);
      return report;
    }
    report.changed = true;
    report.previous_hash = previousHash;
    if (job.selector) {
      log.debug("watch selector recorded but not yet applied to diff", {
        id: job.id,
        selector: job.selector
      });
    }
    report.diff_summary = computeDiffSummary("", markdown);
    recordCheck(job.id, Date.now(), currentHash);
    log.info("watch change detected", { id: job.id, url: job.url });
    if (job.notification && job.notification !== "inline") {
      void deliverWebhook(job.notification, { job_id: job.id, ...report }).catch((err) => {
        log.warn("watch webhook delivery failed", {
          id: job.id,
          url: job.notification,
          error: err instanceof Error ? err.message : String(err)
        });
      });
    }
    return report;
  } catch (err) {
    report.error = err instanceof Error ? err.message : String(err);
    log.warn("watch check threw", { id: job.id, error: report.error });
    return report;
  }
}
let firing = false;
async function triggerOverdueJobs(router) {
  if (firing) return;
  firing = true;
  try {
    let overdue;
    try {
      overdue = getOverdueJobs();
    } catch (err) {
      log.debug("watch lazy fire skipped \u2014 db not ready", {
        error: err instanceof Error ? err.message : String(err)
      });
      return;
    }
    if (overdue.length === 0) return;
    log.debug("watch lazy fire", { count: overdue.length });
    for (const job of overdue) {
      const fresh = getJob(job.id);
      if (!fresh || fresh.status !== "active") continue;
      await runCheck(fresh, router).catch((err) => {
        log.warn("watch lazy check failed", { id: job.id, error: String(err) });
      });
    }
  } finally {
    firing = false;
  }
}
function scheduleOverdueCheck(router) {
  setImmediate(() => {
    void triggerOverdueJobs(router).catch((err) => {
      log.debug("watch overdue trigger failed", { error: String(err) });
    });
  });
}
async function deliverWebhook(webhookUrl, payload) {
  const res = await fetch(webhookUrl, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(payload),
    redirect: "manual"
  });
  if (res.status >= 300 && res.status < 400) {
    log.debug("webhook redirect refused", {
      url: webhookUrl,
      status: res.status,
      location: res.headers.get("location") ?? null
    });
    throw new Error(`webhook returned a redirect (${res.status}); not followed`);
  }
  if (!res.ok) {
    throw new Error(`webhook responded ${res.status}`);
  }
}
function _resetSchedulerGuard() {
  firing = false;
}
export {
  _resetSchedulerGuard,
  runCheck,
  scheduleOverdueCheck,
  triggerOverdueJobs
};
//# sourceMappingURL=scheduler.js.map