import { q as query, l as log, a as logError } from '../../../../../chunks/db_Dsd4uyc_.mjs';
import crypto from 'crypto';
import { parse } from 'csv-parse';
import { Readable } from 'stream';
export { renderers } from '../../../../../renderers.mjs';

function stripHtml(value) {
  return value.replace(/<[^>]*>/g, "").replace(/&[a-z]+;/gi, " ").trim();
}
function parsePrice(value) {
  let cleaned = String(value).replace(/[^\d,.\-]/g, "");
  if (cleaned.includes(",") && !cleaned.includes(".")) {
    cleaned = cleaned.replace(",", ".");
  } else if (cleaned.includes(",") && cleaned.includes(".")) {
    if (cleaned.indexOf(",") > cleaned.indexOf(".")) {
      cleaned = cleaned.replace(/\./g, "").replace(",", ".");
    } else {
      cleaned = cleaned.replace(/,/g, "");
    }
  }
  const num = parseFloat(cleaned);
  return isNaN(num) ? "0" : num.toFixed(2);
}
function parseBoolean(value) {
  const v = String(value).toLowerCase().trim();
  return ["true", "1", "yes", "in stock", "available", "ja"].includes(v) ? "true" : "false";
}
const TRANSFORMS = {
  lowercase: (v) => v.toLowerCase(),
  uppercase: (v) => v.toUpperCase(),
  strip_html: stripHtml,
  strip_whitespace: (v) => v.replace(/\s+/g, "").trim(),
  parse_price: parsePrice,
  parse_boolean: parseBoolean,
  trim: (v) => v.trim(),
  regex: (v, args) => {
    if (!args?.pattern) return v;
    const match = v.match(new RegExp(args.pattern));
    return match ? match[1] || match[0] : v;
  }
};
const BRAND_ALIASES = {
  "nike": "Nike",
  "NIKE": "Nike",
  "adidas": "adidas",
  "Adidas": "adidas",
  "ADIDAS": "adidas",
  "new balance": "New Balance",
  "new-balance": "New Balance",
  "nb": "New Balance",
  "asics": "ASICS",
  "Asics": "ASICS",
  "puma": "PUMA",
  "Puma": "PUMA",
  "converse": "Converse",
  "CONVERSE": "Converse",
  "reebok": "Reebok",
  "REEBOK": "Reebok",
  "vans": "Vans",
  "VANS": "Vans",
  "jordan": "Jordan",
  "JORDAN": "Jordan",
  "air jordan": "Jordan",
  "saucony": "Saucony",
  "SAUCONY": "Saucony",
  "on": "On Running",
  "on running": "On Running",
  "On": "On Running",
  "hoka": "HOKA",
  "hoka one one": "HOKA",
  "HOKA ONE ONE": "HOKA"
};
function normalizeBrand(brand) {
  const trimmed = brand.trim();
  return BRAND_ALIASES[trimmed] || BRAND_ALIASES[trimmed.toLowerCase()] || trimmed;
}
function normalizeModel(name, brand) {
  let model = name.replace(new RegExp(brand, "gi"), "").trim();
  model = model.replace(/\b(maat|size|eu|us|uk)\s*\d+[\.\d]*/gi, "");
  model = model.replace(/\b(heren|damen|men'?s?|women'?s?|unisex|kids?|junior|gs|td|ps)\b/gi, "");
  model = model.replace(/\s+/g, " ").trim();
  return model;
}
function generateFingerprint(product) {
  const input = [
    product.brand_normalized,
    product.model_normalized,
    product.colorway_normalized
  ].filter(Boolean).join("|").toLowerCase();
  return crypto.createHash("sha256").update(input).digest("hex").slice(0, 16);
}
async function getMappings(providerId, feedId) {
  const result = await query(
    `SELECT * FROM field_mappings
     WHERE is_active = true
       AND (
         (provider_id = $1 AND feed_id IS NULL)
         OR (feed_id = $2)
       )
     ORDER BY feed_id NULLS LAST`,
    [providerId, feedId || null]
  );
  const mapped = /* @__PURE__ */ new Map();
  for (const m of result.rows.filter((r) => r.feed_id === null)) {
    mapped.set(m.source_field, m);
  }
  for (const m of result.rows.filter((r) => r.feed_id !== null)) {
    mapped.set(m.source_field, m);
  }
  return Array.from(mapped.values());
}
function applyMappings(raw, mappings) {
  const result = {};
  for (const mapping of mappings) {
    const sourceValue = raw[mapping.source_field];
    if (sourceValue == null || sourceValue === "") continue;
    let value = String(sourceValue);
    if (mapping.transform && TRANSFORMS[mapping.transform]) {
      try {
        value = TRANSFORMS[mapping.transform](value, mapping.transform_args);
      } catch {
      }
    }
    result[mapping.target_field] = value;
  }
  return result;
}
function normalizeProduct(mapped, externalId) {
  const name = mapped.name?.trim();
  const brand = mapped.brand?.trim();
  const price = parseFloat(mapped.price || "0");
  if (!name || !brand || price <= 0) return null;
  if (!mapped.product_url) return null;
  const brandNorm = normalizeBrand(brand).toLowerCase();
  const modelRaw = mapped.model || normalizeModel(name, brand);
  const modelNorm = modelRaw.toLowerCase().replace(/[^\w\s]/g, "").replace(/\s+/g, " ").trim();
  const colorNorm = mapped.colorway?.toLowerCase().trim() || void 0;
  const product = {
    name,
    brand: normalizeBrand(brand),
    model: modelRaw || void 0,
    colorway: mapped.colorway || void 0,
    ean: mapped.ean && mapped.ean.length >= 8 ? mapped.ean : void 0,
    sku: mapped.sku || void 0,
    price,
    original_price: mapped.original_price ? parseFloat(mapped.original_price) || void 0 : void 0,
    currency: mapped.currency || "EUR",
    product_url: mapped.product_url,
    image_url: mapped.image_url || void 0,
    description: mapped.description || void 0,
    category: mapped.category || void 0,
    size: mapped.size || void 0,
    in_stock: mapped.in_stock !== "false",
    brand_normalized: brandNorm,
    model_normalized: modelNorm || void 0,
    colorway_normalized: colorNorm,
    external_id: externalId || mapped.sku || void 0
  };
  product.match_fingerprint = generateFingerprint(product);
  return product;
}

