// src/lib/bg-api.ts
var WEATHER_CODES = {
  0: { label: "\u6674" },
  1: { label: "\u591A\u4E91" },
  2: { label: "\u9634" },
  3: { label: "\u9634" },
  45: { label: "\u96FE" },
  48: { label: "\u96FE\u51C7" },
  51: { label: "\u5C0F\u6BDB\u6BDB\u96E8" },
  53: { label: "\u6BDB\u6BDB\u96E8" },
  55: { label: "\u5927\u6BDB\u6BDB\u96E8" },
  61: { label: "\u5C0F\u96E8" },
  63: { label: "\u4E2D\u96E8" },
  65: { label: "\u5927\u96E8" },
  71: { label: "\u5C0F\u96EA" },
  73: { label: "\u4E2D\u96EA" },
  75: { label: "\u5927\u96EA" },
  80: { label: "\u9635\u96E8" },
  81: { label: "\u4E2D\u9635\u96E8" },
  82: { label: "\u5927\u9635\u96E8" },
  95: { label: "\u96F7\u66B4" },
  96: { label: "\u96F7\u66B4\u51B0\u96F9" },
  99: { label: "\u5F3A\u96F7\u66B4\u51B0\u96F9" }
};
async function fetchWeather(lat, lng) {
  const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lng}&current=temperature_2m,apparent_temperature,weather_code,wind_speed_10m&daily=temperature_2m_max,temperature_2m_min&timezone=auto&forecast_days=1`;
  const res = await fetch(url);
  if (!res.ok) throw new Error(`Weather API ${res.status}`);
  const json = await res.json();
  const code = json.current?.weather_code ?? 0;
  return {
    temperature: Math.round(json.current?.temperature_2m ?? 0),
    apparentTemp: Math.round(json.current?.apparent_temperature ?? 0),
    weatherCode: code,
    weatherLabel: WEATHER_CODES[code]?.label ?? "\u672A\u77E5",
    windSpeed: Math.round(json.current?.wind_speed_10m ?? 0),
    dailyMax: Math.round(json.daily?.temperature_2m_max?.[0] ?? 0),
    dailyMin: Math.round(json.daily?.temperature_2m_min?.[0] ?? 0)
  };
}

// src/lib/license.ts
var API_BASE_URL = "https://blog.lvonce.com";
var INSTALLATION_ID_KEY = "extension-installation-id-v1";
var LICENSE_STATE_KEY = "extension-license-state-v1";
var ACCESS_RENEWAL_WINDOW_MS = 5 * 60 * 1e3;
var LicenseApiError = class extends Error {
  constructor(status, code, message) {
    super(message);
    this.status = status;
    this.code = code;
  }
};
var pendingLicenseCheck = null;
async function storageGet(key) {
  const result = await chrome.storage.local.get(key);
  return result[key];
}
async function getInstallationId() {
  const existing = await storageGet(INSTALLATION_ID_KEY);
  if (existing) return existing;
  const installationId = crypto.randomUUID();
  await chrome.storage.local.set({ [INSTALLATION_ID_KEY]: installationId });
  return installationId;
}
async function request(path, body, accessToken) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 1e4);
  try {
    const response = await fetch(`${API_BASE_URL}${path}`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        ...accessToken ? { Authorization: `Bearer ${accessToken}` } : {}
      },
      body: JSON.stringify(body),
      signal: controller.signal
    });
    const responseText = await response.text();
    let payload;
    try {
      payload = JSON.parse(responseText);
    } catch {
      const contentType = response.headers.get("content-type") ?? "unknown content type";
      throw new LicenseApiError(
        response.status,
        "invalid_license_response",
        `\u6388\u6743\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\uFF08HTTP ${response.status}, ${contentType}\uFF09`
      );
    }
    if (!response.ok || !payload.ok || !payload.data) {
      throw new LicenseApiError(
        response.status,
        payload.error?.code ?? "license_request_failed",
        payload.error?.message ?? "Extension license request failed"
      );
    }
    return payload.data;
  } finally {
    clearTimeout(timeout);
  }
}
async function saveSession(session) {
  const stored = {
    ...session,
    verified_at: (/* @__PURE__ */ new Date()).toISOString()
  };
  await chrome.storage.local.set({ [LICENSE_STATE_KEY]: stored });
  return stored;
}
function activeSnapshot(state) {
  return {
    status: "active",
    entitlement: state.entitlement,
    access_expires_at: state.access_expires_at
  };
}
async function bootstrap(installationId) {
  const session = await request("/api/extension/v1/session/bootstrap", {
    installation_id: installationId,
    extension_version: chrome.runtime.getManifest().version
  });
  return saveSession(session);
}
async function refresh(installationId, state) {
  const session = await request("/api/extension/v1/session/refresh", {
    installation_id: installationId,
    refresh_token: state.refresh_token,
    extension_version: chrome.runtime.getManifest().version
  });
  return saveSession(session);
}
async function verify(installationId, state) {
  const result = await request(
    "/api/extension/v1/session/verify",
    { installation_id: installationId },
    state.access_token
  );
  const verified = {
    ...state,
    access_expires_at: result.access_expires_at,
    entitlement: result.entitlement,
    verified_at: (/* @__PURE__ */ new Date()).toISOString()
  };
  await chrome.storage.local.set({ [LICENSE_STATE_KEY]: verified });
  return verified;
}
async function performLicenseCheck(force) {
  const installationId = await getInstallationId();
  const state = await storageGet(LICENSE_STATE_KEY);
  const now = Date.now();
  const accessExpiresAt = state ? Date.parse(state.access_expires_at) : 0;
  if (state && !force && accessExpiresAt - now > ACCESS_RENEWAL_WINDOW_MS) {
    return activeSnapshot(state);
  }
  try {
    if (state && accessExpiresAt - now > 6e4) {
      return activeSnapshot(await verify(installationId, state));
    }
    if (state && Date.parse(state.refresh_expires_at) > now) {
      return activeSnapshot(await refresh(installationId, state));
    }
    return activeSnapshot(await bootstrap(installationId));
  } catch (error) {
    if (error instanceof LicenseApiError && error.status === 402) {
      return {
        status: "payment_required",
        entitlement: state?.entitlement ?? null,
        access_expires_at: state?.access_expires_at ?? null,
        message: error.message
      };
    }
    if (error instanceof LicenseApiError && error.status === 401 && state) {
      try {
        return activeSnapshot(await bootstrap(installationId));
      } catch (bootstrapError) {
        error = bootstrapError;
      }
    }
    if (state && accessExpiresAt > now && state.entitlement.active) {
      return activeSnapshot(state);
    }
    return {
      status: "unavailable",
      entitlement: state?.entitlement ?? null,
      access_expires_at: state?.access_expires_at ?? null,
      message: error instanceof Error ? error.message : "Unable to verify extension access"
    };
  }
}
function ensureLicense(force = false) {
  if (!pendingLicenseCheck) {
    pendingLicenseCheck = performLicenseCheck(force).finally(() => {
      pendingLicenseCheck = null;
    });
  }
  return pendingLicenseCheck;
}

// src/lib/store.ts
var hasChromeStorage = typeof chrome !== "undefined" && chrome.storage?.local;
async function get(key) {
  if (hasChromeStorage) {
    const result = await chrome.storage.local.get(key);
    return result[key] ?? null;
  }
  try {
    const raw = localStorage.getItem(key);
    return raw ? JSON.parse(raw) : null;
  } catch {
    return null;
  }
}
async function set(key, value) {
  if (hasChromeStorage) {
    await chrome.storage.local.set({ [key]: value });
    return;
  }
  try {
    localStorage.setItem(key, JSON.stringify(value));
  } catch {
  }
}

// src/lib/bookmarks.ts
var BOOKMARKS_KEY = "lvonce-bookmarks-v2";
var ROOT_FOLDER_ID = "root";
function createBookmarkStore(now = (/* @__PURE__ */ new Date()).toISOString()) {
  return {
    version: 2,
    folders: [{ id: ROOT_FOLDER_ID, name: "\u5168\u90E8\u4E66\u7B7E", parentId: null, createdAt: now }],
    bookmarks: []
  };
}
async function loadBookmarkStore() {
  const stored = await get(BOOKMARKS_KEY);
  if (!stored || !Array.isArray(stored.bookmarks)) return createBookmarkStore();
  const folders = Array.isArray(stored.folders) ? stored.folders.filter(isBookmarkFolder) : [];
  if (!folders.some((folder) => folder?.id === ROOT_FOLDER_ID)) {
    folders.unshift(createBookmarkStore().folders[0]);
  }
  return {
    version: 2,
    folders,
    bookmarks: stored.bookmarks.filter(isExtensionBookmark)
  };
}
async function addBrowserBookmark(browserBookmarkId, title, rawUrl, createdAtMs, browserParentId) {
  const url = normalizeWebUrl(rawUrl);
  if (!url) return false;
  const store = await loadBookmarkStore();
  const alreadyStored = store.bookmarks.some(
    (bookmark) => bookmark.browserBookmarkId === browserBookmarkId || bookmark.url === url
  );
  if (alreadyStored) return false;
  const timestamp = new Date(createdAtMs ?? Date.now()).toISOString();
  store.bookmarks.unshift({
    id: crypto.randomUUID(),
    title: title.trim() || url,
    url,
    note: "",
    folderId: findBrowserFolderId(store, browserParentId),
    createdAt: timestamp,
    updatedAt: timestamp,
    browserBookmarkId
  });
  await set(BOOKMARKS_KEY, store);
  return true;
}
async function addBrowserFolder(browserBookmarkId, name, browserParentId, createdAtMs) {
  const store = await loadBookmarkStore();
  if (store.folders.some((folder) => folder.browserBookmarkId === browserBookmarkId)) return false;
  store.folders.push({
    id: crypto.randomUUID(),
    name: name.trim() || "\u672A\u547D\u540D\u5206\u7C7B",
    parentId: findBrowserFolderId(store, browserParentId),
    createdAt: new Date(createdAtMs ?? Date.now()).toISOString(),
    browserBookmarkId
  });
  await set(BOOKMARKS_KEY, store);
  return true;
}
function normalizeWebUrl(rawUrl) {
  try {
    const url = new URL(rawUrl);
    return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : null;
  } catch {
    return null;
  }
}
function findBrowserFolderId(store, browserParentId) {
  if (!browserParentId) return ROOT_FOLDER_ID;
  return store.folders.find((folder) => folder.browserBookmarkId === browserParentId)?.id ?? ROOT_FOLDER_ID;
}
function isBookmarkFolder(value) {
  if (!value || typeof value !== "object") return false;
  const folder = value;
  return typeof folder.id === "string" && typeof folder.name === "string" && (typeof folder.parentId === "string" || folder.parentId === null) && typeof folder.createdAt === "string";
}
function isExtensionBookmark(value) {
  if (!value || typeof value !== "object") return false;
  const bookmark = value;
  return typeof bookmark.id === "string" && typeof bookmark.title === "string" && typeof bookmark.url === "string" && typeof bookmark.folderId === "string";
}

// src/background.ts
var WEATHER_CACHE_TTL = 5 * 60 * 1e3;
var bookmarkSyncQueue = Promise.resolve();
chrome.bookmarks.onCreated.addListener((id, bookmark) => {
  bookmarkSyncQueue = bookmarkSyncQueue.then(
    () => bookmark.url ? addBrowserBookmark(id, bookmark.title, bookmark.url, bookmark.dateAdded, bookmark.parentId) : addBrowserFolder(id, bookmark.title, bookmark.parentId, bookmark.dateAdded)
  ).then(() => void 0).catch((error) => {
    console.error("Failed to sync browser bookmark:", error);
  });
});
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch((error) => {
  console.error("Failed to configure side panel behavior:", error);
});
var LICENSE_RENEWAL_ALARM = "extension-license-renewal";
function scheduleLicenseRenewal() {
  chrome.alarms.create(LICENSE_RENEWAL_ALARM, { periodInMinutes: 10 });
}
chrome.runtime.onInstalled.addListener(() => {
  scheduleLicenseRenewal();
  void ensureLicense(true);
});
chrome.runtime.onStartup.addListener(() => {
  scheduleLicenseRenewal();
  void ensureLicense(true);
});
chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === LICENSE_RENEWAL_ALARM) void ensureLicense(true);
});
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
  if (msg.type === "weather") {
    handleWeather(msg.lat, msg.lng).then(sendResponse).catch((e) => sendResponse({ error: e.message }));
    return true;
  }
  if (msg.type === "currency") {
    handleCurrency(msg.from, msg.to).then(sendResponse).catch((e) => sendResponse({ error: e.message }));
    return true;
  }
});
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
  if (msg?.type !== "extension-license-status") return;
  ensureLicense(Boolean(msg.force)).then(sendResponse).catch((error) => sendResponse({
    status: "unavailable",
    entitlement: null,
    access_expires_at: null,
    message: error instanceof Error ? error.message : "License verification failed"
  }));
  return true;
});
async function handleWeather(lat, lng) {
  const cacheKey = `weather-cache-${lat.toFixed(1)}-${lng.toFixed(1)}`;
  const cached = await chrome.storage.session.get(cacheKey);
  const entry = cached[cacheKey];
  if (entry && Date.now() - entry.ts < WEATHER_CACHE_TTL) {
    return entry.data;
  }
  const data = await fetchWeather(lat, lng);
  await chrome.storage.session.set({ [cacheKey]: { data, ts: Date.now() } });
  return data;
}
async function handleCurrency(from, to) {
  const cacheKey = "currency-cache-v1";
  const cached = await chrome.storage.session.get(cacheKey);
  const entry = cached[cacheKey];
  const now = Date.now();
  if (entry && now - entry.ts < 24 * 60 * 60 * 1e3) {
    return { rate: entry.rates?.[`${from}${to}`] ?? null };
  }
  const res = await fetch(`https://api.frankfurter.dev/v1/latest?from=${from}&to=${to}`);
  if (!res.ok) throw new Error(`Currency API ${res.status}`);
  const json = await res.json();
  const rate = json.rates?.[to] ?? null;
  await chrome.storage.session.set({
    [cacheKey]: { ts: now, rates: { [`${from}${to}`]: rate } }
  });
  return { rate };
}
