import {
  readSetting
} from "./chunk-2MAQ6JFL.js";

// src/lib/skills-cache.ts
var DEFAULT_FEED_URL = "https://www.agentstep.com/v1/skills/feed";
var DEFAULT_INDEX_URL = "https://www.agentstep.com/v1/skills/index";
function resolveFeedUrl() {
  return process.env.SKILLS_FEED_URL || readSetting("skills_feed_url") || DEFAULT_FEED_URL;
}
function resolveIndexUrl() {
  return process.env.SKILLS_INDEX_URL || readSetting("skills_index_url") || DEFAULT_INDEX_URL;
}
var FEED_TTL_MS = 60 * 60 * 1e3;
var INDEX_TTL_MS = 6 * 60 * 60 * 1e3;
var INDEX_FETCH_TIMEOUT_MS = 3e4;
var feedCache = { data: null, fetchedAt: 0, etag: null, promise: null };
var indexCache = { data: null, fetchedAt: 0, etag: null, promise: null };
var sortedViews = null;
function buildSortedViews(items) {
  sortedViews = {
    allTime: [...items].sort((a, b) => b.installsAllTime - a.installsAllTime),
    trending: [...items].sort((a, b) => b.installsTrending - a.installsTrending),
    hot: [...items].sort((a, b) => b.installsHot - a.installsHot),
    newest: [...items].sort((a, b) => new Date(b.firstSeenAt).getTime() - new Date(a.firstSeenAt).getTime())
  };
}
function normalizeFeed(raw) {
  const r = raw;
  const updatedAt = typeof r.updatedAt === "string" ? r.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
  if (Array.isArray(r.topAllTime)) {
    return {
      title: typeof r.title === "string" ? r.title : "Skills",
      updatedAt,
      topAllTime: r.topAllTime,
      topTrending: r.topTrending ?? r.topAllTime,
      topHot: r.topHot ?? r.topAllTime
    };
  }
  const allTime = r.allTime ?? r.items ?? r.skills;
  const providerId = typeof r.providerId === "string" ? r.providerId : "skills.sh";
  if (Array.isArray(allTime) && allTime.length > 0) {
    const mapped = allTime.slice(0, 50).map((s) => ({
      id: s.skillId ?? s.id ?? `${s.source}/${s.name}`,
      title: s.name ?? s.title ?? s.skillId,
      source: s.source ?? "",
      installs: s.installs ?? s.installsAllTime ?? 0,
      link: s.link ?? `https://github.com/${s.source}`,
      providerId: s.providerId ?? providerId,
      description: s.description ?? ""
    }));
    return { title: "Skills", updatedAt, topAllTime: mapped, topTrending: mapped, topHot: mapped };
  }
  return { title: "Skills", updatedAt, topAllTime: [], topTrending: [], topHot: [] };
}
async function fetchFeed() {
  const headers = {};
  if (feedCache.etag) headers["If-None-Match"] = feedCache.etag;
  const res = await fetch(resolveFeedUrl(), { headers });
  if (res.status === 304 && feedCache.data) {
    feedCache.fetchedAt = Date.now();
    return feedCache.data;
  }
  const etag = res.headers.get("etag");
  const data = normalizeFeed(await res.json());
  feedCache.data = data;
  feedCache.fetchedAt = Date.now();
  feedCache.etag = etag;
  return data;
}
function getFeed() {
  const now = Date.now();
  if (feedCache.data && now - feedCache.fetchedAt < FEED_TTL_MS) {
    return Promise.resolve(feedCache.data);
  }
  if (feedCache.promise) return feedCache.promise;
  feedCache.promise = fetchFeed().catch((err) => {
    if (feedCache.data) return feedCache.data;
    throw err;
  }).finally(() => {
    feedCache.promise = null;
  });
  return feedCache.promise;
}
function normalizeIndex(raw) {
  const r = raw;
  const updatedAt = typeof r.updatedAt === "string" ? r.updatedAt : (/* @__PURE__ */ new Date(0)).toISOString();
  const items = r.items ?? r.skills;
  if (Array.isArray(items) && items.length > 0 && typeof items[0] === "object" && "installsAllTime" in items[0]) {
    const count = r.count ?? r.totalSkills;
    return { updatedAt, count: typeof count === "number" ? count : items.length, items };
  }
  const allTime = r.allTime ?? items;
  const providerId = typeof r.providerId === "string" ? r.providerId : "skills.sh";
  if (Array.isArray(allTime) && allTime.length > 0) {
    const mapped = allTime.map((s) => ({
      id: s.skillId ?? s.id ?? `${s.source}/${s.name}`,
      providerId: s.providerId ?? providerId,
      source: s.source ?? "",
      skillId: s.skillId ?? s.name ?? s.id,
      title: s.name ?? s.title ?? s.skillId,
      link: s.link ?? `https://github.com/${s.source}`,
      installsAllTime: s.installs ?? s.installsAllTime ?? 0,
      installsTrending: s.installs ?? s.installsTrending ?? 0,
      installsHot: s.installs ?? s.installsHot ?? 0,
      firstSeenAt: s.firstSeenAt ?? "",
      description: s.description ?? ""
    }));
    return { updatedAt, count: mapped.length, items: mapped };
  }
  return { updatedAt, count: 0, items: [] };
}
async function fetchIndex() {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), INDEX_FETCH_TIMEOUT_MS);
  try {
    const headers = {};
    if (indexCache.etag) headers["If-None-Match"] = indexCache.etag;
    const res = await fetch(resolveIndexUrl(), { headers, signal: controller.signal });
    if (res.status === 304 && indexCache.data) {
      indexCache.fetchedAt = Date.now();
      return indexCache.data;
    }
    const etag = res.headers.get("etag");
    const data = normalizeIndex(await res.json());
    indexCache.data = data;
    indexCache.fetchedAt = Date.now();
    indexCache.etag = etag;
    buildSortedViews(data.items);
    return data;
  } finally {
    clearTimeout(timeout);
  }
}
function getIndex() {
  const now = Date.now();
  if (indexCache.data && now - indexCache.fetchedAt < INDEX_TTL_MS) {
    return Promise.resolve(indexCache.data);
  }
  if (indexCache.promise) return indexCache.promise;
  indexCache.promise = fetchIndex().catch((err) => {
    if (indexCache.data) return indexCache.data;
    throw err;
  }).finally(() => {
    indexCache.promise = null;
  });
  return indexCache.promise;
}
async function searchSkills(opts) {
  const index = await getIndex();
  let items = index.items;
  if (items.length === 0) {
    const feed = await getFeed();
    const seen = /* @__PURE__ */ new Set();
    const fromFeed = [];
    for (const list of [feed.topAllTime, feed.topTrending, feed.topHot]) {
      for (const s of list) {
        if (seen.has(s.id)) continue;
        seen.add(s.id);
        fromFeed.push({
          id: s.id,
          providerId: s.providerId,
          source: s.source,
          skillId: s.id,
          title: s.title,
          link: s.link,
          installsAllTime: s.installs,
          installsTrending: s.installs,
          installsHot: s.installs,
          firstSeenAt: "",
          description: s.description
        });
      }
    }
    items = fromFeed;
  }
  if (opts.q) {
    const q = opts.q.toLowerCase();
    items = items.filter(
      (s) => s.title.toLowerCase().includes(q) || s.source.toLowerCase().includes(q) || s.skillId.toLowerCase().includes(q)
    );
  }
  if (opts.owner) {
    const owner = opts.owner.toLowerCase();
    items = items.filter((s) => s.source.toLowerCase().startsWith(owner + "/"));
  }
  if (opts.repo || opts.source) {
    const src = (opts.source || opts.repo).toLowerCase();
    items = items.filter((s) => s.source.toLowerCase() === src);
  }
  const sort = opts.sort ?? "allTime";
  const hasFilters = opts.q || opts.owner || opts.source || opts.repo;
  if (!hasFilters && sortedViews && index.items.length > 0) {
    items = sortedViews[sort];
  } else {
    if (sort === "trending") items = [...items].sort((a, b) => b.installsTrending - a.installsTrending);
    else if (sort === "hot") items = [...items].sort((a, b) => b.installsHot - a.installsHot);
    else if (sort === "newest") items = [...items].sort((a, b) => new Date(b.firstSeenAt).getTime() - new Date(a.firstSeenAt).getTime());
    else items = [...items].sort((a, b) => b.installsAllTime - a.installsAllTime);
  }
  const total = items.length;
  const limit = Math.min(opts.limit ?? 50, 200);
  const offset = opts.offset ?? 0;
  const skills = items.slice(offset, offset + limit);
  return { skills, total, limit, offset };
}
async function getStats() {
  const feed = await getFeed();
  let totalSkills = 0;
  let totalSources = 0;
  let totalOwners = 0;
  let indexLoaded = false;
  if (indexCache.data) {
    indexLoaded = true;
    totalSkills = indexCache.data.count;
    const sources = /* @__PURE__ */ new Set();
    const owners = /* @__PURE__ */ new Set();
    for (const s of indexCache.data.items) {
      sources.add(s.source);
      owners.add(s.source.split("/")[0]);
    }
    totalSources = sources.size;
    totalOwners = owners.size;
  }
  return {
    totalSkills,
    totalSources,
    totalOwners,
    updatedAt: indexCache.data?.updatedAt ?? "",
    feedUpdatedAt: feed.updatedAt,
    indexLoaded
  };
}
async function getSources() {
  const index = await getIndex();
  const map = /* @__PURE__ */ new Map();
  for (const s of index.items) {
    const entry = map.get(s.source);
    if (entry) {
      entry.count++;
      entry.installs += s.installsAllTime;
    } else {
      map.set(s.source, { count: 1, installs: s.installsAllTime });
    }
  }
  return Array.from(map.entries()).map(([source, { count, installs }]) => ({
    source,
    owner: source.split("/")[0],
    skillCount: count,
    totalInstalls: installs
  })).sort((a, b) => b.totalInstalls - a.totalInstalls);
}

export {
  getFeed,
  getIndex,
  searchSkills,
  getStats,
  getSources
};