function tokenize(s) {
  return new Set(s.toLowerCase().split(/[\s\-\/\+]+/).filter((t) => t.length > 0));
}
function tokenOverlap(a, b) {
  const tokensA = tokenize(a);
  const tokensB = tokenize(b);
  if (tokensA.size === 0 && tokensB.size === 0) return 1;
  if (tokensA.size === 0 || tokensB.size === 0) return 0;
  let intersection = 0;
  for (const t of tokensA) {
    if (tokensB.has(t)) intersection++;
  }
  const union = (/* @__PURE__ */ new Set([...tokensA, ...tokensB])).size;
  return intersection / union;
}
function computeSimilarity(a, candidate) {
  const modelScore = a.model_normalized && candidate.model_normalized ? tokenOverlap(a.model_normalized, candidate.model_normalized) : 0;
  const colorScore = a.colorway_normalized && candidate.colorway_normalized ? tokenOverlap(a.colorway_normalized, candidate.colorway_normalized) : 0.5;
  return modelScore * 0.7 + colorScore * 0.3;
}
async function matchProduct(product) {
  if (product.ean) {
    const eanResult = await query(
      "SELECT id FROM products WHERE ean = $1 OR gtin = $1 LIMIT 1",
      [product.ean]
    );
    if (eanResult.rows.length > 0) {
      return { productId: eanResult.rows[0].id, confidence: 1, matchType: "ean" };
    }
  }
  if (product.brand_normalized && product.model_normalized) {
    const exactResult = await query(
      `SELECT id FROM products
       WHERE brand_normalized = $1 AND model_normalized = $2
       LIMIT 1`,
      [product.brand_normalized, product.model_normalized]
    );
    if (exactResult.rows.length > 0) {
      return { productId: exactResult.rows[0].id, confidence: 0.95, matchType: "exact_model" };
    }
  }
  if (product.brand_normalized && product.model_normalized) {
    const fuzzyResult = await query(
      `SELECT id, brand_normalized, model_normalized, colorway_normalized, match_fingerprint
       FROM products
       WHERE brand_normalized = $1
       ORDER BY similarity(COALESCE(model_normalized, ''), $2) DESC
       LIMIT 10`,
      [product.brand_normalized, product.model_normalized]
    );
    for (const candidate of fuzzyResult.rows) {
      const score = computeSimilarity(product, candidate);
      if (score >= 0.85) {
        return { productId: candidate.id, confidence: score, matchType: "fuzzy" };
      }
    }
  }
  return { productId: null, confidence: 0, matchType: "new" };
}
async function createProduct(product) {
  const result = await query(
    `INSERT INTO products (name, brand, slug, image_url, ean, gtin, brand_normalized, model_normalized, colorway_normalized, match_fingerprint, created_at, updated_at)
     VALUES ($1, $2, $3, $4, $5, $5, $6, $7, $8, $9, NOW(), NOW())
     RETURNING id`,
    [
      product.name,
      product.brand,
      generateSlug(product.name, product.brand),
      product.image_url || null,
      product.ean || null,
      product.brand_normalized,
      product.model_normalized || null,
      product.colorway_normalized || null,
      product.match_fingerprint || null
    ]
  );
  return result.rows[0].id;
}
async function upsertOffer(productId, feedId, product) {
  const externalId = product.external_id || product.ean || `${product.brand}-${product.name}`.slice(0, 255);
  const result = await query(
    `INSERT INTO product_offers (product_id, feed_id, external_id, price, original_price, currency, product_url, image_url, in_stock, last_seen_at, updated_at)
     VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW())
     ON CONFLICT (feed_id, external_id)
     DO UPDATE SET
       product_id = $1,
       price = $4,
       original_price = $5,
       product_url = $7,
       image_url = $8,
       in_stock = $9,
       last_seen_at = NOW(),
       updated_at = NOW()
     RETURNING (xmax = 0) AS is_new`,
    [
      productId,
      feedId,
      externalId,
      product.price,
      product.original_price || null,
      product.currency,
      product.product_url,
      product.image_url || null,
      product.in_stock
    ]
  );
  return result.rows[0].is_new ? "new" : "updated";
}
function generateSlug(name, brand) {
  return `${brand}-${name}`.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 200);
}

