(function (root, factory) {
  "use strict";
  const api = factory();
  if (typeof module === "object" && module.exports) module.exports = api;
  root.SDMExtensionAuth = api;
})(typeof globalThis !== "undefined" ? globalThis : this, function () {
  "use strict";

  const ACCOUNTS_KEY = "yilianExtensionAccountsV1";
  const ACTIVE_ACCOUNT_KEY = "yilianExtensionActiveAccountV1";
  const LEGACY_KEYS = ["workflowAuthToken", "workflowDataAccount", "workflowActiveStoreId"];

  class ExtensionAuthError extends Error {
    constructor(message, code = "EXTENSION_AUTH_ERROR", status = 0) {
      super(message);
      this.name = "ExtensionAuthError";
      this.code = code;
      this.status = Number(status || 0);
    }
  }

  function clean(value) {
    return String(value == null ? "" : value).trim();
  }

  function profileIdFor(username, dataAccount) {
    const loginAccount = clean(username || dataAccount).toLowerCase();
    return `${loginAccount}::${loginAccount}`;
  }

  function legacyLoginHint(profile) {
    const id = clean(profile?.id);
    const username = clean(profile?.username);
    const separator = id.indexOf("::");
    if (separator > 0) {
      const prefix = clean(id.slice(0, separator));
      const suffix = clean(id.slice(separator + 2));
      if (username && username.toLowerCase() === prefix.toLowerCase()) return username;
      if (prefix && prefix.toLowerCase() !== suffix.toLowerCase()
        && (!username || username.toLowerCase() === suffix.toLowerCase())) return prefix;
    }
    return clean(username || profile?.dataAccount);
  }

  function canonicalProfiles(rawProfiles, rawActiveId = "") {
    const profiles = Array.isArray(rawProfiles) ? rawProfiles.filter(item => item && typeof item === "object") : [];
    const parent = profiles.map((_, index) => index);
    const find = index => {
      let cursor = index;
      while (parent[cursor] !== cursor) {
        parent[cursor] = parent[parent[cursor]];
        cursor = parent[cursor];
      }
      return cursor;
    };
    const union = (left, right) => {
      const leftRoot = find(left);
      const rightRoot = find(right);
      if (leftRoot !== rightRoot) parent[rightRoot] = leftRoot;
    };
    const firstByLogin = new Map();
    const firstByToken = new Map();
    profiles.forEach((profile, index) => {
      const loginHint = legacyLoginHint(profile).toLowerCase();
      const token = clean(profile.token);
      if (loginHint) {
        if (firstByLogin.has(loginHint)) union(index, firstByLogin.get(loginHint));
        else firstByLogin.set(loginHint, index);
      }
      // 同一个 Bearer 会话只可能属于一个真实登录账号。旧版曾按当前查看的
      // 数据账号重复保存同一会话，因此还要用非空 token 合并这些历史记录。
      if (token) {
        if (firstByToken.has(token)) union(index, firstByToken.get(token));
        else firstByToken.set(token, index);
      }
    });
    const groups = new Map();
    profiles.forEach((profile, index) => {
      const rootIndex = find(index);
      const values = groups.get(rootIndex) || [];
      values.push(profile);
      groups.set(rootIndex, values);
    });
    const activeIndex = profiles.findIndex(profile => clean(profile.id) === clean(rawActiveId));
    const activeRoot = activeIndex >= 0 ? find(activeIndex) : -1;
    const canonicalByRoot = new Map();
    const canonical = [];
    for (const [rootIndex, values] of groups.entries()) {
      const canonicalScope = values.filter(profile => {
        const loginHint = legacyLoginHint(profile).toLowerCase();
        return loginHint
          && clean(profile.dataAccount).toLowerCase() === loginHint
          && clean(profile.id).toLowerCase() === profileIdFor(loginHint, loginHint);
      });
      const ownScope = values.filter(profile => clean(profile.dataAccount).toLowerCase() === legacyLoginHint(profile).toLowerCase());
      const candidates = canonicalScope.length ? canonicalScope : ownScope.length ? ownScope : values;
      const winner = [...candidates].sort((a, b) => clean(b.updatedAt).localeCompare(clean(a.updatedAt)))[0];
      const username = legacyLoginHint(winner);
      if (!username) continue;
      const hasOwnScope = clean(winner.dataAccount).toLowerCase() === username.toLowerCase();
      const normalized = {
        ...winner,
        id: profileIdFor(username, username),
        username,
        dataAccount: username,
        storeId: hasOwnScope ? clean(winner.storeId) : "",
        stores: hasOwnScope && Array.isArray(winner.stores) ? winner.stores : []
      };
      canonical.push(normalized);
      canonicalByRoot.set(rootIndex, normalized);
    }
    const activeId = canonicalByRoot.get(activeRoot)?.id || canonical[0]?.id || "";
    const needsMigration = canonical.length !== profiles.length || canonical.some(profile => {
      const original = profiles.find(item => clean(item.id) === clean(profile.id));
      return !original
        || clean(original.username).toLowerCase() !== clean(profile.username).toLowerCase()
        || clean(original.dataAccount).toLowerCase() !== clean(profile.username).toLowerCase();
    });
    return { profiles: canonical, activeId, needsMigration };
  }

  function publicStore(store) {
    return {
      id: clean(store?.id),
      name: clean(store?.name || store?.amazonAccount || store?.id),
      region: clean(store?.region),
      status: clean(store?.status)
    };
  }

  function publicProfile(profile) {
    return {
      id: clean(profile?.id),
      username: clean(profile?.username),
      displayName: clean(profile?.displayName || profile?.username),
      dataAccount: clean(profile?.dataAccount),
      storeId: clean(profile?.storeId),
      stores: Array.isArray(profile?.stores) ? profile.stores.map(publicStore).filter(store => store.id) : [],
      status: profile?.token ? "active" : clean(profile?.status || "expired"),
      updatedAt: clean(profile?.updatedAt)
    };
  }

  function errorDetail(payload, fallback) {
    const detail = payload?.detail;
    if (typeof detail === "string") return detail;
    if (detail && typeof detail.message === "string") return detail.message;
    return clean(payload?.msg) || fallback;
  }

  function createExtensionAuth(storage, settings, fetchImpl = globalThis.fetch) {
    if (!storage?.get || !storage?.set) throw new Error("插件账号管理缺少 storage 适配器");
    if (typeof fetchImpl !== "function") throw new Error("插件账号管理缺少网络请求能力");
    const apiBase = clean(settings?.ERP_BASE_URL).replace(/\/$/, "");
    if (!apiBase) throw new Error("插件账号管理缺少服务器地址");

    async function readRaw() {
      const saved = await storage.get([ACCOUNTS_KEY, ACTIVE_ACCOUNT_KEY, ...LEGACY_KEYS]);
      const canonical = canonicalProfiles(saved?.[ACCOUNTS_KEY], saved?.[ACTIVE_ACCOUNT_KEY]);
      return {
        profiles: canonical.profiles,
        activeId: canonical.activeId,
        needsMigration: canonical.needsMigration,
        legacy: {
          token: clean(saved?.workflowAuthToken),
          account: clean(saved?.workflowDataAccount),
          storeId: clean(saved?.workflowActiveStoreId)
        }
      };
    }

    async function writeRaw(profiles, activeId) {
      const active = profiles.find(profile => profile.id === activeId) || null;
      await storage.set({
        [ACCOUNTS_KEY]: profiles,
        [ACTIVE_ACCOUNT_KEY]: active?.id || "",
        workflowAuthToken: clean(active?.token),
        workflowDataAccount: clean(active?.dataAccount),
        workflowActiveStoreId: clean(active?.storeId)
      });
      return active;
    }

    async function serverRequest(path, context = {}, options = {}) {
      const headers = { ...(options.headers || {}) };
      const token = clean(context.token);
      if (token) headers.Authorization = `Bearer ${token}`;
      if (context.dataAccount) headers["X-Data-Account"] = clean(context.dataAccount);
      if (context.storeId) headers["X-Store-Id"] = clean(context.storeId);
      if (options.body != null && !headers["Content-Type"]) headers["Content-Type"] = "application/json";
      let response;
      try {
        response = await fetchImpl(`${apiBase}${path}`, { ...options, headers });
      } catch (error) {
        throw new ExtensionAuthError(`无法连接易联跨境服务器：${error?.message || String(error)}`, "ERP_UNAVAILABLE");
      }
      const payload = await response.json().catch(() => ({}));
      if (!response.ok) {
        const code = response.status === 401 ? "ERP_SESSION_EXPIRED" : response.status === 403 ? "ERP_PERMISSION_DENIED" : "ERP_REQUEST_FAILED";
        throw new ExtensionAuthError(errorDetail(payload, `易联跨境服务器返回 HTTP ${response.status}`), code, response.status);
      }
      return payload?.data == null ? payload : payload.data;
    }

    async function upsertSession(session, makeActive = true) {
      const token = clean(session?.token);
      const user = session?.user || {};
      const username = clean(user.username || session?.username);
      const dataAccount = username;
      if (!token || !username || !dataAccount) throw new ExtensionAuthError("服务器没有返回完整的登录会话", "INVALID_SESSION");
      const snapshot = await readRaw();
      const id = profileIdFor(username, dataAccount);
      const existing = snapshot.profiles.find(profile => profile.id === id) || {};
      const stores = (Array.isArray(session?.stores) ? session.stores : existing.stores || []).map(publicStore).filter(store => store.id);
      const allowedStoreIds = new Set(stores.map(store => store.id));
      let storeId = clean(session?.storeId || existing.storeId);
      if (!allowedStoreIds.has(storeId)) storeId = stores[0]?.id || "";
      const timestamp = new Date().toISOString();
      const profile = {
        ...existing,
        id,
        username,
        displayName: clean(user.displayName || session?.displayName || existing.displayName || username),
        dataAccount,
        token,
        storeId,
        stores,
        status: "active",
        createdAt: clean(existing.createdAt) || timestamp,
        updatedAt: timestamp
      };
      const profiles = snapshot.profiles.filter(item => (
        item.id !== id
        && (!token || clean(item.token) !== token)
        && clean(item.username || item.dataAccount).toLowerCase() !== username.toLowerCase()
      ));
      profiles.push(profile);
      await writeRaw(profiles, makeActive ? id : (snapshot.activeId || id));
      return profile;
    }

    async function loadStores(token, dataAccount, preferredStoreId = "") {
      const context = await serverRequest("/stores", { token, dataAccount, storeId: preferredStoreId });
      const stores = Array.isArray(context?.stores) ? context.stores.map(publicStore).filter(store => store.id) : [];
      const allowed = new Set(stores.map(store => store.id));
      const serverActive = clean(context?.activeStoreId);
      const preferred = clean(preferredStoreId);
      const storeId = allowed.has(preferred) ? preferred : allowed.has(serverActive) ? serverActive : (stores[0]?.id || "");
      return { stores, storeId };
    }

    async function login(usernameValue, passwordValue) {
      const username = clean(usernameValue);
      const password = String(passwordValue == null ? "" : passwordValue);
      if (!username || !password) throw new ExtensionAuthError("请输入账号和登录密码", "LOGIN_REQUIRED");
      const loginData = await serverRequest("/auth/login", {}, {
        method: "POST",
        body: JSON.stringify({ username, password })
      });
      const token = clean(loginData?.token);
      const user = loginData?.user || {};
      const dataAccount = clean(user.username || loginData?.dataAccount || username);
      const storeContext = await loadStores(token, dataAccount);
      return publicProfile(await upsertSession({ token, user, dataAccount, ...storeContext }, true));
    }

    async function syncContext(context) {
      const token = clean(context?.token);
      const requestedAccount = clean(context?.dataAccount || context?.account);
      const requestedStoreId = clean(context?.storeId);
      if (!token || !requestedAccount) throw new ExtensionAuthError("工作台没有提供完整的账号会话", "INVALID_SESSION");
      const snapshot = await readRaw();
      const existing = snapshot.profiles.find(profile => clean(profile.token) === token);
      const loginAccount = clean(existing?.username);
      const isOwnContext = Boolean(loginAccount) && requestedAccount.toLowerCase() === loginAccount.toLowerCase();
      if (existing && (!isOwnContext || !requestedStoreId || existing.stores?.some(store => clean(store.id) === requestedStoreId))) {
        const profiles = snapshot.profiles.map(profile => profile.id === existing.id
          ? { ...profile, storeId: isOwnContext ? (requestedStoreId || profile.storeId) : profile.storeId, status: "active", updatedAt: new Date().toISOString() }
          : profile);
        await writeRaw(profiles, existing.id);
        return publicProfile(profiles.find(profile => profile.id === existing.id));
      }
      const me = await serverRequest("/auth/me", { token });
      const user = me?.user || {};
      const username = clean(user.username || existing?.username || requestedAccount);
      const preferredStoreId = requestedAccount.toLowerCase() === username.toLowerCase()
        ? requestedStoreId
        : clean(existing?.storeId);
      const storeContext = await loadStores(token, username, preferredStoreId);
      return publicProfile(await upsertSession({
        token,
        user,
        username,
        dataAccount: username,
        ...storeContext
      }, true));
    }

    async function ensureLegacyProfile() {
      let snapshot = await readRaw();
      if (snapshot.needsMigration) {
        await writeRaw(snapshot.profiles, snapshot.activeId);
        const active = snapshot.profiles.find(profile => profile.id === snapshot.activeId);
        if (active?.token) {
          try {
            await switchAccount(active.id);
          } catch (error) {
            if (error?.status !== 401) throw error;
          }
        }
        snapshot = await readRaw();
      }
      if (!snapshot.legacy.token || !snapshot.legacy.account) return state();
      const matching = snapshot.profiles.find(profile => (
        clean(profile.token) === snapshot.legacy.token
        && clean(profile.dataAccount).toLowerCase() === snapshot.legacy.account.toLowerCase()
      ));
      if (matching) {
        if (snapshot.activeId !== matching.id) await writeRaw(snapshot.profiles, matching.id);
        return state();
      }
      try {
        await syncContext({
          token: snapshot.legacy.token,
          dataAccount: snapshot.legacy.account,
          storeId: snapshot.legacy.storeId
        });
      } catch (error) {
        if (error?.status === 401) {
          await storage.set({ workflowAuthToken: "", workflowDataAccount: "", workflowActiveStoreId: "" });
        }
      }
      return state();
    }

    async function state() {
      const snapshot = await readRaw();
      const active = snapshot.profiles.find(profile => profile.id === snapshot.activeId) || null;
      return {
        accounts: snapshot.profiles.map(publicProfile).sort((a, b) => a.displayName.localeCompare(b.displayName, "zh-CN")),
        activeAccountId: active?.id || "",
        activeAccount: active ? publicProfile(active) : null
      };
    }

    async function switchAccount(profileId) {
      const id = clean(profileId);
      const snapshot = await readRaw();
      const profile = snapshot.profiles.find(item => item.id === id);
      if (!profile) throw new ExtensionAuthError("找不到要切换的插件账号", "ACCOUNT_NOT_FOUND");
      if (!clean(profile.token)) {
        await writeRaw(snapshot.profiles, id);
        throw new ExtensionAuthError("该账号登录已过期，请重新输入密码", "ERP_SESSION_EXPIRED", 401);
      }
      await writeRaw(snapshot.profiles, id);
      try {
        // 不能相信旧插件记录里的 dataAccount/username。先只用 token 向服务器
        // 确认真正的登录账号，再用该账号加载店铺，避免串入正在查看的二级账号店铺。
        const me = await serverRequest("/auth/me", { token: profile.token });
        const authenticatedUsername = clean(me?.user?.username || profile.username || profile.dataAccount);
        const sameLogin = authenticatedUsername.toLowerCase() === clean(profile.username || profile.dataAccount).toLowerCase();
        const storeContext = await loadStores(profile.token, authenticatedUsername, sameLogin ? profile.storeId : "");
        return publicProfile(await upsertSession({
          token: profile.token,
          user: me?.user || { username: authenticatedUsername, displayName: profile.displayName },
          username: authenticatedUsername,
          dataAccount: authenticatedUsername,
          ...storeContext
        }, true));
      } catch (error) {
        if (error?.status === 401) await markExpired(id);
        throw error;
      }
    }

    async function selectStore(storeIdValue) {
      const storeId = clean(storeIdValue);
      const snapshot = await readRaw();
      const profile = snapshot.profiles.find(item => item.id === snapshot.activeId);
      if (!profile?.token) throw new ExtensionAuthError("请先在插件中登录账号", "ERP_SESSION_EXPIRED", 401);
      if (!profile.stores?.some(store => clean(store.id) === storeId)) throw new ExtensionAuthError("当前账号没有该店铺权限", "ERP_PERMISSION_DENIED", 403);
      try {
        await serverRequest("/stores/active", { token: profile.token, dataAccount: profile.dataAccount, storeId }, {
          method: "PUT",
          body: JSON.stringify({ storeId })
        });
      } catch (error) {
        if (error?.status === 401) await markExpired(profile.id);
        throw error;
      }
      const profiles = snapshot.profiles.map(item => item.id === profile.id
        ? { ...item, storeId, updatedAt: new Date().toISOString() }
        : item);
      await writeRaw(profiles, profile.id);
      return publicProfile(profiles.find(item => item.id === profile.id));
    }

    async function removeAccount(profileIdValue) {
      const id = clean(profileIdValue);
      const snapshot = await readRaw();
      const removed = snapshot.profiles.find(item => item.id === id);
      if (!removed) return state();
      const profiles = snapshot.profiles.filter(item => item.id !== id);
      const nextId = snapshot.activeId === id ? (profiles[0]?.id || "") : snapshot.activeId;
      await writeRaw(profiles, nextId);
      const sharedToken = profiles.some(item => item.token && item.token === removed.token);
      if (removed.token && !sharedToken) {
        await serverRequest("/auth/logout", { token: removed.token, dataAccount: removed.dataAccount, storeId: removed.storeId }, { method: "POST" }).catch(() => {});
      }
      return state();
    }

    async function markExpired(profileIdValue) {
      const id = clean(profileIdValue);
      const snapshot = await readRaw();
      const expiredToken = clean(snapshot.profiles.find(profile => profile.id === id)?.token);
      const profiles = snapshot.profiles.map(profile => (profile.id === id || (expiredToken && clean(profile.token) === expiredToken))
        ? { ...profile, token: "", status: "expired", updatedAt: new Date().toISOString() }
        : profile);
      await writeRaw(profiles, snapshot.activeId);
      return state();
    }

    async function resolve(requested = {}) {
      const snapshot = await readRaw();
      const profileId = clean(requested.profileId);
      const account = clean(requested.account);
      let profile = profileId ? snapshot.profiles.find(item => item.id === profileId) : null;
      if (!profile && profileId.includes("::")) {
        const loginAccount = profileId.split("::", 1)[0].toLowerCase();
        profile = snapshot.profiles.find(item => clean(item.username).toLowerCase() === loginAccount) || null;
      }
      if (!profile && account) {
        profile = snapshot.profiles.find(item => clean(item.dataAccount).toLowerCase() === account.toLowerCase() && item.token);
      }
      if (!profile) profile = snapshot.profiles.find(item => item.id === snapshot.activeId) || null;
      if (profile?.token) {
        return {
          token: clean(profile.token),
          account: account || clean(profile.dataAccount),
          storeId: clean(requested.storeId || profile.storeId),
          profileId: clean(profile.id)
        };
      }
      if (snapshot.legacy.token && (!account || snapshot.legacy.account.toLowerCase() === account.toLowerCase())) {
        return {
          token: snapshot.legacy.token,
          account: account || snapshot.legacy.account,
          storeId: clean(requested.storeId || snapshot.legacy.storeId),
          profileId: ""
        };
      }
      return { token: "", account: account || clean(profile?.dataAccount), storeId: clean(requested.storeId || profile?.storeId), profileId: clean(profile?.id) };
    }

    return {
      login,
      syncContext,
      ensureLegacyProfile,
      state,
      switchAccount,
      selectStore,
      removeAccount,
      markExpired,
      resolve,
      serverRequest
    };
  }

  return {
    ACCOUNTS_KEY,
    ACTIVE_ACCOUNT_KEY,
    ExtensionAuthError,
    profileIdFor,
    createExtensionAuth
  };
});
