import { getDatabase } from "../cache/db.js";
import { createLogger } from "../logger.js";
const log = createLogger("crawl");
function getCachedHeaders(url) {
  try {
    const db = getDatabase();
    const row = db.prepare(
      "SELECT etag, last_modified, fetched_at FROM crawl_etags WHERE url = ?"
    ).get(url);
    if (!row) return null;
    const out = { fetchedAt: row.fetched_at };
    if (row.etag) out.etag = row.etag;
    if (row.last_modified) out.lastModified = row.last_modified;
    return out;
  } catch (err) {
    log.debug("getCachedHeaders failed", { url, error: String(err) });
    return null;
  }
}
function saveFetchHeaders(url, headers) {
  try {
    const lower = {};
    for (const [k, v] of Object.entries(headers)) lower[k.toLowerCase()] = v;
    const etag = lower["etag"] ?? null;
    const lastModified = lower["last-modified"] ?? null;
    const origin = new URL(url).origin;
    const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
    const db = getDatabase();
    db.prepare(`
      INSERT INTO crawl_etags (url, origin, etag, last_modified, fetched_at)
      VALUES (?, ?, ?, ?, ?)
      ON CONFLICT(url) DO UPDATE SET
        origin = excluded.origin,
        etag = excluded.etag,
        last_modified = excluded.last_modified,
        fetched_at = excluded.fetched_at
    `).run(url, origin, etag, lastModified, fetchedAt);
  } catch (err) {
    log.debug("saveFetchHeaders failed", { url, error: String(err) });
  }
}
function markFetchedNotModified(url) {
  try {
    const db = getDatabase();
    db.prepare("UPDATE crawl_etags SET fetched_at = ? WHERE url = ?").run((/* @__PURE__ */ new Date()).toISOString(), url);
  } catch (err) {
    log.debug("markFetchedNotModified failed", { url, error: String(err) });
  }
}
async function conditionalFetch(url, rawFetchFn) {
  const cached = getCachedHeaders(url);
  const conditionalHeaders = {};
  if (cached?.etag) conditionalHeaders.ifNoneMatch = cached.etag;
  if (cached?.lastModified) conditionalHeaders.ifModifiedSince = cached.lastModified;
  const opts = conditionalHeaders.ifNoneMatch || conditionalHeaders.ifModifiedSince ? { conditionalHeaders } : void 0;
  const result = await rawFetchFn(url, opts);
  if (result.statusCode === 304) {
    markFetchedNotModified(url);
    return { ...result, notModified: true };
  }
  const respHeaders = {};
  for (const [k, v] of Object.entries(result.headers ?? {})) respHeaders[k.toLowerCase()] = v;
  const respEtag = respHeaders["etag"];
  const respLastModified = respHeaders["last-modified"];
  let notModified = false;
  if (cached) {
    if (cached.etag && respEtag && cached.etag === respEtag) notModified = true;
    else if (!respEtag && cached.lastModified && respLastModified && cached.lastModified === respLastModified) notModified = true;
  }
  if (respEtag || respLastModified) {
    saveFetchHeaders(url, result.headers ?? {});
  } else if (cached) {
    markFetchedNotModified(url);
  } else {
    saveFetchHeaders(url, result.headers ?? {});
  }
  if (notModified) return { ...result, notModified };
  return result;
}
function _clearEtagCacheForTest() {
  try {
    const db = getDatabase();
    db.exec("DELETE FROM crawl_etags");
  } catch {
  }
}
export {
  _clearEtagCacheForTest,
  conditionalFetch,
  getCachedHeaders,
  markFetchedNotModified,
  saveFetchHeaders
};
//# sourceMappingURL=etag-incremental.js.map