const BATCH_SIZE$1 = 1e3;
class WebgainsAdapter {
  slug = "webgains";
  apiUrl = "";
  token = "";
  publisherId = "";
  async authenticate(config) {
    this.apiUrl = config.api_url || process.env.WEBGAINS_API_URL || "https://platform-api.webgains.com";
    this.token = config.token || process.env.WEBGAINS_TOKEN || "";
    this.publisherId = config.publisher_id || process.env.WEBGAINS_PUBLISHER_ID || "";
    if (!this.token) throw new Error("Webgains: missing API token");
    if (!this.publisherId) throw new Error("Webgains: missing publisher ID");
    log(`Webgains: authenticated (publisher ${this.publisherId})`);
  }
  headers() {
    return {
      "Authorization": `Bearer ${this.token}`,
      "Content-Type": "application/json"
    };
  }
  async listFeeds() {
    const url = `${this.apiUrl}/auth/publishers/${this.publisherId}/feeds`;
    const resp = await fetch(url, { headers: this.headers() });
    if (!resp.ok) throw new Error(`Webgains listFeeds: ${resp.status} ${resp.statusText}`);
    const data = await resp.json();
    return (data.data || []).map((f) => ({
      externalId: String(f.id),
      name: f.name || "",
      merchantName: f.program?.name || f.name || "",
      productCount: f.products_successfully_stored || 0,
      lastUpdated: null,
      feedUrl: void 0
    }));
  }
  async *fetchProducts(feedId, options) {
    options?.limit ? void 0 : void 0;
    let offset = options?.offset || 0;
    const pageSize = BATCH_SIZE$1;
    let hasMore = true;
    while (hasMore) {
      const url = new URL(`${this.apiUrl}/auth/publishers/${this.publisherId}/feeds/products`);
      url.searchParams.set("feeds[]", feedId);
      url.searchParams.set("size", String(pageSize));
      url.searchParams.set("offset", String(offset));
      url.searchParams.set("fields[]", "title");
      url.searchParams.append("fields[]", "brand");
      url.searchParams.append("fields[]", "price");
      url.searchParams.append("fields[]", "link");
      url.searchParams.append("fields[]", "image_link");
      url.searchParams.append("fields[]", "description");
      url.searchParams.append("fields[]", "id");
      url.searchParams.append("fields[]", "ean");
      url.searchParams.append("fields[]", "colour");
      url.searchParams.append("fields[]", "rrp");
      url.searchParams.append("fields[]", "currency");
      url.searchParams.append("fields[]", "category_name");
      url.searchParams.append("fields[]", "in_stock");
      url.searchParams.append("fields[]", "model_number");
      const resp = await fetch(url.toString(), { headers: this.headers() });
      if (!resp.ok) {
        if (resp.status === 429) {
          const retryAfter = parseInt(resp.headers.get("Retry-After") || "60");
          log(`Webgains: rate limited, waiting ${retryAfter}s`);
          await new Promise((r) => setTimeout(r, retryAfter * 1e3));
          continue;
        }
        throw new Error(`Webgains fetchProducts: ${resp.status} ${resp.statusText}`);
      }
      const data = await resp.json();
      const products = (data.data || []).map((p) => ({
        ...p,
        _external_id: String(p.id || "")
      }));
      if (products.length === 0) {
        hasMore = false;
      } else {
        yield products;
        offset += products.length;
        if (products.length < pageSize) hasMore = false;
      }
    }
  }
  async getFeedInfo(feedId) {
    const feeds = await this.listFeeds();
    const feed = feeds.find((f) => f.externalId === feedId);
    return {
      productCount: feed?.productCount || 0,
      lastUpdated: feed?.lastUpdated || null
    };
  }
}

const PAGE_SIZE = 100;
class TradeTrackerAdapter {
  slug = "tradetracker";
  customerId = "";
  accessKey = "";
  locale = "nl_NL";
  soapClient = null;
  authenticated = false;
  async authenticate(config) {
    this.customerId = config.customer_id || process.env.TRADETRACKER_CLIENT_ID || "";
    this.accessKey = config.access_key || process.env.TRADETRACKER_ACCESS_KEY || "";
    this.locale = config.locale || "nl_NL";
    if (!this.customerId || !this.accessKey) {
      throw new Error("TradeTracker: missing customer_id or access_key");
    }
    try {
      const soap = await import('soap');
      const wsdlUrl = config.wsdl_url || "https://ws.tradetracker.com/soap/affiliate?wsdl";
      this.soapClient = await soap.createClientAsync(wsdlUrl);
      await this.soapClient.authenticateAsync({
        customerID: parseInt(this.customerId),
        passphrase: this.accessKey,
        sandbox: false,
        locale: this.locale,
        demo: false
      });
      this.authenticated = true;
      log("TradeTracker: SOAP authenticated");
    } catch (err) {
      log(`TradeTracker: SOAP not available (${err.message}), will use feed URLs only`);
      this.authenticated = false;
    }
  }
  async listFeeds() {
    if (!this.soapClient || !this.authenticated) {
      log("TradeTracker: SOAP not available, returning empty feed list. Configure feeds manually with feed URLs.");
      return [];
    }
    try {
      const [sitesResult] = await this.soapClient.getAffiliateSitesAsync();
      const sites = sitesResult?.affiliateSites || [];
      if (sites.length === 0) {
        log("TradeTracker: no affiliate sites found");
        return [];
      }
      const siteId = sites[0].ID;
      const [feedsResult] = await this.soapClient.getFeedsAsync(siteId, {
        assignmentStatus: "accepted"
      });
      return (feedsResult?.feeds || []).map((f) => ({
        externalId: String(f.ID),
        name: f.name || "",
        merchantName: f.campaign?.name || f.name || "",
        productCount: Number(f.productCount) || 0,
        lastUpdated: f.updateDate ? new Date(f.updateDate) : null,
        feedUrl: f.URL || void 0
      }));
    } catch (err) {
      logError("TradeTracker listFeeds failed", err);
      return [];
    }
  }
  async *fetchProducts(feedId, options) {
    options?.limit !== void 0 ? void 0 : void 0;
    if (this.soapClient && this.authenticated) {
      yield* this.fetchViaSoap(feedId, options);
    } else {
      log(`TradeTracker: no SOAP client for feed ${feedId}, skipping`);
    }
  }
  async *fetchViaSoap(feedId, options) {
    const [sitesResult] = await this.soapClient.getAffiliateSitesAsync();
    const siteId = (sitesResult?.affiliateSites || [])[0]?.ID;
    if (!siteId) throw new Error("TradeTracker: no affiliate site found");
    let offset = options?.offset || 0;
    let hasMore = true;
    while (hasMore) {
      try {
        const [result] = await this.soapClient.getFeedProductsAsync(siteId, {
          feedID: parseInt(feedId),
          limit: PAGE_SIZE,
          offset
        });
        const products = result?.feedProducts || [];
        if (products.length === 0) {
          hasMore = false;
          break;
        }
        const batch = products.map((p) => ({
          _external_id: p.identifier || "",
          name: p.name || "",
          description: p.description || "",
          price: String(p.price || ""),
          productURL: p.productURL || "",
          imageURL: p.imageURL || "",
          productCategoryName: p.productCategoryName || "",
          ...this.flattenAdditional(p.additional)
        }));
        yield batch;
        offset += PAGE_SIZE;
        if (products.length < PAGE_SIZE) hasMore = false;
        await new Promise((r) => setTimeout(r, 200));
      } catch (err) {
        logError(`TradeTracker fetch error at offset ${offset}`, err);
        hasMore = false;
      }
    }
  }
  flattenAdditional(additional) {
    if (!additional || !Array.isArray(additional)) return {};
    const result = {};
    for (const item of additional) {
      if (item.name && item.value != null) {
        result[`additional_${item.name}`] = String(item.value);
      }
    }
    return result;
  }
  async getFeedInfo(feedId) {
    const feeds = await this.listFeeds();
    const feed = feeds.find((f) => f.externalId === feedId);
    return {
      productCount: feed?.productCount || 0,
      lastUpdated: feed?.lastUpdated || null
    };
  }
}

const BATCH_SIZE = 1e3;
class AwinAdapter {
  slug = "awin";
  apiToken = "";
  publisherId = "";
  async authenticate(config) {
    this.apiToken = config.api_token || process.env.AWIN_API_TOKEN || "";
    this.publisherId = config.publisher_id || process.env.AWIN_PUBLISHER_ID || "";
    if (!this.apiToken || !this.publisherId) {
      log("Awin: credentials not configured yet (placeholder adapter)");
      return;
    }
    log(`Awin: authenticated (publisher ${this.publisherId})`);
  }
  async listFeeds() {
    if (!this.apiToken || !this.publisherId) {
      log("Awin: not configured, returning empty feed list");
      return [];
    }
    try {
      const resp = await fetch(
        `https://api.awin.com/publishers/${this.publisherId}/programmedetails`,
        { headers: { Authorization: `Bearer ${this.apiToken}` } }
      );
      if (!resp.ok) throw new Error(`Awin: ${resp.status} ${resp.statusText}`);
      const programmes = await resp.json();
      return programmes.filter((p) => p.hasProductFeed).map((p) => ({
        externalId: String(p.id),
        name: p.name || "",
        merchantName: p.name || "",
        productCount: p.productFeedCount || 0,
        lastUpdated: null,
        feedUrl: p.productFeedUrl || void 0
      }));
    } catch (err) {
      logError("Awin listFeeds failed", err);
      return [];
    }
  }
  async *fetchProducts(feedId, _options) {
    if (!this.apiToken) {
      log("Awin: not configured, skipping fetch");
      return;
    }
    const feedUrl = `https://productdata.awin.com/datafeed/list/apikey/${this.apiToken}/language/nl/fid/${feedId}/format/csv/`;
    try {
      const resp = await fetch(feedUrl);
      if (!resp.ok) throw new Error(`Awin feed download: ${resp.status}`);
      const text = await resp.text();
      const records = await this.parseCsv(text);
      for (let i = 0; i < records.length; i += BATCH_SIZE) {
        const batch = records.slice(i, i + BATCH_SIZE).map((row) => ({
          ...row,
          _external_id: row.aw_product_id || row.merchant_product_id || ""
        }));
        yield batch;
      }
    } catch (err) {
      logError(`Awin fetch error for feed ${feedId}`, err);
    }
  }
  parseCsv(text) {
    return new Promise((resolve, reject) => {
      const records = [];
      const parser = parse({
        columns: true,
        skip_empty_lines: true,
        relax_column_count: true,
        delimiter: ","
      });
      parser.on("readable", () => {
        let record;
        while ((record = parser.read()) !== null) {
          records.push(record);
        }
      });
      parser.on("error", reject);
      parser.on("end", () => resolve(records));
      Readable.from(text).pipe(parser);
    });
  }
  async getFeedInfo(feedId) {
    return { productCount: 0, lastUpdated: null };
  }
}

const ADAPTERS = {
  webgains: WebgainsAdapter,
  tradetracker: TradeTrackerAdapter,
  awin: AwinAdapter
};
function getAdapter(adapterType) {
  const AdapterClass = ADAPTERS[adapterType];
  if (!AdapterClass) throw new Error(`Unknown adapter type: ${adapterType}`);
  return new AdapterClass();
}
async function syncFeed(feedId) {
  const stats = {
    fetched: 0,
    new: 0,
    updated: 0,
    removed: 0,
    matched: 0,
    errors: 0,
    errorDetails: []
  };
  const feedResult = await query(
    `SELECT f.*, p.slug as provider_slug, p.config as provider_config, p.adapter_type
     FROM feeds f JOIN providers p ON f.provider_id = p.id
     WHERE f.id = $1`,
    [feedId]
  );
  if (feedResult.rows.length === 0) throw new Error(`Feed ${feedId} not found`);
  const feed = feedResult.rows[0];
  const logResult = await query(
    `INSERT INTO sync_logs (feed_id, status) VALUES ($1, 'running') RETURNING id`,
    [feedId]
  );
  const syncLogId = logResult.rows[0].id;
  const syncStart = /* @__PURE__ */ new Date();
  try {
    log(`Sync started: feed ${feedId} (${feed.name}) via ${feed.provider_slug}`);
    const adapter = getAdapter(feed.adapter_type);
    const config = { ...feed.provider_config, ...feed.config };
    await adapter.authenticate(config);
    const mappings = await getMappings(feed.provider_id, feedId);
    if (mappings.length === 0) {
      throw new Error(`No field mappings found for feed ${feedId} (provider ${feed.provider_id})`);
    }
    const externalId = feed.external_id || String(feedId);
    for await (const batch of adapter.fetchProducts(externalId)) {
      for (const raw of batch) {
        try {
          stats.fetched++;
          const mapped = applyMappings(raw, mappings);
          const extId = String(raw._external_id || raw.identifier || raw.id || "");
          const normalized = normalizeProduct(mapped, extId);
          if (!normalized) {
            stats.errors++;
            if (stats.errorDetails.length < 100) {
              stats.errorDetails.push({ message: `Failed to normalize product: ${mapped.name || "unknown"}` });
            }
            continue;
          }
          const match = await matchProduct(normalized);
          let productId;
          if (match.productId) {
            productId = match.productId;
            stats.matched++;
          } else {
            productId = await createProduct(normalized);
            stats.new++;
          }
          const offerResult = await upsertOffer(productId, feedId, normalized);
          if (offerResult === "updated") stats.updated++;
        } catch (err) {
          stats.errors++;
          if (stats.errorDetails.length < 100) {
            stats.errorDetails.push({ message: err.message });
          }
        }
      }
    }
    const staleResult = await query(
      `UPDATE product_offers SET in_stock = false, updated_at = NOW()
       WHERE feed_id = $1 AND last_seen_at < $2 AND in_stock = true
       RETURNING id`,
      [feedId, syncStart]
    );
    stats.removed = staleResult.rowCount || 0;
    await query(
      `UPDATE feeds SET
        last_sync_at = NOW(),
        last_sync_status = 'success',
        last_error = NULL,
        product_count = $2,
        updated_at = NOW()
       WHERE id = $1`,
      [feedId, stats.fetched]
    );
    await query(
      `UPDATE sync_logs SET
        finished_at = NOW(), status = 'success',
        products_fetched = $2, products_new = $3, products_updated = $4,
        products_removed = $5, products_matched = $6, errors_count = $7,
        error_details = $8
       WHERE id = $1`,
      [
        syncLogId,
        stats.fetched,
        stats.new,
        stats.updated,
        stats.removed,
        stats.matched,
        stats.errors,
        stats.errorDetails.length > 0 ? JSON.stringify(stats.errorDetails) : null
      ]
    );
    log(`Sync completed: feed ${feedId} — ${stats.fetched} fetched, ${stats.new} new, ${stats.updated} updated, ${stats.matched} matched, ${stats.removed} stale, ${stats.errors} errors`);
  } catch (err) {
    logError(`Sync failed: feed ${feedId}`, err);
    await query(
      `UPDATE sync_logs SET finished_at = NOW(), status = 'failed', error_details = $2 WHERE id = $1`,
      [syncLogId, JSON.stringify([{ message: err.message }])]
    );
    await query(
      `UPDATE feeds SET last_sync_at = NOW(), last_sync_status = 'failed', last_error = $2, updated_at = NOW() WHERE id = $1`,
      [feedId, err.message]
    );
    throw err;
  }
  return stats;
}

const POST = async ({ params }) => {
  try {
    const id = parseInt(params.id);
    if (isNaN(id)) {
      return new Response(JSON.stringify({ error: "Invalid feed ID" }), { status: 400 });
    }
    const stats = await syncFeed(id);
    return new Response(JSON.stringify({ success: true, stats }), {
      status: 200,
      headers: { "Content-Type": "application/json" }
    });
  } catch (err) {
    return new Response(JSON.stringify({ error: err.message }), { status: 500 });
  }
};

const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
  __proto__: null,
  POST
}, Symbol.toStringTag, { value: 'Module' }));

const page = () => _page;

export { page };
