// 小青蛙电商助手 - 后台页面脚本 v3.1 (大白鹅风格)

function ensureSafeChromeRuntime() {
  if (typeof window === 'undefined') return;
  const storeKey = 'temuWebBridgeStorage';
  const loadStore = () => {
    try {
      return JSON.parse(localStorage.getItem(storeKey) || '{}') || {};
    } catch {
      return {};
    }
  };
  const saveStore = (store) => {
    try {
      localStorage.setItem(storeKey, JSON.stringify(store || {}));
    } catch {
      // Rendering the website must not depend on browser storage availability.
    }
  };
  const callbackResult = (value, callback) => {
    if (typeof callback === 'function') queueMicrotask(() => callback(value));
    return Promise.resolve(value);
  };
  const fallbackRuntime = {
    getURL(path) {
      return String(path || '').replace(/^\/+/, '');
    },
    sendMessage(message, callback) {
      const response = { success: false, skipped: true, error: 'Execution plugin is not connected', message };
      if (typeof callback === 'function') queueMicrotask(() => callback(response));
      return Promise.resolve(response);
    }
  };
  const fallbackStorage = {
    get(keys, callback) {
      const store = loadStore();
      let result = {};
      if (keys === null || keys === undefined) result = { ...store };
      else if (typeof keys === 'string') result = { [keys]: store[keys] };
      else if (Array.isArray(keys)) result = keys.reduce((acc, key) => ({ ...acc, [key]: store[key] }), {});
      else if (typeof keys === 'object') result = Object.keys(keys).reduce((acc, key) => ({ ...acc, [key]: store[key] === undefined ? keys[key] : store[key] }), {});
      return callbackResult(result, callback);
    },
    set(values, callback) {
      const store = loadStore();
      Object.assign(store, values || {});
      saveStore(store);
      return callbackResult(undefined, callback);
    },
    remove(keys, callback) {
      const store = loadStore();
      (Array.isArray(keys) ? keys : [keys]).filter(Boolean).forEach(key => delete store[key]);
      saveStore(store);
      return callbackResult(undefined, callback);
    },
    clear(callback) {
      saveStore({});
      return callbackResult(undefined, callback);
    }
  };
  const baseChrome = window.chrome || {};
  window.chrome = {
    ...baseChrome,
    runtime: {
      ...fallbackRuntime,
      ...(baseChrome.runtime || {})
    },
    storage: {
      ...(baseChrome.storage || {}),
      local: {
        ...fallbackStorage,
        ...(baseChrome.storage?.local || {})
      }
    }
  };
}

ensureSafeChromeRuntime();

function normalizeMallIdValue(value) {
  const text = String(value || '').trim();
  if (!text) return '';
  if (text.toLowerCase().startsWith('ozon:')) return text.split(',')[0].trim();
  const match = text.match(/\d{6,}/);
  return match ? match[0] : text.split(',')[0].trim();
}

function getMallIdPlatform(value) {
  return String(value || '').trim().toLowerCase().startsWith('ozon:') ? 'ozon' : 'temu';
}

function getActiveWorkspacePlatform() {
  try {
    if (typeof getSelectedAuthPlatform === 'function') return getSelectedAuthPlatform();
  } catch (error) {
    // The platform helpers are declared later in the bundled script.
  }
  return 'temu';
}

function isMallIdAllowedForActivePlatform(value) {
  const platform = getMallIdPlatform(value);
  const mallId = normalizeMallIdValue(value);
  if (!mallId) return false;
  return platform === getActiveWorkspacePlatform();
}

function clearMismatchedShopState(mallId, context = '平台校验') {
  if (!mallId || isMallIdAllowedForActivePlatform(mallId)) return false;
  state.mallId = null;
  state.antiContent = null;
  state.mallName = null;
  try {
    if (typeof chrome !== 'undefined') chrome.storage?.local?.remove?.(['temuAuth', 'temuLocalMallId'])?.catch?.(() => {});
  } catch (error) {
    // Ignore storage cleanup failures; keeping state clean is enough for this render.
  }
  console.warn(`[小青蛙电商助手] ${context}: 已忽略跨平台店铺 ${mallId}`);
  return true;
}

function normalizeAntiContentValue(value) {
  const text = String(value || '').trim();
  if (!text) return '';
  return text.split(',').map(part => part.trim()).filter(Boolean)[0] || '';
}

function normalizeTemuAuth(auth = {}) {
  return {
    ...auth,
    mallId: normalizeMallIdValue(auth.mallId),
    antiContent: normalizeAntiContentValue(auth.antiContent)
  };
}

function applyTemuAuthToState(auth = {}) {
  const normalized = normalizeTemuAuth(auth);
  if (normalized.mallId && !isMallIdAllowedForActivePlatform(normalized.mallId)) {
    clearMismatchedShopState(normalized.mallId, '恢复当前店铺');
    return { ...normalized, mallId: '' };
  }
  if (normalized.mallId) state.mallId = normalized.mallId;
  if (normalized.antiContent) state.antiContent = normalized.antiContent;
  if (auth.mallName !== undefined) state.mallName = auth.mallName || null;
  return normalized;
}

// ===== 全局图片加载失败处理（替代 onerror 内联事件，兼容 MV3 CSP）=====
document.addEventListener('error', function(e) {
  if (e.target && e.target.tagName === 'IMG') {
    const ozonPreviewModal = e.target.closest?.('#ozonImagePreviewModal');
    if (e.target.id === 'ozonImagePreviewImg' || ozonPreviewModal) {
      e.target.classList.add('is-error');
      const errorEl = ozonPreviewModal?.querySelector?.('.ozon-image-preview-error')
        || e.target.closest?.('.ozon-image-preview-body')?.querySelector?.('.ozon-image-preview-error');
      if (errorEl) errorEl.style.display = 'block';
      return;
    }
    const size = parseInt(e.target.dataset.fallbackSize) || 44;
    const fontSize = size >= 48 ? 18 : 16;
    const placeholder = document.createElement('div');
    placeholder.style.cssText = `width:${size}px;height:${size}px;background:#f5f5f5;border-radius:4px;display:flex;align-items:center;justify-content:center;color:#ccc;font-size:${fontSize}px;flex-shrink:0;`;
    placeholder.textContent = '📷';
    e.target.replaceWith(placeholder);
  }
}, true); // 使用捕获阶段，确保先于其他处理器

window.addEventListener('unhandledrejection', (event) => {
  const message = event.reason?.message || String(event.reason || '未知异步错误');
  console.error('[小青蛙电商助手] 未捕获异步错误:', event.reason);
  if (typeof showToast === 'function') {
    showToast(`操作失败: ${message}`, 'error');
  }
  event.preventDefault();
});

window.addEventListener('error', (event) => {
  if (event.target && event.target.tagName === 'IMG') return;
  const message = event.message || event.error?.message || '未知脚本错误';
  console.error('[小青蛙电商助手] 未捕获脚本错误:', event.error || message);
  if (typeof showToast === 'function') {
    showToast(`页面脚本错误: ${message}`, 'error');
  }
  event.preventDefault();
});

async function safeRuntimeSendMessage(message, fallback = null, timeoutMs = 30000) {
  if (isOzonWorkspace() && shouldBlockTemuRuntimeMessageOnOzon(message)) {
    return fallback ?? { success: false, skipped: true, error: 'OZON 工作台使用官方 API，不调用 TEMU 执行插件' };
  }
  try {
    const messageWithTimeout = message && typeof message === 'object'
      ? { timeoutMs: Math.max(Number(timeoutMs || 30000), Number(message.timeoutMs || 0)), ...message }
      : message;
    const enrichedMessage = await enrichRuntimeMessageWithServerAuth(messageWithTimeout);
    return await Promise.race([
      chrome.runtime.sendMessage(enrichedMessage),
      new Promise((_, reject) => setTimeout(() => reject(new Error('background 响应超时')), timeoutMs))
    ]);
  } catch (error) {
    const text = error?.message || String(error) || '消息发送失败';
    console.warn('[小青蛙电商助手] 后台消息失败:', message?.action || message?.type || message, text);
    return fallback ?? { success: false, error: text };
  }
}

function shouldBlockTemuRuntimeMessageOnOzon(message = {}) {
  const action = String(message?.action || message?.type || '').trim();
  if (!action) return false;
  return new Set([
    'autoDetectAuth',
    'getAuth',
    'setAuth',
    'clearAuth',
    'getCurrentMallId',
    'fetchProductList',
    'fetchLivePhotoProducts',
    'fetchActivityList',
    'fetchQualificationList',
    'fetchPricingList',
    'getDashboardHomeSales'
  ]).has(action);
}

function isExtensionContextInvalidatedError(error) {
  const text = typeof error === 'string' ? error : (error?.message || error?.error || String(error || ''));
  return /Extension context invalidated|Execution plugin is not connected|Receiving end does not exist|Could not establish connection|消息发送失败|background 响应超时/i.test(text);
}

function getRuntimeReconnectMessage(featureName = '当前功能') {
  return `${featureName}连接已失效，请刷新小青蛙网页，并在首页点击“重新连接”后再试。`;
}

async function enrichRuntimeMessageWithServerAuth(message) {
  if (!message || typeof message !== 'object' || message.serverAuthToken) return message;
  try {
    const auth = await getServerAuthState();
    if (!auth?.temuServerToken) return message;
    return {
      ...message,
      serverAuthToken: auth.temuServerToken,
      serverAuthUser: auth.temuServerUser || null,
      serverAuthAt: auth.temuServerAuthAt || Date.now()
    };
  } catch {
    return message;
  }
}

async function sendRuntimeMessageWithRetry(message, fallback = null, timeoutMs = 30000, retries = 1) {
  let lastResult = fallback;
  for (let attempt = 0; attempt <= retries; attempt += 1) {
    const result = await safeRuntimeSendMessage(message, fallback, timeoutMs);
    lastResult = result;
    if (result?.success !== false || attempt >= retries) return result;
    const errorText = String(result.error || '');
    if (!/未完成连接|未连接|Extension context invalidated|background 响应超时|消息发送失败|Receiving end does not exist/i.test(errorText)) {
      return result;
    }
    await new Promise(resolve => setTimeout(resolve, 800));
  }
  return lastResult;
}

const rawRuntimeSendMessage = chrome.runtime.sendMessage.bind(chrome.runtime);
chrome.runtime.sendMessage = function safeGlobalRuntimeSendMessage(...args) {
  try {
    if (isOzonWorkspace() && shouldBlockTemuRuntimeMessageOnOzon(args[0])) {
      const response = { success: false, skipped: true, error: 'OZON 工作台使用官方 API，不调用 TEMU 执行插件' };
      const callback = args.find(arg => typeof arg === 'function');
      if (callback) queueMicrotask(() => callback(response));
      return Promise.resolve(response);
    }
    if (args[0] && typeof args[0] === 'object' && !args[0].serverAuthToken) {
      const enrichedPromise = enrichRuntimeMessageWithServerAuth(args[0])
        .then(enrichedMessage => rawRuntimeSendMessage(enrichedMessage, ...args.slice(1)))
        .catch(error => {
          const text = error?.message || String(error) || '消息发送失败';
          console.warn('[小青蛙电商助手] 全局后台消息失败:', args[0]?.action || args[0]?.type || args[0], text);
          return { success: false, error: text };
        });
      return enrichedPromise;
    }
    const maybePromise = rawRuntimeSendMessage(...args);
    if (maybePromise && typeof maybePromise.then === 'function') {
      return maybePromise.catch(error => {
        const text = error?.message || String(error) || '消息发送失败';
        console.warn('[小青蛙电商助手] 全局后台消息失败:', args[0]?.action || args[0]?.type || args[0], text);
        return { success: false, error: text };
      });
    }
    return maybePromise;
  } catch (error) {
    const text = error?.message || String(error) || '消息发送失败';
    console.warn('[小青蛙电商助手] 全局后台消息异常:', args[0]?.action || args[0]?.type || args[0], text);
    return Promise.resolve({ success: false, error: text });
  }
};

async function safeStorageSet(values = {}, context = 'storage') {
  try {
    await chrome.storage.local.set(values);
    return true;
  } catch (error) {
    console.warn(`[小青蛙电商助手] ${context} 写入失败:`, error);
    return false;
  }
}

function isWebsiteBridgeMode() {
  return window.location.protocol.startsWith('http');
}

function getBridgeRuntimeState() {
  const active = Boolean(window.__TEMU_WEB_BRIDGE_ACTIVE__) || document.documentElement?.getAttribute?.('data-temu-extension-installed') === '1';
  const ready = Boolean(window.__TEMU_WEB_BRIDGE_READY__);
  const attrReady = document.documentElement?.getAttribute?.('data-temu-bridge-ready') === '1';
  return {
    active,
    ready: ready || attrReady
  };
}

function updateBridgeBanner(options = {}) {
  const banner = document.getElementById('bridgeBanner');
  const titleEl = document.getElementById('bridgeBannerTitle');
  const descEl = document.getElementById('bridgeBannerDesc');
  if (!banner || !titleEl || !descEl) return;

  const visible = Boolean(options.visible);
  banner.classList.toggle('show', visible);
  banner.classList.toggle('is-warning', Boolean(options.warning));
  banner.style.display = 'none';
  if (!visible) return;

  titleEl.textContent = options.title || '网站工作台已启动';
  descEl.textContent = options.description || '请保持“小青蛙电商助手”已安装，并在另一个标签页登录 TEMU 卖家后台，这样网站上的操作才能直接调用本地浏览器执行。';
}

async function reconnectCurrentShop() {
  const btn = document.getElementById('reconnectBtn');
  const dhBtn = document.getElementById('dhTemuReconnectBtn');
  const dhQuickBtn = document.getElementById('dhTemuReconnectQuickBtn');
  const stored = await chrome.storage.local.get(['temuAuth', 'temuLocalMallId']).catch(() => ({}));
  const storedAuth = stored?.temuAuth || {};
  const previousAuth = {
    mallId: normalizeMallIdValue(state.mallId || storedAuth.mallId || stored?.temuLocalMallId || ''),
    antiContent: normalizeAntiContentValue(state.antiContent || storedAuth.antiContent || ''),
    mallName: state.mallName || storedAuth.mallName || null
  };
  const hasPreviousAuth = Boolean(previousAuth.mallId && previousAuth.antiContent);
  if (btn) {
    btn.disabled = true;
    btn.textContent = '⏳ 检测中...';
  }
  if (dhBtn) {
    dhBtn.disabled = true;
    if (dhBtn.querySelector('b')) dhBtn.querySelector('b').textContent = '正在检测店铺';
    else dhBtn.textContent = '正在检测店铺...';
  }
  if (dhQuickBtn) {
    dhQuickBtn.disabled = true;
    dhQuickBtn.querySelector('b') && (dhQuickBtn.querySelector('b').textContent = '正在检测店铺');
  }

  try {
    const currentPageAuth = await safeRuntimeSendMessage({ action: 'getCurrentMallId' }, null, 4000);
    const currentPageMallId = normalizeMallIdValue(currentPageAuth?.mallId || '');
    if (currentPageMallId && currentPageMallId !== previousAuth.mallId) {
      rememberShopName(previousAuth.mallId, previousAuth.mallName);
      await chrome.storage.local.remove(['temuAuth']);
      await safeRuntimeSendMessage({ action: 'clearAuth' }, { success: false }, 2500);
      state.mallId = null;
      state.antiContent = null;
      state.mallName = currentPageAuth?.mallName || null;
      resetSalesDataState();
      updateSalesSyncDisplay();
      renderSalesDataTable();
      updateConnectionStatus(false);
    } else {
      await safeRuntimeSendMessage({ action: 'clearAuth' }, { success: false }, 2500);
    }

    const detectPromise = autoDetectAuth({ forceRefresh: true, rejectCache: true });
    const timeoutPromise = new Promise((_, reject) =>
      setTimeout(() => reject(new Error('检测超时（15秒），请刷新页面重试')), 15000)
    );
    await Promise.race([detectPromise, timeoutPromise]);
    if (!state.mallId || !state.antiContent) {
      throw new Error('未获取到完整店铺认证信息');
    }
    rememberShopName(state.mallId, state.mallName);
    await loadDashboardMembership();
    await refreshDashboardHomeSales();
  } catch (err) {
    console.error('[小青蛙电商助手] 重新连接失败:', err);
    if (hasPreviousAuth) {
      const restoredAuth = applyTemuAuthToState(previousAuth);
      await safeStorageSet({
        temuAuth: { ...storedAuth, ...restoredAuth, updatedAt: Date.now() },
        temuLocalMallId: restoredAuth.mallId
      }, '恢复店铺认证信息');
      await safeRuntimeSendMessage({
        action: 'setAuth',
        mallId: restoredAuth.mallId,
        antiContent: restoredAuth.antiContent,
        mallName: restoredAuth.mallName || null
      });
      updateConnectionStatus(true, restoredAuth.mallName);
      showToast('重新连接暂未获取到新认证，已保持当前店铺连接', 'warning');
    } else {
      showToast('连接失败，请刷新 TEMU 后台后再试', 'error');
      updateConnectionStatus(false);
    }
  } finally {
    const latestBtn = document.getElementById('reconnectBtn');
    const latestDhBtn = document.getElementById('dhTemuReconnectBtn');
    const latestDhQuickBtn = document.getElementById('dhTemuReconnectQuickBtn');
    if (latestBtn) {
      latestBtn.disabled = false;
      latestBtn.textContent = '🔄 重新连接';
    }
    if (latestDhBtn) {
      latestDhBtn.disabled = false;
      if (latestDhBtn.querySelector('b')) latestDhBtn.querySelector('b').textContent = '重新连接店铺';
      else latestDhBtn.textContent = '重新连接店铺';
    }
    if (latestDhQuickBtn) {
      latestDhQuickBtn.disabled = false;
      latestDhQuickBtn.querySelector('b') && (latestDhQuickBtn.querySelector('b').textContent = '重新连接店铺');
    }
  }
}

function visitCurrentStore() {
  const mallId = state.mallId;
  if (mallId) {
    window.open(`https://www.temu.com/mall.html?mall_id=${mallId}`, '_blank');
  } else {
    showToast('未检测到店铺ID，请先连接店铺', 'warning');
  }
}

function syncWebsiteBridgeUi() {
  if (isOzonWorkspace()) {
    updateBridgeBanner({ visible: false });
    return;
  }
  if (!isWebsiteBridgeMode()) return;
  const statusEl = document.getElementById('connectionStatus');
  const mallInfoEl = document.getElementById('mallInfo');
  const bridge = getBridgeRuntimeState();

  if (state.mallId && state.antiContent) {
    updateConnectionStatus(true, state.mallName);
    return;
  }

  if (!bridge.active) {
    if (statusEl) {
      statusEl.textContent = '未装助手';
      statusEl.className = 'status-badge status-bridge-missing';
    }
    if (mallInfoEl) mallInfoEl.textContent = '请先安装并启用小青蛙电商助手';
    updateBridgeBanner({
      visible: true,
      warning: true,
      title: '还没有连接到小青蛙电商助手',
      description: '当前网页已经切到网站版工作台，但真正执行 TEMU 操作仍需要浏览器里的“小青蛙电商助手”。请确认插件已加载，并允许它在 xiaoqingwa.vip 运行。'
    });
    syncDingHuiTemuHome();
    return;
  }

  if (!bridge.ready) {
    if (statusEl) {
      statusEl.textContent = '等待助手';
      statusEl.className = 'status-badge status-disconnected';
    }
    if (mallInfoEl) mallInfoEl.textContent = '小青蛙电商助手已安装，正在等待连接';
    updateBridgeBanner({
      visible: true,
      title: '正在连接小青蛙电商助手',
      description: '助手已注入网站页面。如果长时间停留在这里，请刷新当前页面，或确认扩展已允许在本站运行。'
    });
    syncDingHuiTemuHome();
    return;
  }

  initDeferredBridgeModulesIfReady();
  restoreConnectedShopFromStorage({ updateUi: true, detectIfMissing: true }).then((connected) => {
    if (!connected && !(state.mallId && state.antiContent)) {
      scheduleBridgeShopDetection();
    }
  }).catch(err => {
    console.warn('[小青蛙电商助手] 桥接就绪后主动读取店铺失败:', err);
  });
  if (statusEl) {
    statusEl.textContent = '助手已连接';
    statusEl.className = 'status-badge status-bridge-ready';
  }
  if (mallInfoEl) mallInfoEl.textContent = getCurrentShopDisplayName();
  updateBridgeBanner({
    visible: true,
    title: '网站工作台已连接小青蛙电商助手',
    description: '账号和店铺授权以服务器为准；需要执行 TEMU 操作时，助手会再读取当前浏览器里的 TEMU 店铺。'
  });
  syncDingHuiTemuHome();
}

window.addEventListener('temu-web-bridge-state-change', () => {
  if (isOzonWorkspace()) return;
  syncWebsiteBridgeUi();
  restoreConnectedShopFromStorage({ updateUi: true, detectIfMissing: true }).catch(err => {
    console.warn('[小青蛙电商助手] 桥接就绪后恢复店铺状态失败:', err);
  });
});

function shouldDeferBridgeDependentInit() {
  if (isOzonWorkspace()) return false;
  if (!isWebsiteBridgeMode()) return false;
  const bridge = getBridgeRuntimeState();
  return !(bridge.active && bridge.ready);
}

const deferredBridgeModules = {
  customized: false,
  activity: false,
  qualification: false
};
let deferredStartupTasksScheduled = false;
let restoreShopPromise = null;
let bridgeShopDetectPromise = null;
let bridgeShopDetectStartedAt = 0;

function scheduleDeferredStartupTasks() {
  if (isOzonWorkspace()) return;
  if (deferredStartupTasksScheduled) return;
  deferredStartupTasksScheduled = true;
  window.setTimeout(() => {
    const tasks = [
      ['检测认证信息', autoDetectAuth],
      ['加载设置', loadSettings],
      ['加载合规模板', loadTemplates],
      ['加载销量缓存', loadSalesDataCache]
    ];
    tasks.reduce((chain, [label, task]) => chain.then(() => task().catch((error) => {
      console.warn(`[小青蛙电商助手] 延迟${label}失败:`, error);
    })), Promise.resolve());
  }, 1200);
}

async function restoreConnectedShopFromStorage(options = {}) {
  if (isOzonWorkspace()) return false;
  const { updateUi = false, detectIfMissing = false } = options;
  if (restoreShopPromise) return restoreShopPromise;
  restoreShopPromise = (async () => {
  try {
    const stored = await Promise.race([
      chrome.storage.local.get(['temuAuth', 'temuLocalMallId']),
      new Promise((resolve) => window.setTimeout(() => resolve({}), 700))
    ]);
    let auth = normalizeTemuAuth(stored?.temuAuth || {});
    if ((!auth.mallId || !auth.antiContent) && getBridgeRuntimeState().ready) {
      const authResult = await safeRuntimeSendMessage({ action: 'getAuth' }, null, 1200);
      if (authResult?.mallId) {
        auth = normalizeTemuAuth({
          mallId: authResult.mallId,
          antiContent: authResult.antiContent || auth.antiContent || '',
          mallName: authResult.mallName || auth.mallName || null,
          updatedAt: Date.now()
        });
        await safeStorageSet({ temuAuth: auth, temuLocalMallId: auth.mallId }, '店铺认证信息');
      }
    }
    const mallId = normalizeMallIdValue(auth.mallId || stored?.temuLocalMallId || '');
    if (!mallId) return false;

    state.mallId = mallId;
    state.mallName = auth.mallName || state.mallName || null;
    if (auth.antiContent) state.antiContent = normalizeAntiContentValue(auth.antiContent);
    if (updateUi) updateConnectionStatus(true, state.mallName);
    return true;
  } catch (error) {
    console.warn('[小青蛙电商助手] 恢复店铺连接状态失败:', error?.message || error);
    return false;
  }
  })();
  try {
    const connected = await restoreShopPromise;
    if (!connected && detectIfMissing) {
      scheduleBridgeShopDetection();
    }
    return connected;
  } finally {
    restoreShopPromise = null;
  }
}

function scheduleBridgeShopDetection(force = false) {
  if (isOzonWorkspace()) return;
  if (!isWebsiteBridgeMode()) return;
  const bridge = getBridgeRuntimeState();
  if (!bridge.active || !bridge.ready) return;
  if (state.mallId && state.antiContent) return;
  const now = Date.now();
  if (!force && bridgeShopDetectPromise && now - bridgeShopDetectStartedAt < 15000) return;
  bridgeShopDetectStartedAt = now;
  bridgeShopDetectPromise = (async () => {
    try {
      const result = await safeRuntimeSendMessage({ action: 'autoDetectAuth' }, null, 6500);
      if (result?.success && result.mallId) {
        const normalized = normalizeTemuAuth(result);
        state.mallId = normalized.mallId;
        state.mallName = result.mallName || state.mallName || null;
        if (normalized.antiContent) state.antiContent = normalized.antiContent;
        await safeStorageSet({
          temuAuth: {
            mallId: state.mallId,
            antiContent: state.antiContent || '',
            mallName: state.mallName || null,
            updatedAt: Date.now()
          },
          temuLocalMallId: state.mallId
        }, '店铺认证信息');
        updateConnectionStatus(Boolean(state.mallId && state.antiContent), state.mallName);
        loadDashboardMembership().catch(err => console.warn('[小青蛙电商助手] 店铺检测后刷新会员状态失败:', err));
        return true;
      }
      console.warn('[小青蛙电商助手] 桥接店铺检测未成功:', result?.error || result);
      return false;
    } catch (error) {
      console.warn('[小青蛙电商助手] 桥接店铺检测失败:', error?.message || error);
      return false;
    } finally {
      window.setTimeout(() => {
        bridgeShopDetectPromise = null;
      }, 1000);
    }
  })();
}

function scheduleDashboardDataRefresh() {
  if (isOzonWorkspace()) return;
  if (!state.mallId || !state.antiContent) return;
  window.setTimeout(() => {
    Promise.allSettled([
      refreshDashboardHomeSales(),
      refreshDashboardStockReminder(true)
    ]).catch(err => console.warn('[小青蛙电商助手] 后台刷新首页数据失败:', err));
  }, 700);
}

function getShopDisplayName(shop = null) {
  if (!shop || typeof shop !== 'object') return '';
  const mallId = normalizeMallIdValue(shop.mallId);
  const name = pickRealShopName([shop.shopName, shop.mallName, shop.storeName, shop.name], mallId);
  if (name) {
    rememberShopName(mallId, name);
    return name;
  }
  return getCachedShopName(mallId);
}

function getCurrentShopDisplayName() {
  if (state.mallId) {
    const matchedShop = findBoundShopForMall(state.boundShops || [], state.mallId);
    const name = pickRealShopName([state.mallName, getShopDisplayName(matchedShop), getCachedShopName(state.mallId)], state.mallId)
      || `店铺 ${state.mallId}`;
    return `${name} (${state.mallId})`;
  }
  const shopList = Array.isArray(state.boundShops) ? state.boundShops : [];
  if (shopList.length === 1) {
    const shop = shopList[0];
    const name = getShopDisplayName(shop) || (shop.mallId ? `店铺 ${shop.mallId}` : '已绑定店铺');
    return `${name}${shop.mallId ? ` (${shop.mallId})` : ''}`;
  }
  if (shopList.length > 1) return `已加载 ${shopList.length} 个绑定店铺`;
  return '账号已登录，暂无绑定店铺';
}

function enterDashboardOptimistically(token, user = null, message = '登录成功，正在同步会员和店铺信息...') {
  applyRoleFeatureVisibility(user);
  renderDashAuthStatus(token, user);
  setDashboardAuthPanelVisible(false);
  setDashboardLockedState(false);
  setDashAuthResult(message, 'success');
  if (isOzonWorkspace()) {
    setFeatureAccessState(false, '');
    switchWorkspaceForPlatform('ozon');
    scheduleOzonPostAuthLoad({ forceInitialCache: true, delayMs: 120 });
    return;
  }
  switchWorkspaceForPlatform('temu');
  renderDashboardAccessPanels(user, null, state.boundShops || [], null);
  window.setTimeout(() => {
    restoreConnectedShopFromStorage({ updateUi: true, detectIfMissing: true }).catch(err => {
      console.warn('[小青蛙电商助手] 登录后恢复店铺状态失败:', err);
    });
  }, 350);
  window.setTimeout(() => {
    loadDashboardMembership().catch(err => console.warn('[xiaoqingwa] refresh membership after login failed:', err));
  }, 2200);
}


function waitForDashboardPaint() {
  return new Promise((resolve) => {
    if (typeof requestAnimationFrame !== 'function') {
      window.setTimeout(resolve, 0);
      return;
    }
    requestAnimationFrame(() => window.setTimeout(resolve, 0));
  });
}

function renderPendingDashboardLogin(user = {}) {
  const phoneEl = document.getElementById('dashLoginPhone');
  const nameEl = document.getElementById('dashLoginNickname');
  const roleEl = document.getElementById('dashLoginRole');
  const memberEl = document.getElementById('dashMemberStatus');
  if (phoneEl) phoneEl.textContent = user.phone || '同步中...';
  if (nameEl) nameEl.textContent = user.nickname || '同步中...';
  if (roleEl) roleEl.textContent = '同步中...';
  if (memberEl) memberEl.textContent = '同步中...';
}

function initDeferredBridgeModulesIfReady() {
  if (isOzonWorkspace()) return false;
  if (shouldDeferBridgeDependentInit()) return false;

  if (!deferredBridgeModules.customized) {
    initCustomizedTab();
    deferredBridgeModules.customized = true;
  }
  if (!deferredBridgeModules.activity) {
    initActivityTab();
    deferredBridgeModules.activity = true;
  }
  if (!deferredBridgeModules.qualification) {
    initQualificationTab();
    deferredBridgeModules.qualification = true;
  }
  scheduleDeferredStartupTasks();
  return true;
}

const SERVER_AUTH_KEYS = ['temuServerToken', 'temuServerUser', 'temuServerAuthAt'];
let xlsxLoadPromise = null;
const AUTO_BOUND_SHOPS_KEY = 'temuAutoBoundShops';
const SHOP_NAME_CACHE_KEY = 'temuShopNameCache';
const OZON_READ_CACHE_PREFIX = 'xqwOzonReadCache:';
const OZON_READ_CACHE_VERSION = 24;
const OZON_READ_CACHE_DEFAULT_TTL_MS = 60 * 1000;
const OZON_READ_CACHE_MEMORY_MAX = 24;
const OZON_READ_CACHE_LOCAL_MAX_CHARS = 2_500_000;
const OZON_HOME_READ_TIMEOUT_MS = 8000;
const OZON_MARKETING_LIST_READ_TIMEOUT_MS = 6000;
const OZON_SERVER_CACHE_ONLY_PATHS = new Set([
  '/overview',
  '/tasks/summary',
  '/products',
  '/orders',
  '/analytics/orders',
  '/analytics/products',
  '/collection-links',
  '/performance/campaigns',
  '/performance/traffic',
  '/marketing/actions',
  '/marketing/discount-tasks',
  '/finance',
  '/messages',
  '/messages/changes',
  '/messages/history',
  '/notification-events'
]);
const OZON_READ_CACHE_MEMORY_ONLY_PATHS = new Set([
  '/accounts',
  '/performance/config',
  '/notification-events'
]);
const OZON_READ_CACHE_TTLS = {
  '/accounts': 30 * 60 * 1000,
  '/overview': 3 * 60 * 1000,
  '/tasks/summary': 60 * 1000,
  '/products': 10 * 60 * 1000,
  '/orders': 5 * 60 * 1000,
  '/analytics/orders': 5 * 60 * 1000,
  '/analytics/products': 10 * 60 * 1000,
  '/collection-links': 10 * 60 * 1000,
  '/sync-tasks': 2 * 60 * 1000,
  '/performance/config': 30 * 60 * 1000,
  '/performance/campaigns': 10 * 60 * 1000,
  '/performance/traffic': 10 * 60 * 1000,
  '/marketing/actions': 10 * 60 * 1000,
  '/marketing/discount-tasks': 10 * 60 * 1000,
  '/finance': 10 * 60 * 1000,
  '/messages': 0,
  '/messages/changes': 0,
  '/messages/history': 0,
  '/notification-events': 30 * 1000
};
const ozonReadMemoryCache = new Map();
let ozonPersistentReadCachePurged = false;

function getOzonLoadedXlsx() {
  if (window.XLSX?.utils) return window.XLSX;
  if (typeof exports === 'object' && exports?.utils) {
    window.XLSX = exports;
    return exports;
  }
  if (typeof module === 'object' && module?.exports?.utils) {
    window.XLSX = module.exports;
    return module.exports;
  }
  return null;
}

async function ensureXlsxLoaded() {
  const loadedXlsx = getOzonLoadedXlsx();
  if (loadedXlsx) return loadedXlsx;
  if (!xlsxLoadPromise) {
    xlsxLoadPromise = new Promise((resolve, reject) => {
      const timeout = window.setTimeout(() => {
        reject(new Error('Excel 组件加载超时，请刷新页面后重试'));
      }, 12000);
      const finish = (value) => {
        window.clearTimeout(timeout);
        resolve(value);
      };
      const fail = (error) => {
        window.clearTimeout(timeout);
        reject(error);
      };
      const existing = document.querySelector('script[data-temu-xlsx-loader="1"]');
      if (existing) {
        const existingXlsx = getOzonLoadedXlsx();
        if (existingXlsx) {
          finish(existingXlsx);
          return;
        }
        existing.addEventListener('load', () => {
          const scriptXlsx = getOzonLoadedXlsx();
          scriptXlsx ? finish(scriptXlsx) : fail(new Error('Excel 组件未正确初始化'));
        }, { once: true });
        existing.addEventListener('error', () => fail(new Error('Excel 组件加载失败')), { once: true });
        return;
      }
      const script = document.createElement('script');
      script.src = '/ozon/xlsx.full.min.js?v=ozon-upload-test-bulk-actions-20260718-49';
      script.async = true;
      script.dataset.temuXlsxLoader = '1';
      script.onload = () => {
        const scriptXlsx = getOzonLoadedXlsx();
        scriptXlsx ? finish(scriptXlsx) : fail(new Error('Excel 组件未正确初始化'));
      };
      script.onerror = () => fail(new Error('Excel 组件加载失败'));
      document.head.appendChild(script);
    }).catch((error) => {
      xlsxLoadPromise = null;
      throw error;
    });
  }
  return xlsxLoadPromise;
}

function readLocalJson(key, fallback = null) {
  try {
    const value = localStorage.getItem(key);
    return value ? JSON.parse(value) : fallback;
  } catch {
    return fallback;
  }
}

function writeLocalJson(key, value) {
  try {
    localStorage.setItem(key, JSON.stringify(value));
    return true;
  } catch (error) {
    console.warn('[小青蛙电商助手] 本地缓存写入失败:', error?.message || error);
    return false;
  }
}

function isPlaceholderShopName(value, mallId = '') {
  const text = String(value ?? '').trim();
  if (!text) return true;
  const id = String(mallId || '').trim();
  return text === '当前店铺'
    || text === '当前店铺 '
    || text === '当前连接店铺'
    || text === '未命名店铺'
    || (id && text === `店铺 ${id}`);
}

function readShopNameCache() {
  const cache = readLocalJson(SHOP_NAME_CACHE_KEY, {});
  return cache && typeof cache === 'object' ? cache : {};
}

function writeShopNameCache(cache = {}) {
  const safeCache = cache && typeof cache === 'object' ? cache : {};
  try {
    localStorage.setItem(SHOP_NAME_CACHE_KEY, JSON.stringify(safeCache));
  } catch (error) {
    console.warn('[小青蛙电商助手] 店铺名称本地缓存失败:', error?.message || error);
  }
  safeStorageSet({ [SHOP_NAME_CACHE_KEY]: safeCache }, '店铺名称缓存').catch(() => {});
}

function getCachedShopName(mallId = '') {
  const id = normalizeMallIdValue(mallId);
  if (!id) return '';
  const name = String(readShopNameCache()[id] || '').trim();
  return isPlaceholderShopName(name, id) ? '' : name;
}

function pickRealShopName(values = [], mallId = '') {
  const id = normalizeMallIdValue(mallId);
  return (Array.isArray(values) ? values : [])
    .map(value => String(value ?? '').trim())
    .find(value => !isPlaceholderShopName(value, id)) || '';
}

function pickShopName(...values) {
  const flatValues = values.flatMap(value => Array.isArray(value) ? value : [value]);
  const fallback = flatValues.length ? String(flatValues[flatValues.length - 1] ?? '').trim() : '';
  return pickRealShopName(flatValues, fallback) || fallback;
}

function rememberShopName(mallId = '', name = '') {
  const id = normalizeMallIdValue(mallId);
  const realName = pickRealShopName([name], id);
  if (!id || !realName) return;
  const cache = readShopNameCache();
  if (cache[id] === realName) return;
  cache[id] = realName;
  writeShopNameCache(cache);
}

function rememberShopNames(shops = [], current = {}) {
  (Array.isArray(shops) ? shops : []).forEach(shop => {
    const mallId = normalizeMallIdValue(shop?.mallId);
    const name = pickRealShopName([shop?.shopName, shop?.mallName, shop?.storeName, shop?.name], mallId);
    rememberShopName(mallId, name);
  });
  rememberShopName(current.mallId, current.mallName || current.shopName);
}

function hydrateShopNameFromCache(shop = {}) {
  if (!shop || typeof shop !== 'object') return shop;
  const mallId = normalizeMallIdValue(shop.mallId);
  const realName = pickRealShopName([shop.shopName, shop.mallName, shop.storeName, shop.name], mallId);
  if (realName) {
    rememberShopName(mallId, realName);
    return shop;
  }
  const cachedName = getCachedShopName(mallId);
  return cachedName ? { ...shop, shopName: cachedName } : shop;
}

function normalizeServerAuthToken(value) {
  if (value == null) return '';
  const token = String(value).trim().replace(/^"|"$/g, '');
  return token === 'undefined' || token === 'null' ? '' : token;
}

function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function normalizeServerAuthAt(value) {
  const timestamp = Number(value || 0);
  return Number.isFinite(timestamp) ? timestamp : 0;
}

function normalizeServerAuthUser(value) {
  if (!value) return null;
  if (typeof value === 'string') {
    try {
      const parsed = JSON.parse(value);
      return parsed && typeof parsed === 'object' ? parsed : null;
    } catch {
      return null;
    }
  }
  return typeof value === 'object' ? value : null;
}

function normalizeServerAuthState(data = {}) {
  return {
    temuServerToken: normalizeServerAuthToken(data.temuServerToken),
    temuServerUser: normalizeServerAuthUser(data.temuServerUser),
    temuServerAuthAt: normalizeServerAuthAt(data.temuServerAuthAt)
  };
}

function pickNewestServerAuthState(localState, pluginState) {
  const localAuth = normalizeServerAuthState(localState);
  const pluginAuth = normalizeServerAuthState(pluginState);
  if (!pluginAuth.temuServerToken) return localAuth;
  if (!localAuth.temuServerToken) return pluginAuth;
  return pluginAuth.temuServerAuthAt > localAuth.temuServerAuthAt ? pluginAuth : localAuth;
}

function getLocalServerAuthState() {
  return normalizeServerAuthState({
    temuServerToken: localStorage.getItem('temuServerToken') || '',
    temuServerUser: readLocalJson('temuServerUser', null),
    temuServerAuthAt: Number(localStorage.getItem('temuServerAuthAt') || 0)
  });
}

async function getServerAuthState() {
  const localState = getLocalServerAuthState();
  if (isWebsiteBridgeMode()) {
    return localState;
  }

  if (!shouldDeferBridgeDependentInit()) {
    try {
      const pluginState = await chrome.storage.local.get(SERVER_AUTH_KEYS);
      return pickNewestServerAuthState(localState, pluginState);
    } catch (error) {
      console.warn('[小青蛙电商助手] 读取插件登录态失败，降级读取网站本地登录态:', error?.message || error);
    }
  }

  return localState;
}

async function setServerAuthState({ token, user, authAt = Date.now() }) {
  state.serverUser = user || null;
  const values = normalizeServerAuthState({
    temuServerToken: token || '',
    temuServerUser: user || null,
    temuServerAuthAt: authAt
  });

  localStorage.setItem('temuServerToken', values.temuServerToken);
  localStorage.setItem('temuServerUser', JSON.stringify(values.temuServerUser || {}));
  localStorage.setItem('temuServerAuthAt', String(values.temuServerAuthAt || Date.now()));

  if (!shouldDeferBridgeDependentInit()) {
    const writePluginAuth = () => chrome.storage.local.set(values);
    if (isWebsiteBridgeMode()) {
      Promise.resolve().then(writePluginAuth).catch((error) => {
        console.warn('[小青蛙电商助手] 写入插件登录态失败，已保存在网站本地:', error?.message || error);
      });
    } else {
      try {
        await writePluginAuth();
      } catch (error) {
        console.warn('[小青蛙电商助手] 写入插件登录态失败，已保存在网站本地:', error?.message || error);
      }
    }
  }
}

async function clearServerAuthState(options = {}) {
  const { localOnly = false } = options;
  SERVER_AUTH_KEYS.forEach(key => localStorage.removeItem(key));
  localStorage.removeItem(AUTO_BOUND_SHOPS_KEY);
  state.serverUser = null;
  applyRoleFeatureVisibility(null);
  if (!localOnly && !shouldDeferBridgeDependentInit()) {
    try {
      await chrome.storage.local.remove([...SERVER_AUTH_KEYS, AUTO_BOUND_SHOPS_KEY]);
    } catch (error) {
      console.warn('[小青蛙电商助手] 清理插件登录态失败，已清理网站本地登录态:', error?.message || error);
    }
  }
}

// 模板 storage key（按店铺隔离）
function getTemplateKey(mallId) {
  return mallId ? `temuTemplates_${mallId}` : 'temuTemplates';
}

async function resolveCurrentMallIdForStorage() {
  if (state.mallId) return String(state.mallId);
  const stored = await chrome.storage.local.get(['temuAuth', 'temuLocalMallId']);
  const auth = stored.temuAuth || {};
  const mallId = String(auth.mallId || stored.temuLocalMallId || '').trim();
  if (mallId) {
    state.mallId = mallId;
    state.mallName = auth.mallName || state.mallName || null;
    if (auth.antiContent && !state.antiContent) state.antiContent = auth.antiContent;
  }
  return mallId;
}

function mergeComplianceTemplates(...lists) {
  const merged = new Map();
  lists.flat().forEach(template => {
    if (!template || typeof template !== 'object') return;
    const key = String(
      template.serverId
        ? `server:${template.serverId}`
        : (template.id || `${template.name || 'template'}__${template.createdAt || ''}__${template.spuId || ''}`)
    );
    merged.set(key, template);
  });
  return Array.from(merged.values());
}

async function getComplianceTemplatesForCurrentMall() {
  const mallId = await resolveCurrentMallIdForStorage();
  const key = getTemplateKey(mallId);
  const result = await chrome.storage.local.get([key, 'temuTemplates']);
  const current = Array.isArray(result[key]) ? result[key] : [];
  if (mallId) return current;
  return mergeComplianceTemplates(current, Array.isArray(result.temuTemplates) ? result.temuTemplates : []);
}

async function getServerAuthToken() {
  const data = await getServerAuthState();
  return data.temuServerToken || '';
}

async function getServerAuthTokenForRead(options = {}) {
  const timeoutMs = Math.max(0, Number(options.timeoutMs ?? 1800) || 0);
  const intervalMs = Math.max(50, Number(options.intervalMs ?? 120) || 120);
  const startedAt = Date.now();
  let token = await getServerAuthToken();
  while (!token && Date.now() - startedAt < timeoutMs) {
    await wait(Math.min(intervalMs, Math.max(50, timeoutMs - (Date.now() - startedAt))));
    token = await getServerAuthToken();
  }
  return token || '';
}

async function getServerAuthTokenForWrite() {
  let token = await getServerAuthToken();
  if (token) return token;

  await wait(250);
  token = await getServerAuthToken();
  if (token) return token;

  try {
    const stored = await chrome.storage.local.get(SERVER_AUTH_KEYS);
    token = normalizeServerAuthToken(stored?.temuServerToken);
    if (token) return token;
  } catch (error) {
    console.warn('[小青蛙电商助手] 保存前读取插件登录态失败:', error?.message || error);
  }

  return '';
}

async function fetchUserTemplates(type = '', mallIdOverride = null) {
  const token = await getServerAuthToken();
  if (!token) return { success: true, templates: [], skipped: true };
  const mallId = String(mallIdOverride || state.mallId || '').trim();
  const params = new URLSearchParams();
  if (type) params.set('type', type);
  if (mallId) params.set('mallId', mallId);
  const query = params.toString() ? `?${params.toString()}` : '';
  const resp = await fetch(`${SERVER_URL}/data/templates${query}`, {
    headers: { 'Authorization': `Bearer ${token}` }
  });
  const result = await resp.json().catch(() => ({}));
  if (!resp.ok || !result.ok) {
    return { success: false, error: result.error || result.message || `HTTP ${resp.status}` };
  }
  return { success: true, templates: Array.isArray(result.templates) ? result.templates : [] };
}

async function saveUserTemplate(type, template) {
  const token = await getServerAuthTokenForWrite();
  const mallId = await resolveCurrentMallIdForStorage();
  if (!mallId) return { success: false, error: '未检测到当前店铺，无法按店铺保存模板' };
  if (!token) {
    return {
      success: true,
      skipped: true,
      localOnly: true,
      warning: '未登录会员账号，模板已保存到本地，暂未同步到云端'
    };
  }
  let resp;
  let result = {};
  try {
    resp = await fetch(`${SERVER_URL}/data/templates`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: template.name || '未命名模板',
        type,
        data: { ...template, mallId },
        mallId,
        isDefault: 0
      })
    });
    result = await resp.json().catch(() => ({}));
  } catch (error) {
    return {
      success: true,
      localOnly: true,
      warning: `会员模板云端同步失败，已保存到本地：${error?.message || error}`
    };
  }
  if (!resp.ok || !result.ok) {
    const errorText = result.error || result.message || `HTTP ${resp.status}`;
    return {
      success: true,
      localOnly: true,
      warning: `会员模板云端未同步，已保存到本地：${errorText}`
    };
  }
  return { success: true, id: result.id, message: result.message || '保存成功', result };
}

async function deleteUserTemplate(serverId) {
  if (!serverId) return { success: true, skipped: true };
  const token = await getServerAuthTokenForWrite();
  if (!token) return { success: false, error: '请先登录会员账号后再删除模板' };
  const mallId = await resolveCurrentMallIdForStorage();
  const params = new URLSearchParams();
  if (mallId) params.set('mallId', mallId);
  const query = params.toString() ? `?${params.toString()}` : '';
  const resp = await fetch(`${SERVER_URL}/data/templates/${encodeURIComponent(serverId)}${query}`, {
    method: 'DELETE',
    headers: { 'Authorization': `Bearer ${token}` }
  });
  const result = await resp.json().catch(() => ({}));
  if (!resp.ok || !result.ok) {
    return { success: false, error: result.error || result.message || `HTTP ${resp.status}` };
  }
  return { success: true };
}

async function fetchProductMoveTemplates(sourceMallId = '') {
  const token = await getServerAuthToken();
  if (!token) return { success: true, templates: [], skipped: true };
  const params = new URLSearchParams();
  if (sourceMallId) params.set('sourceMallId', sourceMallId);
  const query = params.toString() ? `?${params.toString()}` : '';
  const resp = await fetch(`${SERVER_URL}/data/product-templates${query}`, {
    headers: { 'Authorization': `Bearer ${token}` }
  });
  const result = await resp.json().catch(() => ({}));
  if (!resp.ok || !result.ok) {
    return { success: false, error: result.error || result.message || `HTTP ${resp.status}` };
  }
  return { success: true, templates: Array.isArray(result.templates) ? result.templates : [] };
}

async function saveProductMoveTemplate(product) {
  const token = await getServerAuthTokenForWrite();
  if (!token) return { success: false, error: '请先登录会员账号后再保存商品模板' };
  const mallId = await resolveCurrentMallIdForStorage();
  if (!mallId) return { success: false, error: '未检测到当前源店铺，无法保存商品模板' };
  const productName = product?.title || product?.productName || product?.spuName || `商品 ${product?.goodsId || product?.spuId || ''}`.trim();
  const payload = normalizeProductMoveTemplatePayload(product, mallId);
  const resp = await fetch(`${SERVER_URL}/data/product-templates`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: productName || '未命名商品模板',
      mallId,
      mallName: state.mallName || '',
      data: payload
    })
  });
  const result = await resp.json().catch(() => ({}));
  if (!resp.ok || !result.ok) {
    return { success: false, error: result.error || result.message || `HTTP ${resp.status}` };
  }
  return { success: true, id: result.id, message: result.message || '商品模板已保存' };
}

async function deleteProductMoveTemplate(serverId) {
  const token = await getServerAuthTokenForWrite();
  if (!token) return { success: false, error: '请先登录会员账号后再删除商品模板' };
  const resp = await fetch(`${SERVER_URL}/data/product-templates/${encodeURIComponent(serverId)}`, {
    method: 'DELETE',
    headers: { 'Authorization': `Bearer ${token}` }
  });
  const result = await resp.json().catch(() => ({}));
  if (!resp.ok || !result.ok) {
    return { success: false, error: result.error || result.message || `HTTP ${resp.status}` };
  }
  return { success: true };
}


// ===== 全局状态 =====
const state = {
  mallId: null,
  mallName: null,
  antiContent: null,
  boundShops: [],
  templates: [],
  products: [],
  selectedProducts: [],
  isUploading: false,
  uploadProgress: 0,
  pageNum: 1,
  pageSize: 10,
  totalPages: 0,
  total: 0,
  // 新增：选中的模板 ID
  selectedTemplateId: null,
  // 新增：商品选择模式 'list' | 'manual'
  productSelectMode: 'list',
  // 新增：实拍图分页状态
  livePhotoPageNum: 1,
  livePhotoPageSize: 10,
  livePhotoTotalPages: 0,
  livePhotoTotal: 0,
  // 新增：实拍图商品列表
  lpProducts: [],
  lpSelectedProducts: [],
  lpProductSelectMode: 'list'
};

const featureAccessState = {
  locked: true,
  reason: '请先登录并完成店铺授权'
};
const ADMIN_ONLY_TABS = new Set(['collection', 'finance']);
let adminFeatureMounted = false;
let adminFeatureEventsBound = false;

function isAdminUser(user = null) {
  return String(user?.role || '').toLowerCase() === 'admin';
}

function getCurrentServerUser() {
  return state.serverUser || readLocalJson('temuServerUser', null) || null;
}

function persistCurrentServerUser(user = null) {
  state.serverUser = user || null;
  if (!user) {
    localStorage.removeItem('temuServerUser');
    return;
  }
  localStorage.setItem('temuServerUser', JSON.stringify(user));
}

function mountAdminFeatureDom() {
  if (adminFeatureMounted) return;
  const template = document.getElementById('adminFeatureTemplate');
  const navMount = document.getElementById('adminFeatureNavMount');
  const contentArea = document.querySelector('.content-area');
  if (!template?.content || !navMount || !contentArea) return;

  const fragment = template.content.cloneNode(true);
  const sections = Array.from(fragment.querySelectorAll('.section.feature-admin-only'));
  const navButtons = [
    { tab: 'collection', text: '🛒', label: '店铺搬家' },
    { tab: 'finance', text: '💳', label: '财务数据' }
  ].map(item => {
    const button = document.createElement('button');
    button.className = 'tab nav-item feature-admin-only';
    button.dataset.tab = item.tab;
    button.dataset.adminMountedFeature = '1';
    button.type = 'button';
    button.append(document.createTextNode(item.text));
    const span = document.createElement('span');
    span.textContent = item.label;
    button.appendChild(span);
    return button;
  });

  sections.forEach(section => {
    section.dataset.adminMountedFeature = '1';
  });
  navButtons.forEach(button => navMount.appendChild(button));
  sections.forEach(section => contentArea.appendChild(section));
  adminFeatureMounted = true;
  normalizeWorkspaceTabLabels();
  normalizeButtonTypes(document);
  initTabs();
  bindAdminFeatureEvents();
}

function unmountAdminFeatureDom() {
  if (!adminFeatureMounted) return;
  document.querySelectorAll('[data-admin-mounted-feature="1"]').forEach(node => node.remove());
  adminFeatureMounted = false;
  adminFeatureEventsBound = false;
}

function applyRoleFeatureVisibility(user = getCurrentServerUser()) {
  const admin = isAdminUser(user);
  state.serverUser = user || null;
  if (admin) mountAdminFeatureDom();
  else unmountAdminFeatureDom();
  document.body.classList.toggle('admin-user', admin);
  document.querySelectorAll('.section.feature-admin-only').forEach((section) => {
    section.hidden = !admin;
  });
  document.querySelectorAll('.tab').forEach((tab) => {
    const tabId = tab.dataset.tab;
    if (!ADMIN_ONLY_TABS.has(tabId)) return;
    tab.hidden = !admin;
    tab.disabled = !admin;
    if (!admin && tab.classList.contains('active')) {
      document.querySelector('.tab[data-tab="dashboard"]')?.click();
    }
  });
}

// 任务类型名称映射
const TASK_TYPE_NAMES = {
  25: '欧盟负责人',
  60: '制造商信息',
  61: '加州65号提案',
  84: '土耳其负责人',
  166: '包装材料信息',
  167: '韩国公示信息'
};

// 商品状态映射（TEMU goods_status）


// ===== 初始化 =====
document.addEventListener('DOMContentLoaded', async () => {
  console.log('[小青蛙电商助手] 后台页面已加载');
  window.__XQW_FULL_APP_READY__ = true;
  const fastLoginBinding = window.__XQW_FAST_LOGIN_BINDING__;
  if (fastLoginBinding) {
    fastLoginBinding.btn?.removeEventListener?.('click', fastLoginBinding.onClick);
    fastLoginBinding.passwordEl?.removeEventListener?.('keydown', fastLoginBinding.onKeydown);
    window.__XQW_FAST_LOGIN_BINDING__ = null;
  }
  normalizeButtonTypes(document);
  applyRoleFeatureVisibility();
  const deferBridgeInit = shouldDeferBridgeDependentInit();
  const bootSteps = [
    ['解析服务器地址', resolveServerUrl],
    ['初始化标签页', initTabs],
    ['初始化模式切换', initModeSwitch],
    ['初始化手动输入', initManualSpuInput],
    ['绑定按钮事件', bindEvents],
    ['初始化首页', initDashboard]
  ];
  for (const [label, step] of bootSteps) {
    try {
      await step();
    } catch (error) {
      console.error(`[小青蛙电商助手] ${label}失败:`, error);
      if (typeof showToast === 'function') {
        showToast(`${label}失败：${error?.message || String(error)}`, 'error');
      }
    }
  }
  warmServerApi();
  window.setTimeout(syncWebsiteBridgeUi, 0);
  getServerAuthState().then((auth) => {
    if (auth?.temuServerToken) {
      enterDashboardOptimistically(auth.temuServerToken, auth.temuServerUser || null, '已登录，正在同步店铺授权信息...');
    }
  }).catch(err => console.warn('[小青蛙电商助手] 快速恢复登录态失败:', err));
  window.setTimeout(() => {
    restoreConnectedShopFromStorage({ updateUi: true, detectIfMissing: true }).catch(err => {
      console.warn('[小青蛙电商助手] 延迟恢复店铺状态失败:', err);
    });
  }, 650);
  window.setTimeout(() => {
    loadDashboardMembership().catch(err => console.warn('[小青蛙电商助手] 延迟刷新会员状态失败:', err));
  }, 1400);
  window.setTimeout(syncWebsiteBridgeUi, 1200);
  if (!deferBridgeInit) {
    scheduleDeferredStartupTasks();
  }
  if (deferBridgeInit) {
    console.log('[小青蛙电商助手] 当前为网站模式，桥接未就绪，已延后依赖插件的初始化步骤');
  }
});

window.addEventListener('temu-fast-login-success', () => {
  getServerAuthState().then((auth) => {
    if (auth?.temuServerToken) {
      enterDashboardOptimistically(auth.temuServerToken, auth.temuServerUser || null, '登录成功，正在同步店铺授权信息...');
    }
  }).catch(err => console.warn('[小青蛙电商助手] 快速登录后恢复状态失败:', err));
});

function normalizeButtonTypes(root = document) {
  root.querySelectorAll?.('button:not([type])').forEach(button => {
    button.setAttribute('type', 'button');
  });
}

const dynamicButtonTypeObserver = new MutationObserver((mutations) => {
  for (const mutation of mutations) {
    mutation.addedNodes.forEach(node => {
      if (node?.nodeType !== Node.ELEMENT_NODE) return;
      if (node.matches?.('button:not([type])')) {
        node.setAttribute('type', 'button');
      }
      normalizeButtonTypes(node);
    });
  }
});

dynamicButtonTypeObserver.observe(document.documentElement, {
  childList: true,
  subtree: true
});

document.addEventListener('click', (event) => {
  const button = event.target?.closest?.('button');
  if (!button) return;
  if (!button.getAttribute('type')) button.setAttribute('type', 'button');
}, true);

document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    stopOzonMessageRealtimeSync();
  } else if (isOzonMessagesTabActive()) {
    startOzonMessageRealtimeSync();
    loadOzonMessages({ silent: true }).catch(err => console.warn('[小青蛙电商助手][OZON] 消息恢复读取失败:', err?.message || err));
  }
});

// ===== 标签页 =====
function initTabs() {
  document.querySelectorAll('.tab').forEach(tab => {
    if (tab.dataset.tabBound === '1') return;
    tab.dataset.tabBound = '1';
    tab.addEventListener('click', (event) => {
      event.preventDefault();
      handleTabClick(tab);
    });
  });
}

function handleTabClick(tab) {
  const tabs = document.querySelectorAll('.tab');
  const sections = document.querySelectorAll('.section');
  const tabId = tab.dataset.tab;
  if (isOzonWorkspace() && !OZON_TAB_IDS.has(tabId)) {
    switchWorkspaceForPlatform('ozon');
    return;
  }
  if (ADMIN_ONLY_TABS.has(tabId) && !isAdminUser(getCurrentServerUser())) {
    showToast('该功能正在内部测试，暂未对普通账号开放', 'warning');
    return;
  }
  if (featureAccessState.locked && tabId !== 'dashboard') {
    showToast(featureAccessState.reason || '当前店铺未授权，请先绑定并完成卡密授权', 'warning');
    return;
  }
  tabs.forEach(t => t.classList.remove('active'));
  sections.forEach(s => s.classList.remove('active'));
  tab.classList.add('active');
  const targetSection = document.getElementById(tabId);
  if (!targetSection) {
    console.warn('[小青蛙电商助手] 标签页对应区域不存在:', tabId);
    showToast('页面区域未初始化完成，请刷新页面后重试', 'error');
    return;
  }
  targetSection.classList.add('active');
  if (OZON_TAB_IDS.has(tabId)) {
    if (tabId !== 'ozonMessages') stopOzonMessageRealtimeSync();
    loadOzonTab(tabId).catch(err => setOzonResult(err.message || '加载失败', 'error'));
    return;
  }
  stopOzonMessageRealtimeSync();
  // 切换到实拍图 tab 时自动加载商品列表（仅首次）
  if (tabId === 'livephotos' && state.lpProducts.length === 0 && state.mallId && state.antiContent) {
    refreshLivePhotoProductList();
  }
  // 切换到定制品 tab 时自动加载
  if (tabId === 'customized' && customizedProductState.products.length === 0) {
    if (state.mallId && state.antiContent) {
      loadCustomizedProducts();
    } else {
      showToast('请先打开 TEMU 卖家后台并点击重新连接，再加载定制品商品', 'warning');
    }
  }
  // 切换到活动报名 tab 时自动加载
  if (tabId === 'activity' && activityState.activities.length === 0) {
    if (state.mallId && state.antiContent) {
      loadActivityList();
    } else {
      showToast('请先打开 TEMU 卖家后台并点击重新连接，再加载活动数据', 'warning');
    }
  }
  // 切换到资质上传 tab 时自动加载
  if (tabId === 'qualification' && qualState.products.length === 0) {
    if (state.mallId && state.antiContent) {
      loadQualProductList();
    } else {
      showToast('请先打开 TEMU 卖家后台并点击重新连接，再加载资质商品', 'warning');
    }
  }
  if (tabId === 'finance') {
    renderFinanceData();
  }
  if (tabId === 'collection') {
    updateCollectionStats();
    renderCollectionResults();
  }
}

// ===== 商品选择模式切换 =====
function initModeSwitch() {
  const listBtn = document.getElementById('modeListBtn');
  const manualBtn = document.getElementById('modeManualBtn');
  const listSection = document.getElementById('productListSection') || document.getElementById('productListContainer')?.closest('.compliance-left');
  const manualSection = document.getElementById('manualSpuSection');
  const scopeWrap = document.getElementById('uploadScopeWrap');

  if (!listBtn || !manualBtn || !listSection || !manualSection) {
    console.warn('[小青蛙电商助手] 商品选择模式切换区域未初始化完成');
    return;
  }

  listBtn.addEventListener('click', () => {
    state.productSelectMode = 'list';
    listBtn.classList.add('active');
    manualBtn.classList.remove('active');
    listSection.classList.remove('hidden');
    manualSection.classList.remove('active');
    // 列表模式下显示上传范围
    if (scopeWrap) scopeWrap.style.display = 'flex';
  });

  manualBtn.addEventListener('click', () => {
    state.productSelectMode = 'manual';
    manualBtn.classList.add('active');
    listBtn.classList.remove('active');
    manualSection.classList.add('active');
    listSection.classList.add('hidden');
    // 手动输入模式下隐藏上传范围
    if (scopeWrap) scopeWrap.style.display = 'none';
    // 触发一次 SPU 计数
    updateManualSpuCount();
  });
}

// ===== 手动输入 SPU 的实时计数 =====
function initManualSpuInput() {
  const textarea = document.getElementById('manualSpuInput');
  if (!textarea) return;
  textarea.addEventListener('input', updateManualSpuCount);
}

function updateManualSpuCount() {
  const textarea = document.getElementById('manualSpuInput');
  const countEl = document.getElementById('manualSpuCount');
  if (!textarea || !countEl) return;

  const text = textarea.value.trim();
  if (!text) {
    countEl.style.display = 'none';
    return;
  }

  const spuList = text.split(/[\n,，\s]+/).map(s => s.trim()).filter(s => s && /^\d+$/.test(s));
  countEl.style.display = 'block';
  countEl.innerHTML = `共 <strong>${spuList.length}</strong> 个 SPU`;
}

// ===== 自动检测认证 =====
async function autoDetectAuth(options = {}) {
  if (isOzonWorkspace()) return { success: false, skipped: true, error: 'OZON 工作台不执行 TEMU 认证检测' };
  showToast('正在检测认证信息...', 'info');

  try {
    // 先唤醒 Service Worker（发一个 ping）
    try {
      await chrome.runtime.sendMessage({ action: 'ping' });
    } catch (e) {
      console.log('[小青蛙电商助手] ping Service Worker 失败（可能正在唤醒）:', e.message);
    }

    // 带超时的 autoDetectAuth 消息
    const result = await Promise.race([
      chrome.runtime.sendMessage({
        action: 'autoDetectAuth',
        forceRefresh: options.forceRefresh === true,
        rejectCache: options.rejectCache === true
      }),
      new Promise((_, reject) => setTimeout(() => reject(new Error('background 响应超时')), 10000))
    ]);
    console.log('[小青蛙电商助手] autoDetectAuth 结果:', result);

    if (result.success) {
      if (options.rejectCache && result.fromCache) {
        throw new Error('重新连接需要读取当前 TEMU 页面，不能使用旧缓存认证');
      }
      const normalizedAuth = applyTemuAuthToState(result);
      state.mallName = result.mallName || null;
      rememberShopName(normalizedAuth.mallId, result.mallName);

      // 保存到 storage
      await safeStorageSet({
        temuAuth: {
          mallId: normalizedAuth.mallId,
          antiContent: normalizedAuth.antiContent,
          mallName: result.mallName || null,
          updatedAt: Date.now()
        },
        temuLocalMallId: normalizedAuth.mallId
      }, '认证信息');

      // 通知 background
      await safeRuntimeSendMessage({
        action: 'setAuth',
        mallId: normalizedAuth.mallId,
        antiContent: normalizedAuth.antiContent,
        mallName: result.mallName || null
      });

      updateConnectionStatus(true, result.mallName);

      if (result.fromCache) {
        showToast('使用缓存的认证信息', 'info');
      } else {
        showToast('自动检测成功！' + (result.mallName ? ` 店铺: ${result.mallName}` : ''), 'success');
      }

      // 自动刷新商品列表
      setTimeout(() => {
        refreshProductList();
        refreshLivePhotoProductList();
      }, 1000);
    } else {
      let errorMsg = result.error || '未获取到认证信息';
      const hasUsableExistingAuth = Boolean(state.mallId && state.antiContent);
      if (result.mallId) {
        errorMsg += ` (但获取到了 mallId: ${result.mallId})`;
        state.mallId = normalizeMallIdValue(result.mallId);
        state.mallName = result.mallName || null;
      }
      // 显示诊断信息
      if (result.diagnostics) {
        const d = result.diagnostics;
        console.log('[小青蛙电商助手] 诊断信息:', d);
        errorMsg += `\n诊断: ${JSON.stringify(d)}`;
      }
      if (!result.mallId) {
        errorMsg += '\n请先在 TEMU 卖家中心页面完成登录并刷新该页面，再点“重新连接”。';
      }
      if (hasUsableExistingAuth) {
        updateConnectionStatus(true, state.mallName);
        console.warn('[小青蛙电商助手] 自动检测未刷新认证，但已保留现有店铺连接:', errorMsg);
      } else {
        showToast('提示：' + errorMsg, 'info');
        updateConnectionStatus(false);
      }
    }
    await Promise.allSettled([
      loadTemplates(),
      loadCustomizedTemplates()
    ]);
  } catch (error) {
    console.error('[小青蛙电商助手] 自动检测失败:', error);
    if (state.mallId && state.antiContent) {
      updateConnectionStatus(true, state.mallName);
      console.warn('[小青蛙电商助手] 自动检测失败，已保留现有店铺连接');
    } else {
      showToast('提示：自动检测失败，请确保已登录 TEMU 店铺', 'info');
      updateConnectionStatus(false);
    }
  }
}

// ===== 加载设置 =====
async function loadSettings() {
  if (isOzonWorkspace()) return;
  const tplKey = getTemplateKey(state.mallId);
  const result = await chrome.storage.local.get(['temuAuth', tplKey]);

  // 尝试从当前页面获取最新 mallId 检测店铺切换
  let currentMallId = null;
  try {
    const authResult = await chrome.runtime.sendMessage({ action: 'getCurrentMallId' });
    if (authResult && authResult.mallId) {
      currentMallId = normalizeMallIdValue(authResult.mallId);
    }
  } catch (e) { /* ignore */ }

  if (result.temuAuth) {
    const normalizedStoredAuth = normalizeTemuAuth(result.temuAuth);
    const storedMallId = normalizedStoredAuth.mallId;

    if (currentMallId && storedMallId !== currentMallId) {
      console.log('[小青蛙电商助手] 检测到店铺切换！', storedMallId, '→', currentMallId);
      await chrome.storage.local.remove(['temuAuth']);
      try {
        await chrome.runtime.sendMessage({ action: 'clearAuth' });
      } catch (e) {
        console.warn('[小青蛙电商助手] 清理后台认证状态失败:', e);
      }
      state.mallId = null;
      state.antiContent = null;
      state.mallName = null;
      resetSalesDataState();
      updateSalesSyncDisplay();
      renderSalesDataTable();
      updateConnectionStatus(false);
      showToast('检测到店铺切换，请重新连接', 'warning');
      return;
    }

    state.mallId = normalizedStoredAuth.mallId;
    state.antiContent = normalizedStoredAuth.antiContent;
    state.mallName = result.temuAuth.mallName || state.mallName || null;
    if (state.mallId && state.antiContent && (result.temuAuth.mallId !== state.mallId || result.temuAuth.antiContent !== state.antiContent)) {
      await safeStorageSet({
        temuAuth: { ...result.temuAuth, mallId: state.mallId, antiContent: state.antiContent, updatedAt: Date.now() },
        temuLocalMallId: state.mallId
      }, '认证信息清理');
      await safeRuntimeSendMessage({ action: 'setAuth', mallId: state.mallId, antiContent: state.antiContent, mallName: state.mallName || null });
    }
    updateConnectionStatus(true);
  } else {
    // 尝试从 background 获取默认认证信息
    try {
      const authResult = await chrome.runtime.sendMessage({ action: 'getAuth' });
      if (authResult && authResult.mallId && authResult.antiContent) {
        const normalizedAuth = normalizeTemuAuth(authResult);
        state.mallId = normalizedAuth.mallId;
        state.antiContent = normalizedAuth.antiContent;
        state.mallName = authResult.mallName || state.mallName || null;

        await safeStorageSet({
          temuAuth: { mallId: state.mallId, antiContent: state.antiContent, mallName: authResult.mallName || null, updatedAt: Date.now() },
          temuLocalMallId: state.mallId
        }, '认证信息');
        await safeRuntimeSendMessage({ action: 'setAuth', mallId: state.mallId, antiContent: state.antiContent, mallName: authResult.mallName || null });

        updateConnectionStatus(true);
        showToast('已自动设置认证信息', 'success');
      } else {
        showToast('请先登录 TEMU 店铺', 'info');
      }
    } catch (error) {
      console.error('[小青蛙电商助手] 获取默认认证信息失败:', error);
      showToast('获取认证信息失败', 'error');
    }
  }

  if (result[tplKey]) {
    state.templates = result[tplKey];
    renderTemplates();
  }
}

// ===== 连接状态 =====
function updateConnectionStatus(connected, mallName = null) {
  const statusEl = document.getElementById('connectionStatus');
  const mallInfoEl = document.getElementById('mallInfo');
  const topAuthStatusEl = document.getElementById('topAuthStatus');

  if (connected === false) {
    state.mallId = null;
    state.antiContent = null;
    state.mallName = null;
  }

  if (connected && state.mallId) {
    if (statusEl) {
      statusEl.textContent = '已连接';
      statusEl.className = 'status-badge status-connected';
    }

    const name = [mallName, state.mallName].find(value => value && value !== '当前店铺' && value !== '当前店铺 ') || `店铺 ${state.mallId}`;
    if (mallInfoEl) mallInfoEl.textContent = `${name} (${state.mallId})`;
    updateBridgeBanner({
      visible: true,
      title: '网站工作台与 TEMU 店铺已连接',
      description: `当前店铺已连接${name ? `：${name}` : ''}，后续操作会通过本地浏览器里的“小青蛙电商助手”执行。`
    });
  } else {
    const bridge = getBridgeRuntimeState();
    if (statusEl) {
      statusEl.textContent = bridge.active && bridge.ready ? '助手已连接' : '未连接助手';
      statusEl.className = `status-badge ${bridge.active && bridge.ready ? 'status-bridge-ready' : 'status-disconnected'}`;
    }
    if (mallInfoEl) mallInfoEl.textContent = getCurrentShopDisplayName();
    if (topAuthStatusEl && !getCurrentServerUser()) topAuthStatusEl.style.display = 'none';
  }
  syncDingHuiTemuHome();
}

function requireAdminFeature(featureName = '该功能') {
  if (isAdminUser(getCurrentServerUser())) return true;
  showToast(`${featureName}正在内部测试，暂未对普通账号开放`, 'warning');
  return false;
}

function bindAdminFeatureEvents() {
  if (adminFeatureEventsBound || !adminFeatureMounted) return;
  adminFeatureEventsBound = true;

  document.getElementById('collectionLoadSourceBtn')?.addEventListener('click', loadCollectionSourceProducts);
  document.getElementById('collectionSaveTemplateBtn')?.addEventListener('click', () => saveSelectedSourceProducts());
  document.getElementById('collectionSourceSearchInput')?.addEventListener('input', renderCollectionSourceProducts);
  document.getElementById('collectionTemplateRefreshBtn')?.addEventListener('click', loadCollectionProductTemplates);
  document.getElementById('collectionTemplateSelectAllBtn')?.addEventListener('click', () => {
    if (collectionState.templateSelected.size === collectionState.templates.length) {
      collectionState.templateSelected.clear();
    } else {
      collectionState.templates.forEach(template => collectionState.templateSelected.add(getProductTemplateKey(template)));
    }
    renderCollectionTemplates();
  });
  document.getElementById('collectionAddTemplateToTaskBtn')?.addEventListener('click', () => {
    const templates = collectionState.templates.filter(template => collectionState.templateSelected.has(getProductTemplateKey(template)));
    if (!templates.length) return showToast('请先选择商品模板', 'error');
    addTemplatesToMoveTasks(templates);
  });
  document.getElementById('collectionCreateDraftBtn')?.addEventListener('click', createCollectionDrafts);
  document.getElementById('collectionClearBtn')?.addEventListener('click', clearCollection);
  document.getElementById('collectionSelectAllBtn')?.addEventListener('click', () => {
    if (collectionState.selected.size === collectionState.items.length) {
      collectionState.selected.clear();
    } else {
      collectionState.items.forEach(item => collectionState.selected.add(getCollectionItemKey(item)));
    }
    renderCollectionResults();
  });
  updateCollectionStats();
  renderCollectionSourceProducts();
  loadCollectionProductTemplates().catch(err => console.warn('[小青蛙电商助手] 初始化商品模板失败:', err));
  document.getElementById('collectionRepeatInput')?.addEventListener('input', updateCollectionStats);

  document.getElementById('financeRefreshBtn')?.addEventListener('click', refreshFinanceData);
  document.getElementById('financeQueryBtn')?.addEventListener('click', queryFinanceHistory);
  document.getElementById('financeExportBtn')?.addEventListener('click', () => {
    exportFinanceHistory();
  });
}

// ===== 模板管理 =====
async function loadTemplates() {
  await resolveCurrentMallIdForStorage();
  const result = await safeRuntimeSendMessage({ action: 'getTemplates' }, { success: false, error: '后台模板加载失败' });
  state.templates = result?.success
    ? (Array.isArray(result.templates) ? result.templates : [])
    : await getComplianceTemplatesForCurrentMall();
  console.log('[小青蛙电商助手] 加载模板:', state.templates.length, '个');
  renderTemplates();
}

async function refreshComplianceTemplatesFromButton(event) {
  const btn = event?.currentTarget;
  const oldText = btn?.textContent || '刷新';
  try {
    if (btn) {
      btn.disabled = true;
      btn.textContent = '刷新中...';
    }
    await loadTemplates();
    showToast(`合规模板已刷新，共 ${state.templates.length} 个`, 'success');
  } catch (error) {
    showToast(`刷新合规模板失败：${error?.message || error || '未知错误'}`, 'error');
  } finally {
    if (btn) {
      btn.disabled = false;
      btn.textContent = oldText;
    }
  }
}

function renderTemplates() {
  // 填充创建合规任务中的模板下拉选择
  const dropdown = document.getElementById('templateSelectDropdown');
  const inlineList = document.getElementById('templateInlineList');
  if (!dropdown) return;

  const currentVal = dropdown.value;
  dropdown.innerHTML = '<option value="">-- 请选择模板 --</option>';
  if (inlineList) inlineList.innerHTML = '';
  state.templates.forEach(tpl => {
    const opt = document.createElement('option');
    opt.value = tpl.id;
    opt.textContent = tpl.name || '未命名模板';
    if (tpl.id === currentVal || tpl.id === state.selectedTemplateId) {
      opt.selected = true;
    }
    dropdown.appendChild(opt);

    if (inlineList) {
      const item = document.createElement('div');
      item.tabIndex = 0;
      item.setAttribute('role', 'button');
      item.className = 'template-inline-item';
      item.dataset.templateId = String(tpl.id);
      item.title = tpl.name;
      if (tpl.id === currentVal || tpl.id === state.selectedTemplateId) {
        item.classList.add('active');
      }

      const name = document.createElement('span');
      name.className = 'template-inline-name';
      name.textContent = tpl.name || '未命名模板';

      const remove = document.createElement('button');
      remove.type = 'button';
      remove.className = 'template-inline-delete';
      remove.textContent = 'X';
      remove.title = `删除模板「${tpl.name}」`;
      remove.addEventListener('click', (event) => {
        event.preventDefault();
        event.stopPropagation();
        deleteComplianceTemplate(tpl.id);
      });

      item.append(name, remove);
      item.addEventListener('click', () => {
        dropdown.value = String(tpl.id);
        state.selectedTemplateId = String(tpl.id);
        renderTemplates();
      });
      item.addEventListener('keydown', (event) => {
        if (event.key !== 'Enter' && event.key !== ' ') return;
        event.preventDefault();
        dropdown.value = String(tpl.id);
        state.selectedTemplateId = String(tpl.id);
        renderTemplates();
      });
      inlineList.appendChild(item);
    }
  });
  // 同步下拉选中到 state
  state.selectedTemplateId = dropdown.value || null;
}

async function deleteSelectedComplianceTemplate() {
  const dropdown = document.getElementById('templateSelectDropdown');
  const templateId = dropdown?.value || state.selectedTemplateId;
  return await deleteComplianceTemplate(templateId);
}

async function deleteComplianceTemplate(templateId) {
  if (!templateId) {
    showToast('请先选择要删除的模板', 'error');
    return;
  }

  const template = state.templates.find(t => String(t.id) === String(templateId));
  if (!template) {
    showToast('模板不存在', 'error');
    return;
  }

  if (!confirm(`确定删除模板「${template.name}」吗？`)) return;

  const deleteResult = await safeRuntimeSendMessage({ action: 'deleteTemplate', templateId: String(templateId) }, { success: false, error: '后台删除失败' });
  if (!deleteResult?.success) {
    showToast(`删除失败：${deleteResult?.error || '服务器删除失败'}`, 'error');
    return;
  }
  state.templates = state.templates.filter(t => String(t.id) !== String(templateId));
  state.selectedTemplateId = null;
  renderTemplates();
  showToast(`模板「${template.name}」已删除`, 'success');
}

// ===== 商品列表 =====
async function fetchProductList(params = {}) {
  if (isOzonWorkspace()) return { success: false, skipped: true, error: 'OZON 工作台不读取 TEMU 商品列表' };
  if (!state.mallId || !state.antiContent) {
    showToast('请先设置认证信息', 'error');
    return null;
  }
  const auth = normalizeTemuAuth({ mallId: state.mallId, antiContent: state.antiContent });
  state.mallId = auth.mallId;
  state.antiContent = auth.antiContent;
  return await safeRuntimeSendMessage({
    action: 'fetchProductList',
    params: { pageNum: params.pageNum, pageSize: params.pageSize, fetchAll: params.fetchAll, mallId: auth.mallId, antiContent: auth.antiContent }
  }, { success: false, error: '获取商品列表失败' }, 45000);
}

async function refreshProductList(params = {}) {
  const container = document.getElementById('productListContainer');
  container.innerHTML = '<div class="loading">加载中...</div>';

  const pageNum = params.pageNum || state.pageNum || 1;
  const pageSize = params.pageSize || state.pageSize || 50;
  const fetchAll = params.fetchAll || false;

  console.log('[小青蛙电商助手] refreshProductList:', { pageNum, pageSize, fetchAll });

  const result = await fetchProductList({ pageNum, pageSize, fetchAll });
  console.log('[小青蛙电商助手] fetchProductList 结果:', result);

  if (result && result.success) {
    state.products = result.products;
    state.pageNum = result.pageNum || pageNum;
    state.pageSize = result.pageSize || pageSize;
    state.total = result.total || 0;
    state.totalPages = result.totalPages || 0;
    renderProductList();
    renderPagination();
  } else {
    const errorMsg = result?.error || '获取商品列表失败';
    const errorCode = result?.errorCode || '';
    container.innerHTML = `
      <div class="empty">
        <div class="empty-icon">❌</div>
        <p>${escapeHtml(errorMsg)}</p>
        ${errorCode ? `<p style="font-size: 11px; color: #bbb;">错误代码: ${escapeHtml(errorCode)}</p>` : ''}
      </div>
    `;
  }
}

// 同步商品状态（逐个查询 TEMU 后台）


function renderProductList() {
  const container = document.getElementById('productListContainer');
  if (state.products.length === 0) {
    container.innerHTML = '<div class="empty"><div class="empty-icon">📦</div><p>暂无商品数据</p></div>';
    updateSelectedCountInfo();
    return;
  }

  const statusFilter = document.getElementById('statusFilter')?.value || '';
  const searchQuery = document.getElementById('searchInput')?.value?.trim().toLowerCase() || '';

  let filtered = state.products;
  if (statusFilter) filtered = filtered.filter(p => p.status === statusFilter);
  if (searchQuery) filtered = filtered.filter(p =>
    p.spuId?.toLowerCase().includes(searchQuery) || p.spuName?.toLowerCase().includes(searchQuery)
  );

  const html = `
    <table class="product-table">
      <thead>
        <tr>
          <th style="width: 30px;"><input type="checkbox" class="checkbox" id="selectAll"></th>
          <th style="width: 60px;">主图</th>
          <th style="width: 110px;">SPU ID</th>
          <th style="min-width: 180px;">商品名称</th>
          <th style="width: 250px;">合规状态</th>
        </tr>
      </thead>
      <tbody>
        ${filtered.map(p => `
          <tr>
          <td><input type="checkbox" class="checkbox product-checkbox" data-spu="${p.spuId}" data-skc="${p.productId || ''}"></td>
            <td>
              ${p.mainImage
                ? `<img src="${escapeHtml(p.mainImage)}" class="product-thumb" style="width:48px;height:48px;object-fit:cover;border-radius:4px;border:1px solid #eee;" crossorigin="anonymous">`
                : '<div style="width:48px;height:48px;background:#f5f5f5;border-radius:4px;display:flex;align-items:center;justify-content:center;color:#ccc;font-size:18px;">📷</div>'}
            </td>
            <td style="font-size:12px;">${p.spuId}</td>
            <td style="max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml(p.spuName || '-')}">${escapeHtml(p.spuName || '-')}</td>
            <td style="text-align:left;padding:8px;">${renderTaskList(p)}</td>
          </tr>
        `).join('')}
      </tbody>
    </table>
  `;

  container.innerHTML = html;

  // 处理图片加载失败，显示占位符
  container.querySelectorAll('img.product-thumb').forEach(img => {
    img.addEventListener('error', function() {
      this.outerHTML = '<div style="width:48px;height:48px;background:#f5f5f5;border-radius:4px;display:flex;align-items:center;justify-content:center;color:#ccc;font-size:18px;">📷</div>';
    });
  });

  document.getElementById('selectAll')?.addEventListener('change', (e) => {
    document.querySelectorAll('.product-checkbox').forEach(cb => cb.checked = e.target.checked);
    updateSelectedCountInfo();
  });

  // 绑定单个 checkbox 的 change 事件
  document.querySelectorAll('.product-checkbox').forEach(cb => {
    cb.addEventListener('change', updateSelectedCountInfo);
  });

  updateSelectedCountInfo();
}

// ===== 更新已选择商品数 =====
function updateSelectedCountInfo() {
  const countEl = document.getElementById('selectedCountInfo');
  if (!countEl) return;

  const checkedCount = document.querySelectorAll('.product-checkbox:checked').length;
  countEl.style.display = checkedCount > 0 ? 'block' : 'none';
  countEl.innerHTML = `已选择 <strong>${checkedCount}</strong> 个商品`;
}


function renderTaskList(product) {
  if (!product.waitTaskList || product.waitTaskList.length === 0) {
    return '<span style="color: #52c41a; font-size: 12px;">✅ 合规信息已完整</span>';
  }

  return product.waitTaskList.map(task => {
    const isRequired = !task.is_not_required;
    let color, statusText;
    if (task.status === 3) {
      color = '#52c41a'; statusText = '上传成功';
    } else {
      color = '#fa8c16'; statusText = '待上传';
    }
    const taskName = task.task_name || `任务${task.task_type}`;
    const req = isRequired ? '<span style="color: #ff4d4f;">*</span>' : '';
    return `<div style="margin: 2px 0; font-size: 12px; line-height: 16px;">
      <span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${color}; margin-right: 4px;"></span>
      ${req}${escapeHtml(taskName)} <span style="color: #999;">${statusText}</span>
    </div>`;
  }).join('');
}

// ===== 分页 =====
function renderPagination() {
  const info = document.getElementById('paginationInfo');
  const btns = document.getElementById('paginationButtons');
  if (!info || !btns) return;

  if (state.total > 0) {
    const start = (state.pageNum - 1) * state.pageSize + 1;
    const end = Math.min(state.pageNum * state.pageSize, state.total);
    info.innerHTML = `共 <strong>${state.total}</strong> 条，显示 <strong>${start}-${end}</strong>，第 <strong>${state.pageNum}</strong>/<strong>${state.totalPages}</strong> 页`;
  } else {
    info.innerHTML = '';
  }

  btns.innerHTML = '';
  if (state.totalPages <= 1) return;

  if (state.pageNum > 1) {
    const btn = document.createElement('button');
    btn.className = 'btn btn-default btn-sm'; btn.textContent = '上一页';
    btn.addEventListener('click', () => goToPage(state.pageNum - 1));
    btns.appendChild(btn);
  }

  const maxBtns = 5;
  let s = Math.max(1, state.pageNum - Math.floor(maxBtns / 2));
  let e = Math.min(state.totalPages, s + maxBtns - 1);
  if (e - s < maxBtns - 1) s = Math.max(1, e - maxBtns + 1);

  for (let i = s; i <= e; i++) {
    const btn = document.createElement('button');
    btn.className = i === state.pageNum ? 'btn btn-primary btn-sm' : 'btn btn-default btn-sm';
    btn.textContent = i;
    if (i !== state.pageNum) btn.addEventListener('click', () => goToPage(i));
    btns.appendChild(btn);
  }

  if (state.pageNum < state.totalPages) {
    const btn = document.createElement('button');
    btn.className = 'btn btn-default btn-sm'; btn.textContent = '下一页';
    btn.addEventListener('click', () => goToPage(state.pageNum + 1));
    btns.appendChild(btn);
  }
}

function goToPage(pageNum) {
  refreshProductList({ pageNum, pageSize: state.pageSize });
}

// ===== 批量上传 =====
async function startBatchUpload() {
  if (state.isUploading) {
    showToast('正在上传中，请稍候', 'info');
    return;
  }

  // 使用卡片选择的模板
  const templateId = state.selectedTemplateId;
  if (!templateId) { showToast('请先选择一个模板', 'error'); return; }

  const template = state.templates.find(t => t.id === templateId);
  if (!template) { showToast('模板不存在', 'error'); return; }
  if (template.compacted) {
    showToast('该模板是旧版本压缩缓存，格式不完整，请重新保存一次模板后再上传', 'error');
    return;
  }

  // 固定上传类型：合规信息 + 实拍图 + 资质 全部上传
  const checkCompliance = true;
  const checkPhoto = true;
  const checkQualification = true;

  let targetProducts = [];

  if (state.productSelectMode === 'manual') {
    // 手动输入模式：从 textarea 解析 SPU 列表
    const text = document.getElementById('manualSpuInput')?.value?.trim() || '';
    if (!text) { showToast('请输入 SPU ID', 'error'); return; }

    const spuList = text.split(/[\n,，\s]+/).map(s => s.trim()).filter(s => s && /^\d+$/.test(s));
    if (spuList.length === 0) { showToast('未找到有效的 SPU ID', 'error'); return; }

    // 将手动输入的 SPU 转换为商品对象格式
    targetProducts = spuList.map(spuId => ({
      spuId: parseInt(spuId),
      spuName: `SPU ${spuId}`,
      status: 'pending'
    }));
  } else {
    // 列表模式：使用原有逻辑
    const scope = document.getElementById('uploadScope')?.value || 'selected';

    switch (scope) {
      case 'selected': {
        const spuIds = Array.from(document.querySelectorAll('.product-checkbox:checked')).map(cb => cb.dataset.spu);
        targetProducts = state.products.filter(p => spuIds.includes(String(p.spuId)));
        break;
      }
      case 'pending':
        targetProducts = state.products.filter(p => p.status === 'pending');
        break;
      case 'all':
        targetProducts = state.products;
        break;
    }
  }

  if (targetProducts.length === 0) { showToast('没有可上传的商品', 'error'); return; }

  state.isUploading = true;
  const uploadBtn = document.getElementById('startUploadBtn');
  if (uploadBtn) {
    uploadBtn.disabled = true;
    uploadBtn.textContent = '⏳ 上传中...';
  }

  // 显示进度区
  const progressSection = document.getElementById('uploadProgressSection');
  if (progressSection) progressSection.style.display = 'block';
  resetUploadProgress(targetProducts.length);

  const result = await safeRuntimeSendMessage({
    action: 'addTask',
    task: { type: 'batchUpload', params: { products: targetProducts, template, checkCompliance, checkPhoto, checkQualification } }
  }, { success: false, error: '添加上传任务失败' }, 30000);

  if (result.success) {
    showToast(`已添加上传任务，共 ${targetProducts.length} 个商品`, 'success');
    pollUploadProgress();
  } else {
    showToast('添加任务失败: ' + result.error, 'error');
    resetUploadState();
  }
}

function resetUploadProgress(total) {
  const fill = document.getElementById('progressFill');
  const text = document.getElementById('progressText');
  const list = document.getElementById('uploadResultList');
  fill.style.width = '0%';
  fill.className = 'progress-fill blue';
  text.textContent = `共 ${total} 个商品，排队中...`;
  list.innerHTML = '';
}

async function pollUploadProgress() {
  const poll = async () => {
    const result = await chrome.runtime.sendMessage({ action: 'getTaskStatus' });
    if (result.success && result.queue.length > 0) {
      const latestTask = result.queue[result.queue.length - 1];
      updateUploadProgress(latestTask);

      if (latestTask.status === 'pending' || latestTask.status === 'processing') {
        setTimeout(poll, 2000);
      } else {
        resetUploadState();
        refreshProductList();
      }
    } else {
      // queue 为空说明 service worker 可能重启了，进度丢失
      const progressSection = document.getElementById('uploadProgressSection');
      if (progressSection && progressSection.style.display === 'block') {
        const text = document.getElementById('progressText');
        if (text && !text.textContent.includes('完成')) {
          text.textContent = '⚠️ 进度丢失（后台服务已重启），请刷新页面后重试';
        }
        resetUploadState();
      }
    }
  };
  poll();
}

function updateUploadProgress(task) {
  const fill = document.getElementById('progressFill');
  const text = document.getElementById('progressText');
  const list = document.getElementById('uploadResultList');

  const progressSection = document.getElementById('uploadProgressSection');
  if (progressSection) progressSection.style.display = 'block';

  // 从 task.progress 读取进度（processBatchUpload 写入的字段）
  const progress = task.progress || {};
  const details = progress.details || [];
  const total = progress.total || 0;
  const completed = progress.completed || 0;
  const failed = progress.failed || 0;
  const processed = completed + failed;

  // 进度百分比
  const pct = total > 0 ? Math.round((processed / total) * 100) : 0;
  fill.style.width = `${pct}%`;

  // 进度条颜色逻辑
  if (failed === 0 && completed > 0) {
    fill.className = 'progress-fill green';
  } else if (completed === 0 && failed > 0) {
    fill.className = 'progress-fill red';
  } else {
    fill.className = 'progress-fill blue';
  }

  // 文字
  if (task.status === 'completed') {
    fill.style.width = '100%';
    if (failed === 0) fill.className = 'progress-fill green';
    text.textContent = `✅ 上传完成！${completed}/${total} 成功`;
  } else if (task.status === 'failed') {
    fill.className = 'progress-fill red';
    text.textContent = `❌ 上传失败: ${task.error || '未知错误'}`;
  } else {
    const currentSpu = progress.currentSpuName || progress.currentSpuId || '';
    text.textContent = currentSpu
      ? `正在处理: ${currentSpu}（${processed}/${total}）— ${completed} 成功, ${failed} 失败`
      : `${processed}/${total} 已处理 — ${completed} 成功, ${failed} 失败`;
  }

  // 渲染每个商品的结果
  if (details.length > 0) {
    list.innerHTML = details.map(r => {
      const cls = r.status === 'success' ? 'success' : r.status === 'failed' ? 'fail' : '';
      const icon = r.status === 'success' ? '✅' : r.status === 'failed' ? '❌' : '⏳';
      const reason = r.message || '处理中...';
      return `<div class="result-item ${cls}">
        <span class="icon">${icon}</span>
        <span class="reason"><strong>${escapeHtml(r.spuId || '')}</strong> — ${escapeHtml(reason)}</span>
      </div>`;
    }).join('');
  }
}

function resetUploadState() {
  state.isUploading = false;
  const uploadBtn = document.getElementById('startUploadBtn');
  if (uploadBtn) { uploadBtn.disabled = false; uploadBtn.textContent = '🚀 开始批量上传'; }
}

// ===== 事件绑定 =====
function bindEvents() {
  // 辅助函数：安全绑定事件（元素不存在不报错）
  const $on = (id, event, handler) => {
    const el = document.getElementById(id);
    if (el) {
      if (el.tagName === 'BUTTON' && !el.getAttribute('type')) {
        el.setAttribute('type', 'button');
      }
      el.addEventListener(event, handler);
    }
    else console.warn('[小青蛙电商助手] 元素不存在，跳过绑定:', id);
  };
  const isOzonPage = typeof getSelectedAuthPlatform === 'function' && getSelectedAuthPlatform() === 'ozon';
  if (isOzonPage) {
    return;
  }
  const bindTemuOnly = (id, event, handler) => {
    if (!isOzonPage) $on(id, event, handler);
  };

  // 搜索/过滤
  $on('searchInput', 'input', renderProductList);
  $on('statusFilter', 'change', renderProductList);

  // 每页条数
  $on('pageSizeSelect', 'change', (e) => {
    state.pageSize = parseInt(e.target.value);
    state.pageNum = 1;
    refreshProductList({ pageNum: 1, pageSize: state.pageSize });
  });

  // 刷新
  $on('refreshBtn', 'click', () => refreshProductList());

  // 开始上传
  $on('startUploadBtn', 'click', startBatchUpload);

  // 重新连接
  bindTemuOnly('reconnectBtn', 'click', reconnectCurrentShop);

  // 一键访问店铺
  bindTemuOnly('visitStoreBtn', 'click', visitCurrentStore);

  // 模板下拉选择
  $on('templateSelectDropdown', 'change', (e) => {
    state.selectedTemplateId = e.target.value || null;
    renderTemplates();
  });
  $on('refreshComplianceTemplatesBtn', 'click', refreshComplianceTemplatesFromButton);

  // 实拍图文件管理
  const livePhotoFileInput = document.getElementById('livePhotoFilesInput');
  $on('livePhotoSelectFilesBtn', 'click', () => {
    livePhotoFileInput.value = '';
    livePhotoFileInput.click();
  });
  $on('livePhotoAddFilesBtn', 'click', () => {
    livePhotoFileInput.value = '';
    livePhotoFileInput.click();
  });
  if (livePhotoFileInput) livePhotoFileInput.addEventListener('change', (e) => {
    if (e.target.files.length > 0) addLivePhotoFiles(e.target.files);
  });
  $on('startLivePhotoUploadBtn', 'click', startLivePhotoUpload);

  // 实拍图 Tab 初始化
  initLivePhotoTab();

  // 核价 Tab 初始化
  initPricingTab();

  // 销售数据 Tab 属于 TEMU，OZON 的销量统一来自 OZON 订单列表缓存。
  if (!isOzonWorkspace()) initSalesDataTab();

  if (!shouldDeferBridgeDependentInit()) {
    // 定制品 Tab 初始化
    initCustomizedTab();

    // 优惠活动报名 Tab 初始化
    initActivityTab();

    // 资质批量上传 Tab 初始化
    initQualificationTab();
  }
}

// ===== 实拍图文件管理 =====
const LIVE_PHOTO_MAX_FILES = 5;
let selectedLivePhotoFiles = []; // { file: File, dataUrl: string }

function renderLivePhotoFileList() {
  const listEl = document.getElementById('livePhotoFileList');
  const countEl = document.getElementById('livePhotoFileCount');
  const emptyHint = document.getElementById('livePhotoFileEmptyHint');
  const addBtn = document.getElementById('livePhotoAddFilesBtn');

  listEl.innerHTML = '';

  if (selectedLivePhotoFiles.length === 0) {
    emptyHint.style.display = 'block';
    addBtn.style.display = 'none';
    countEl.textContent = '';
    return;
  }

  emptyHint.style.display = 'none';
  addBtn.style.display = 'inline-flex';
  countEl.textContent = `${selectedLivePhotoFiles.length}/${LIVE_PHOTO_MAX_FILES}`;

  selectedLivePhotoFiles.forEach((item, index) => {
    const li = document.createElement('li');
    li.className = 'file-list-item';
    const sizeKB = (item.file.size / 1024).toFixed(1);
    li.innerHTML = `
      <img class="file-thumb" src="${item.dataUrl}">
      <span class="file-name" title="${escapeHtml(item.file.name)}">${escapeHtml(item.file.name)}</span>
      <span class="file-size">${sizeKB} KB</span>
      <button class="file-remove" data-index="${index}" title="移除">×</button>
    `;
    listEl.appendChild(li);
  });

  // 绑定删除按钮
  listEl.querySelectorAll('.file-remove').forEach(btn => {
    btn.addEventListener('click', (e) => {
      const idx = parseInt(e.currentTarget.dataset.index);
      selectedLivePhotoFiles.splice(idx, 1);
      renderLivePhotoFileList();
    });
  });
}

function addLivePhotoFiles(fileList) {
  const remaining = LIVE_PHOTO_MAX_FILES - selectedLivePhotoFiles.length;
  if (remaining <= 0) {
    showToast(`最多 ${LIVE_PHOTO_MAX_FILES} 张图片`, 'warning');
    return;
  }

  const files = Array.from(fileList).filter(f => f.type.startsWith('image/'));
  const toAdd = files.slice(0, remaining);

  if (files.length > remaining) {
    showToast(`最多还能添加 ${remaining} 张，已截取`, 'warning');
  }
  if (toAdd.length === 0) return;

  let loaded = 0;
  toAdd.forEach((file) => {
    const reader = new FileReader();
    reader.onload = (e) => {
      selectedLivePhotoFiles.push({ file, dataUrl: e.target.result });
      loaded++;
      if (loaded === toAdd.length) {
        renderLivePhotoFileList();
        showToast(`已选择 ${selectedLivePhotoFiles.length}/${LIVE_PHOTO_MAX_FILES} 张图片`, 'success');
      }
    };
    reader.readAsDataURL(file);
  });
}
// ===== 实拍图上传 =====

function initLivePhotoTab() {
  // 模式切换
  const listBtn = document.getElementById('lpModeListBtn');
  const manualBtn = document.getElementById('lpModeManualBtn');
  const manualSection = document.getElementById('lpManualSpuSection');

  listBtn.addEventListener('click', () => {
    state.lpProductSelectMode = 'list';
    listBtn.classList.add('active');
    manualBtn.classList.remove('active');
    manualSection.classList.remove('active');
  });
  manualBtn.addEventListener('click', () => {
    state.lpProductSelectMode = 'manual';
    manualBtn.classList.add('active');
    listBtn.classList.remove('active');
    manualSection.classList.add('active');
    updateLpManualSpuCount();
  });

  // 手动 SPU 输入实时计数
  const spuTextarea = document.getElementById('livePhotoSpuList');
  if (spuTextarea) {
    spuTextarea.addEventListener('input', updateLpManualSpuCount);
  }

  // 搜索
  document.getElementById('lpSearchInput')?.addEventListener('input', () => {
    renderLivePhotoProductList();
  });

  // 状态筛选
  document.getElementById('lpStatusFilter')?.addEventListener('change', () => {
    renderLivePhotoProductList();
  });

  // 每页条数
  document.getElementById('lpPageSizeSelect')?.addEventListener('change', (e) => {
    state.livePhotoPageSize = parseInt(e.target.value);
    state.livePhotoPageNum = 1;
    refreshLivePhotoProductList();
  });

  // 刷新按钮
  document.getElementById('lpRefreshBtn')?.addEventListener('click', () => {
    state.livePhotoPageNum = 1;
    refreshLivePhotoProductList();
  });

  // 自动加载商品列表（如果已连接）
  if (state.mallId && state.antiContent) {
    refreshLivePhotoProductList();
  }
}

function updateLpManualSpuCount() {
  const textarea = document.getElementById('livePhotoSpuList');
  const countEl = document.getElementById('lpManualSpuCount');
  if (!textarea || !countEl) return;
  const text = textarea.value.trim();
  if (!text) { countEl.style.display = 'none'; return; }
  const spuList = text.split(/[\n,，\s]+/).map(s => s.trim()).filter(s => s && /^\d+$/.test(s));
  countEl.style.display = 'block';
  countEl.innerHTML = `共 <strong>${spuList.length}</strong> 个 SPU`;
}

async function refreshLivePhotoProductList(params = {}) {
  const container = document.getElementById('lpProductListContainer');
  if (!container) return;
  container.innerHTML = '<div class="loading">加载中...</div>';

  const pageNum = params.pageNum || state.livePhotoPageNum || 1;
  const pageSize = params.pageSize || state.livePhotoPageSize || 10;

  const result = await safeRuntimeSendMessage({
    action: 'getLivePhotoList',
    params: { pageNum, pageSize }
  }, { success: false, error: '获取实拍图商品列表失败' }, 30000);
  console.log('[小青蛙电商助手] refreshLivePhotoProductList:', result);

  if (result && result.success) {
    state.lpProducts = result.list || [];
    state.livePhotoPageNum = pageNum;
    state.livePhotoPageSize = pageSize;
    state.livePhotoTotal = result.total || 0;
    state.livePhotoTotalPages = Math.ceil(state.livePhotoTotal / pageSize);
    renderLivePhotoProductList();
    renderLivePhotoPagination();
  } else {
    const error = result?.error || '';
    if (error === 'NO_CAPTURED_DATA') {
      const hint = result?.hint || '请先在 TEMU 页面点击"实拍图"Tab加载一次数据';
      container.innerHTML = `<div class="empty"><div class="empty-icon">📋</div><p>${escapeHtml(hint)}</p><p style="font-size:11px;color:#999;margin-top:8px;">数据会在 TEMU 页面加载实拍图时自动缓存</p></div>`;
    } else {
      const errorMsg = error || '获取实拍图商品列表失败';
      container.innerHTML = `<div class="empty"><div class="empty-icon">❌</div><p>${escapeHtml(errorMsg)}</p></div>`;
    }
  }
}

// 渲染实拍图识别状态（使用 real_picture/list API 的 rule_check_result）
function renderLivePhotoCheckStatus(product) {
  if (!product.rule_check_result_list || product.rule_check_result_list.length === 0) {
    return '<span style="color: #52c41a; font-size: 12px;">✅ 实拍图已通过</span>';
  }

  return product.rule_check_result_list.map(rule => {
    // 用 toast 文字作为状态显示（如"待传图"、"识别成功"、"系统识别能力待建设"）
    const toast = rule.rule_result?.[0]?.toast || '';
    let color, statusText;

    if (toast.includes('成功') || toast.includes('通过')) {
      color = '#52c41a';  // 绿色
      statusText = '识别成功';
    } else if (toast.includes('待建设') || toast.includes('不支持')) {
      color = '#1890ff';  // 蓝色
      statusText = '系统识别能力待建设';
    } else if (toast.includes('失败') || toast.includes('不通过') || toast.includes('驳回')) {
      color = '#ff4d4f';  // 红色
      statusText = toast || '不通过';
    } else {
      color = '#fa8c16';  // 橙色（待传图等）
      statusText = toast || '待传图';
    }

    const ruleName = rule.rule_name || `检查项${rule.check_type}`;
    // 参照 TEMU 截图样式：左规则名，右状态（彩色圆点+文字）
    return `<div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid #f0f0f0; font-size: 12px; line-height: 18px;">
      <span style="flex: 1; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${escapeHtml(ruleName)}</span>
      <span style="flex-shrink: 0; margin-left: 12px; color: ${color};">
        <span style="display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: ${color}; margin-right: 4px; vertical-align: middle;"></span>${escapeHtml(statusText)}
      </span>
    </div>`;
  }).join('');
}

function renderLivePhotoProductList() {
  const container = document.getElementById('lpProductListContainer');
  if (!container) return;

  if (state.lpProducts.length === 0) {
    container.innerHTML = '<div class="empty"><div class="empty-icon">📦</div><p>暂无商品数据</p></div>';
    updateLpSelectedCountInfo();
    return;
  }

  const statusFilter = document.getElementById('lpStatusFilter')?.value || '';
  const searchQuery = document.getElementById('lpSearchInput')?.value?.trim()?.toLowerCase() || '';

  let filtered = state.lpProducts;
  if (statusFilter) filtered = filtered.filter(p => {
    const toast = p.rule_check_result_list?.[0]?.rule_result?.[0]?.toast || '';
    if (statusFilter === 'passed') return toast.includes('成功') || toast.includes('通过');
    if (statusFilter === 'pending') return !toast.includes('成功') && !toast.includes('通过');
    return true;
  });
  if (searchQuery) filtered = filtered.filter(p =>
    String(p.spu_id || '')?.toLowerCase().includes(searchQuery) || (p.spu_name || '')?.toLowerCase().includes(searchQuery)
  );

  const html = `
    <table class="product-table">
      <thead>
        <tr>
          <th style="width:30px;"><input type="checkbox" class="checkbox" id="lpSelectAll"></th>
          <th style="width:50px;">主图</th>
          <th style="width:100px;">SPU ID</th>
          <th style="min-width:140px;">商品名称</th>
          <th style="width:200px;">实拍图状态</th>
        </tr>
      </thead>
      <tbody>
        ${filtered.map(p => `
          <tr>
            <td><input type="checkbox" class="checkbox lp-product-checkbox" data-spu="${p.spu_id}"></td>
            <td>
              ${p.material_img_url
                ? `<img src="${escapeHtml(p.material_img_url)}" class="img-fallback" style="width:48px;height:48px;object-fit:cover;border-radius:4px;border:1px solid #eee;">`
                : '<div style="width:48px;height:48px;background:#f5f5f5;border-radius:4px;display:flex;align-items:center;justify-content:center;color:#ccc;font-size:18px;">📷</div>'}
            </td>
            <td style="font-size:12px;">${p.spu_id}</td>
            <td style="max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml(p.spu_name || '-')}">${escapeHtml(p.spu_name || '-')}</td>
            <td style="text-align:left;padding:8px;">${renderLivePhotoCheckStatus(p)}</td>
          </tr>
        `).join('')}
      </tbody>
    </table>
  `;

  container.innerHTML = html;

  // 全选
  document.getElementById('lpSelectAll')?.addEventListener('change', (e) => {
    document.querySelectorAll('.lp-product-checkbox').forEach(cb => {
      cb.checked = e.target.checked;
      const spu = parseInt(cb.dataset.spu);
      if (e.target.checked) {
        if (!state.lpSelectedProducts.includes(spu)) state.lpSelectedProducts.push(spu);
      } else {
        state.lpSelectedProducts = state.lpSelectedProducts.filter(id => id !== spu);
      }
    });
    updateLpSelectedCountInfo();
  });

  // 单选
  document.querySelectorAll('.lp-product-checkbox').forEach(cb => {
    // 恢复已选状态
    const spu = parseInt(cb.dataset.spu);
    cb.checked = state.lpSelectedProducts.includes(spu);

    cb.addEventListener('change', () => {
      if (cb.checked) {
        if (!state.lpSelectedProducts.includes(spu)) state.lpSelectedProducts.push(spu);
      } else {
        state.lpSelectedProducts = state.lpSelectedProducts.filter(id => id !== spu);
      }
      // 更新全选状态
      const allCb = document.getElementById('lpSelectAll');
      const allCbs = document.querySelectorAll('.lp-product-checkbox');
      if (allCb) allCb.checked = [...allCbs].every(c => c.checked);
      updateLpSelectedCountInfo();
    });
  });

  updateLpSelectedCountInfo();
}

function updateLpSelectedCountInfo() {
  const el = document.getElementById('lpSelectedCountInfo');
  if (!el) return;
  if (state.lpSelectedProducts.length > 0) {
    el.style.display = 'block';
    el.innerHTML = `已选择 <strong>${state.lpSelectedProducts.length}</strong> 个商品`;
  } else {
    el.style.display = 'none';
  }
}

function renderLivePhotoPagination() {
  const info = document.getElementById('lpPaginationInfo');
  const btns = document.getElementById('lpPaginationButtons');
  const container = document.getElementById('lpPaginationContainer');
  if (!info || !btns) return;

  // 有数据时显示分页容器
  if (state.livePhotoTotal > 0 && container) {
    container.style.display = 'flex';
  }

  if (state.livePhotoTotal > 0) {
    const start = (state.livePhotoPageNum - 1) * state.livePhotoPageSize + 1;
    const end = Math.min(state.livePhotoPageNum * state.livePhotoPageSize, state.livePhotoTotal);
    info.innerHTML = `共 <strong>${state.livePhotoTotal}</strong> 条，显示 <strong>${start}-${end}</strong>，第 <strong>${state.livePhotoPageNum}</strong>/<strong>${state.livePhotoTotalPages}</strong> 页`;
  } else {
    info.innerHTML = '共 0 条';
  }

  btns.innerHTML = '';
  if (state.livePhotoTotalPages <= 1) return;

  // 上一页
  if (state.livePhotoPageNum > 1) {
    const btn = document.createElement('button');
    btn.className = 'btn btn-default btn-sm'; btn.textContent = '上一页';
    btn.addEventListener('click', () => refreshLivePhotoProductList({ pageNum: state.livePhotoPageNum - 1 }));
    btns.appendChild(btn);
  }

  // 页码按钮（最多显示 5 个）
  const maxBtns = 5;
  let s = Math.max(1, state.livePhotoPageNum - Math.floor(maxBtns / 2));
  let e = Math.min(state.livePhotoTotalPages, s + maxBtns - 1);
  if (e - s < maxBtns - 1) s = Math.max(1, e - maxBtns + 1);

  for (let i = s; i <= e; i++) {
    const btn = document.createElement('button');
    btn.className = i === state.livePhotoPageNum ? 'btn btn-primary btn-sm' : 'btn btn-default btn-sm';
    btn.textContent = i;
    if (i !== state.livePhotoPageNum) {
      btn.addEventListener('click', () => refreshLivePhotoProductList({ pageNum: i }));
    }
    btns.appendChild(btn);
  }

  // 下一页
  if (state.livePhotoPageNum < state.livePhotoTotalPages) {
    const btn = document.createElement('button');
    btn.className = 'btn btn-default btn-sm'; btn.textContent = '下一页';
    btn.addEventListener('click', () => refreshLivePhotoProductList({ pageNum: state.livePhotoPageNum + 1 }));
    btns.appendChild(btn);
  }

  // 跳转到指定页
  if (state.livePhotoTotalPages > 5) {
    const jumpWrap = document.createElement('span');
    jumpWrap.style.cssText = 'display:flex;align-items:center;gap:4px;margin-left:8px;font-size:12px;';
    jumpWrap.innerHTML = '跳至<input type="number" min="1" max="' + state.livePhotoTotalPages + '" style="width:50px;padding:2px 4px;border:1px solid #d9d9d9;border-radius:4px;font-size:12px;text-align:center;">页';
    const jumpInput = jumpWrap.querySelector('input');
    jumpInput.value = state.livePhotoPageNum;
    jumpInput.addEventListener('keydown', (ev) => {
      if (ev.key === 'Enter') {
        const p = parseInt(jumpInput.value);
        if (p >= 1 && p <= state.livePhotoTotalPages) {
          refreshLivePhotoProductList({ pageNum: p });
        }
      }
    });
    btns.appendChild(jumpWrap);
  }
}

async function startLivePhotoUpload() {
  if (selectedLivePhotoFiles.length === 0) { showToast('请先选择图片', 'error'); return; }

  if ((!state.mallId || !state.antiContent) && isWebsiteBridgeMode()) {
    await restoreConnectedShopFromStorage({ updateUi: true, detectIfMissing: true }).catch(() => {});
  }
  if (isWebsiteBridgeMode() && !getBridgeRuntimeState().ready) {
    syncWebsiteBridgeUi();
    await new Promise(resolve => setTimeout(resolve, 800));
  }

  // 根据模式获取 SPU 列表
  let spuList = [];
  if (state.lpProductSelectMode === 'list') {
    // 从商品列表选择
    spuList = [...state.lpSelectedProducts];
    if (spuList.length === 0) { showToast('请先从商品列表勾选商品', 'error'); return; }
  } else {
    // 手动输入
    const spuText = document.getElementById('livePhotoSpuList')?.value?.trim() || '';
    if (!spuText) { showToast('请输入 SPU ID', 'error'); return; }
    spuList = spuText.split(/[\n,，\s]+/).map(s => s.trim()).filter(s => s && /^\d+$/.test(s)).map(s => parseInt(s));
  }

  if (spuList.length === 0) { showToast('未找到有效的 SPU ID', 'error'); return; }

  const progressSection = document.getElementById('livePhotoProgressSection');
  const fill = document.getElementById('livePhotoProgressFill');
  const text = document.getElementById('livePhotoProgressText');
  const taskList = document.getElementById('livePhotoTaskList');
  if (!progressSection || !fill || !text || !taskList) {
    showToast('实拍图上传界面未初始化完成，请刷新插件页后重试', 'error');
    return;
  }
  progressSection.style.display = 'block';
  fill.style.width = '0%'; fill.className = 'progress-fill blue';
  text.textContent = '正在读取图片...';
  taskList.innerHTML = '';

  const uploadBtn = document.getElementById('startLivePhotoUploadBtn');
  if (uploadBtn) {
    uploadBtn.disabled = true;
    uploadBtn.textContent = '⏳ 上传中...';
  }

  try {
    // 读取图片
    const images = [];
    for (let i = 0; i < selectedLivePhotoFiles.length; i++) {
      text.textContent = `读取图片 ${i + 1}/${selectedLivePhotoFiles.length}...`;
      images.push({
        filename: selectedLivePhotoFiles[i].file.name,
        imageData: selectedLivePhotoFiles[i].dataUrl
      });
      fill.style.width = `${((i + 1) / selectedLivePhotoFiles.length) * 20}%`;
    }

    text.textContent = `图片就绪，开始上传 ${spuList.length} 个 SPU...`;
    let successCount = 0, failCount = 0;

    for (let i = 0; i < spuList.length; i++) {
      const spuId = spuList[i];
      const pct = 20 + ((i + 1) / spuList.length) * 80;
      text.textContent = `正在上传 SPU ${spuId}（${i + 1}/${spuList.length}）...`;
      fill.style.width = `${pct}%`;

      const result = await sendRuntimeMessageWithRetry({
        action: 'uploadLivePhotosBatch',
        params: { spuId, images }
      }, { success: false, error: '实拍图上传请求失败' }, 45000, 1);

      const cls = result.success ? 'success' : 'fail';
      const icon = result.success ? '✅' : '❌';
      const msg = result.success ? `上传成功（${result.imageCount || 0}张图）` : (result.error || '上传失败');

      const item = document.createElement('div');
      item.className = `result-item ${cls}`;
      item.innerHTML = `<span class="icon">${icon}</span><span class="reason"><strong>SPU ${spuId}</strong> — ${escapeHtml(msg)}</span>`;
      taskList.appendChild(item);

      if (result.success) successCount++; else failCount++;
      await new Promise(r => setTimeout(r, 500));
    }

    fill.style.width = '100%';
    // 最终颜色
    if (failCount === 0) fill.className = 'progress-fill green';
    else if (successCount === 0) fill.className = 'progress-fill red';
    else fill.className = 'progress-fill blue';

    text.textContent = `上传完成！✅ ${successCount} 成功, ❌ ${failCount} 失败`;
    showToast(`上传完成！成功: ${successCount}, 失败: ${failCount}`, successCount > 0 ? 'success' : 'error');
  } catch (error) {
    text.textContent = `上传失败: ${error.message}`;
    fill.className = 'progress-fill red';
    showToast(`上传失败: ${error.message}`, 'error');
  } finally {
    if (uploadBtn) {
      uploadBtn.disabled = false;
      uploadBtn.textContent = '🚀 开始上传';
    }
  }
}

// ===== 全局函数（供 onclick） =====
window.saveAsTemplate = async (spuId) => {
  showToast(`正在保存商品 ${spuId} 为模板...`, 'info');
  try {
    const result = await safeRuntimeSendMessage({
      action: 'saveAsTemplateDirect',
      data: { spuId, templateName: `模板-${spuId}` }
    }, { success: false, error: '保存模板失败' }, 45000);
    if (result && result.success) {
      showToast('保存成功：模板已保存', 'success');
      await loadTemplates();
    } else {
      showToast(result?.error || '保存模板失败', 'error');
    }
  } catch (err) {
    showToast(`保存失败: ${err.message}`, 'error');
  }
};

function compactComplianceTemplatePayload(data = {}) {
  return data;
}

window.editTemplate = (templateId) => {
  const tpl = state.templates.find(t => t.id === templateId);
  if (!tpl) { showToast('模板不存在', 'error'); return; }

  const complianceData = tpl.data || {};
  const fieldCount = Object.keys(complianceData).length;
  let dataHtml = '';
  if (fieldCount > 0) {
    dataHtml = Object.entries(complianceData).map(([k, v]) => {
      let dv = '';
      if (typeof v === 'object' && v.value) { dv = v.value; if (v.status) dv = `[${v.status}] ${dv}`; }
      else { dv = v; }
      const trunc = dv && dv.length > 100 ? dv.substring(0, 100) + '...' : (dv || '(空)');
      return `<div style="margin-bottom:6px;"><strong>${k}:</strong> ${escapeHtml(trunc)}</div>`;
    }).join('');
  } else {
    dataHtml = '<div style="color:#999;">⚠️ 模板中没有保存任何数据</div>';
  }

  const modal = document.createElement('div');
  modal.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:10000;';
  modal.innerHTML = `
    <div style="background:white;padding:24px;border-radius:8px;max-width:600px;max-height:80vh;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,0.15);">
      <h2 style="margin:0 0 16px;">📋 模板: ${escapeHtml(tpl.name)}</h2>
      <div style="margin-bottom:16px;color:#666;font-size:13px;">创建时间: ${tpl.createdAt ? new Date(tpl.createdAt).toLocaleString() : '-'}<br>字段数: ${fieldCount}</div>
      <div style="background:#f5f5f5;padding:16px;border-radius:4px;max-height:300px;overflow-y:auto;">
        <h3 style="margin:0 0 12px;font-size:15px;">保存的数据:</h3>
        ${dataHtml}
      </div>
      <div style="text-align:right;margin-top:16px;">
        <button id="closeModalBtn" class="btn btn-primary">关闭</button>
      </div>
    </div>`;
  document.body.appendChild(modal);
  document.getElementById('closeModalBtn').addEventListener('click', () => document.body.removeChild(modal));
  modal.addEventListener('click', (e) => { if (e.target === modal) document.body.removeChild(modal); });
};

// ===== 模板存储辅助 =====
async function getTemplates() {
  return await getComplianceTemplatesForCurrentMall();
}

async function saveTemplates(templates) {
  const key = getTemplateKey(state.mallId);
  const normalized = templates.map(normalizeComplianceTemplateForStorage);
  if (await safeStorageSet({ [key]: normalized }, '合规模板')) {
    return;
  }
  const compacted = normalized.slice(-20).map(compactComplianceTemplateForStorage);
  if (await safeStorageSet({ [key]: compacted }, '合规模板降级')) {
    return;
  }
  console.warn('[小青蛙电商助手] 模板压缩后仍超出配额，继续降级保存');
  await safeStorageSet({ [key]: compacted.slice(-10) }, '合规模板最终降级');
}

function cloneComplianceValueForStorage(value) {
  if (value === undefined) return undefined;
  try {
    return JSON.parse(JSON.stringify(value));
  } catch (_) {
    return value;
  }
}

function normalizeComplianceTemplateDataForStorage(data = {}) {
  const sourceData = data?.data?.template_edit_request_list ? data.data : data;
  const editList = Array.isArray(sourceData.template_edit_request_list) ? sourceData.template_edit_request_list : [];
  return {
    ...(sourceData.spu_id !== undefined ? { spu_id: sourceData.spu_id } : {}),
    ...(sourceData.goods_id !== undefined ? { goods_id: sourceData.goods_id } : {}),
    ...(sourceData.cat_id !== undefined ? { cat_id: sourceData.cat_id } : {}),
    group_sku_by_same_info: sourceData.group_sku_by_same_info !== undefined ? sourceData.group_sku_by_same_info : true,
    template_edit_request_list: editList.map(item => {
      const normalized = {};
      [
        'task_id',
        'task_status',
        'task_type',
        'template_id',
        'task_name',
        'is_not_required',
        'status',
        'punish_time',
        'properties',
        'input_text',
        'images',
        'rep_detail_list',
        'sku_group_multi_detail_list'
      ].forEach(key => {
        if (item[key] !== undefined) normalized[key] = cloneComplianceValueForStorage(item[key]);
      });
      return normalized;
    })
  };
}

function hasCompactedComplianceValueForStorage(value) {
  if (value === null || value === undefined) return false;
  if (typeof value === 'string') return value.includes('…');
  if (Array.isArray(value)) return value.some(hasCompactedComplianceValueForStorage);
  if (typeof value === 'object') return Object.values(value).some(hasCompactedComplianceValueForStorage);
  return false;
}

function normalizeComplianceTemplateForStorage(tpl = {}) {
  const data = normalizeComplianceTemplateDataForStorage(tpl.data || {});
  const editList = Array.isArray(data.template_edit_request_list) ? data.template_edit_request_list : [];
  return {
    ...tpl,
    data,
    taskCount: tpl.taskCount || editList.length,
    taskTypes: tpl.taskTypes || editList.map(item => item.task_type).filter(Boolean),
    compacted: Boolean(tpl.compacted || hasCompactedComplianceValueForStorage(data))
  };
}

function compactComplianceValue(value, depth = 0) {
  if (value === null || value === undefined) return value;
  if (typeof value === 'string') return value.length > 180 ? `${value.slice(0, 180)}…` : value;
  if (typeof value === 'number' || typeof value === 'boolean') return value;
  if (Array.isArray(value)) {
    const maxItems = depth === 0 ? 10 : depth === 1 ? 5 : 3;
    return value.slice(0, maxItems).map(item => compactComplianceValue(item, depth + 1));
  }
  if (typeof value !== 'object') return value;

  const out = {};
  for (const [key, item] of Object.entries(value)) {
    if (/^(raw|html|base64|blob|buffer|file|content)$/i.test(key)) continue;
    if (typeof item === 'function') continue;
    const compacted = compactComplianceValue(item, depth + 1);
    if (compacted !== undefined) out[key] = compacted;
  }
  return out;
}

function compactComplianceTemplateForStorage(tpl = {}) {
  const normalizedTpl = normalizeComplianceTemplateForStorage(tpl);
  const data = normalizedTpl.data || {};
  const editList = Array.isArray(data.template_edit_request_list) ? data.template_edit_request_list : [];
  return {
    ...normalizedTpl,
    data: {
      spu_id: data.spu_id,
      goods_id: data.goods_id,
      cat_id: data.cat_id,
      group_sku_by_same_info: data.group_sku_by_same_info,
      template_edit_request_list: editList.map(item => ({
        task_type: item.task_type,
        task_id: item.task_id,
        task_status: item.task_status,
        template_id: item.template_id,
        task_name: item.task_name,
        is_not_required: item.is_not_required,
        properties: compactComplianceValue(item.properties || {}),
        input_text: compactComplianceValue(item.input_text || {}),
        images: compactComplianceValue(item.images || {}),
        rep_detail_list: compactComplianceValue(item.rep_detail_list || []),
        sku_group_multi_detail_list: compactComplianceValue(item.sku_group_multi_detail_list || [])
      }))
    },
    taskCount: normalizedTpl.taskCount || editList.length,
    taskTypes: normalizedTpl.taskTypes || editList.map(item => item.task_type).filter(Boolean),
    compacted: true
  };
}

// ===== 工具函数 =====
function escapeHtml(str) {
  if (str === null || str === undefined) return '';
  return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}

function getOzonResizableTableStorageKey(tableKey) {
  return `${OZON_RESIZABLE_TABLE_STORAGE_PREFIX}${String(tableKey || 'table')}`;
}

function loadOzonResizableColumnWidths(tableKey, defaults = []) {
  try {
    const raw = localStorage.getItem(getOzonResizableTableStorageKey(tableKey));
    const parsed = raw ? JSON.parse(raw) : null;
    if (!Array.isArray(parsed)) return defaults.slice();
    return defaults.map((fallback, index) => {
      const value = Number(parsed[index]);
      return Number.isFinite(value) && value > 0 ? Math.round(value) : fallback;
    });
  } catch {
    return defaults.slice();
  }
}

function getOzonResizableColumnMaxWidth(tableKey, index) {
  if (String(tableKey || '') === 'orderList' && Number(index) === 8) return 96;
  return 0;
}

function clampOzonResizableColumnWidth(tableKey, index, width, minWidth = 1) {
  const min = Math.max(1, Math.round(Number(minWidth) || 1));
  const max = Math.round(Number(getOzonResizableColumnMaxWidth(tableKey, index)) || 0);
  const value = Math.max(min, Math.round(Number(width) || min));
  return max > 0 ? Math.min(max, value) : value;
}

function clampOzonResizableColumnWidths(tableKey, widths = [], mins = []) {
  return widths.map((width, index) => clampOzonResizableColumnWidth(tableKey, index, width, mins[index] || 1));
}

function saveOzonResizableColumnWidths(tableKey, widths = []) {
  try {
    localStorage.setItem(getOzonResizableTableStorageKey(tableKey), JSON.stringify(clampOzonResizableColumnWidths(tableKey, widths).map(width => Math.round(Number(width) || 0))));
  } catch {
    // Column preferences are optional; table rendering must not depend on browser storage.
  }
}

function renderOzonColumnGroup(defaults = [], tableKey = '') {
  const widths = clampOzonResizableColumnWidths(tableKey, loadOzonResizableColumnWidths(tableKey, defaults), defaults);
  return `<colgroup>${widths.map(width => `<col style="width:${Math.max(1, Math.round(Number(width) || 1))}px">`).join('')}</colgroup>`;
}

function applyOzonResizableTableWidth(table, widths = []) {
  if (!table) return;
  const total = widths.reduce((sum, width) => sum + Math.max(1, Math.round(Number(width) || 1)), 0);
  if (!total) return;
  const containerWidth = Math.floor(table.parentElement?.clientWidth || 0);
  const targetWidth = Math.max(total, containerWidth);
  table.style.setProperty('width', `${targetWidth}px`, 'important');
  table.style.setProperty('min-width', `${total}px`, 'important');
  table.style.tableLayout = 'fixed';
}

function getOzonResizableColumnWidthsFromTable(table, defaults = []) {
  const cols = Array.from(table?.querySelectorAll('col') || []);
  if (!cols.length) return defaults.slice();
  const tableKey = table?.dataset?.ozonTableKey || '';
  return cols.map((col, index) => {
    const value = parseFloat(col.style.width || col.getAttribute('width') || '');
    const fallback = defaults[index] || 80;
    return clampOzonResizableColumnWidth(tableKey, index, Number.isFinite(value) && value > 0 ? value : fallback, fallback);
  });
}

function setupOzonResizableTable(table, tableKey, defaults = [], mins = []) {
  if (!table || !defaults.length) return;
  table.dataset.ozonTableKey = String(tableKey || '');
  const cols = Array.from(table.querySelectorAll('col'));
  if (cols.length !== defaults.length) return;
  const widths = clampOzonResizableColumnWidths(tableKey, loadOzonResizableColumnWidths(tableKey, defaults), mins);
  cols.forEach((col, index) => {
    const minWidth = Math.max(1, Number(mins[index] || 36));
    col.style.width = `${clampOzonResizableColumnWidth(tableKey, index, widths[index] || defaults[index] || minWidth, minWidth)}px`;
  });
  saveOzonResizableColumnWidths(tableKey, getOzonResizableColumnWidthsFromTable(table, defaults));
  applyOzonResizableTableWidth(table, getOzonResizableColumnWidthsFromTable(table, defaults));
  const headers = Array.from(table.querySelectorAll('thead th'));
  headers.forEach((th, index) => {
    if (index >= cols.length) return;
    th.querySelector('.ozon-col-resizer')?.remove();
    th.classList.add('ozon-resizable-th');
    const handle = document.createElement('span');
    handle.className = 'ozon-col-resizer';
    handle.setAttribute('role', 'separator');
    handle.setAttribute('aria-label', '拖动调整列宽');
    handle.dataset.ozonColIndex = String(index);
    th.appendChild(handle);
  });
  if (table.dataset.ozonResizableBound === tableKey) return;
  table.dataset.ozonResizableBound = tableKey;
  table.addEventListener('pointerdown', (event) => {
    const handle = event.target.closest?.('.ozon-col-resizer');
    if (!handle || !table.contains(handle)) return;
    const colIndex = Number(handle.dataset.ozonColIndex);
    const col = table.querySelectorAll('col')[colIndex];
    if (!col) return;
    event.preventDefault();
    const startX = event.clientX;
    const startWidth = parseFloat(col.style.width || '') || table.querySelectorAll('thead th')[colIndex]?.getBoundingClientRect?.().width || defaults[colIndex] || 80;
    const minWidth = Math.max(1, Number(mins[colIndex] || 36));
    document.body.classList.add('ozon-column-resizing');
    handle.classList.add('is-resizing');
    const pointerId = event.pointerId;
    try {
      handle.setPointerCapture?.(pointerId);
    } catch {
      // Pointer capture is best effort; document listeners below keep resizing usable.
    }
    const onMove = (moveEvent) => {
      const nextWidth = clampOzonResizableColumnWidth(tableKey, colIndex, startWidth + (moveEvent.clientX - startX), minWidth);
      col.style.width = `${nextWidth}px`;
      applyOzonResizableTableWidth(table, getOzonResizableColumnWidthsFromTable(table, defaults));
    };
    const onUp = () => {
      document.removeEventListener('pointermove', onMove);
      document.removeEventListener('pointerup', onUp);
      document.removeEventListener('pointercancel', onUp);
      document.body.classList.remove('ozon-column-resizing');
      handle.classList.remove('is-resizing');
      saveOzonResizableColumnWidths(tableKey, getOzonResizableColumnWidthsFromTable(table, defaults));
      try {
        handle.releasePointerCapture?.(pointerId);
      } catch {
        // Ignore release failures from browsers that did not capture the pointer.
      }
    };
    document.addEventListener('pointermove', onMove);
    document.addEventListener('pointerup', onUp);
    document.addEventListener('pointercancel', onUp);
  });
}

function showToast(text, type = 'info') {
  const el = document.createElement('div');
  el.className = `toast toast-${type}`;
  el.textContent = text;
  document.body.appendChild(el);
  setTimeout(() => el.remove(), 3000);
}

// ========== 自动核价功能 ==========

// 核价状态
const pricingState = {
  list: [],          // 核价商品列表
  allList: [],       // 当前已加载的完整待核价商品列表
  total: 0,          // 总数
  pageNum: 1,        // 当前页
  pageSize: 10,      // 每页条数
  selected: new Set(), // 选中的行索引
  isRunning: false,  // 是否正在执行自动核价
  runTimer: null,    // 定时器
  retryCounts: {},   // priceOrderId -> 核价次数
  minPriceMap: {},   // skuId -> 阈值（最低价）
  refPriceMap: {},   // skuId -> 参考申报价（分）
};

function getPricingSkuId(sku = {}) {
  return String(sku.skuId || sku.goodsSkuId || sku.productSkuId || sku.skuIdStr || sku.id || '').trim();
}

function getPricingSkcId(skc = {}) {
  return String(skc.skcId || skc.goodsSkcId || skc.productSkcId || skc.skcIdStr || skc.id || '').trim();
}

function getPricingSearchTerms() {
  return String(document.getElementById('pricingSearchInput')?.value || '')
    .split(/[\s,，;；\n\r]+/)
    .map(value => value.trim())
    .filter(Boolean);
}

function pricingItemHasSkc(item = {}, terms = []) {
  if (!terms.length) return true;
  const lowerTerms = terms.map(term => term.toLowerCase());
  return (item.skcList || []).some((skc) => {
    const skcId = getPricingSkcId(skc).toLowerCase();
    return skcId && lowerTerms.some(term => skcId.includes(term));
  });
}

function rebuildPricingRefPriceMap(list = pricingState.list) {
  pricingState.refPriceMap = {};
  (list || []).forEach(item => {
    (item.skcList || []).forEach(skc => {
      (skc.supplierPriceReviewInfoList || []).forEach(reviewInfo => {
        const supplyPrice = reviewInfo.supplyPrice || 0;
        const priceDiff = reviewInfo.priceDifference || 0;
        const refPriceValue = supplyPrice - priceDiff;
        (reviewInfo.productSkuList || []).forEach(ps => {
          const skuId = ps.skuId || ps.goodsSkuId;
          if (skuId) pricingState.refPriceMap[skuId] = refPriceValue;
        });
      });
    });
  });
}

function selectAllVisiblePricingRows() {
  const cbs = Array.from(document.querySelectorAll('.pricing-cb'));
  pricingState.selected.clear();
  cbs.forEach((cb, index) => {
    cb.checked = true;
    pricingState.selected.add(index);
  });
  const selectAll = document.getElementById('pricingSelectAll');
  if (selectAll) selectAll.checked = cbs.length > 0;
}

function applyPricingSearchFilter({ autoSelect = false } = {}) {
  const terms = getPricingSearchTerms();
  const source = Array.isArray(pricingState.allList) && pricingState.allList.length
    ? pricingState.allList
    : pricingState.list;
  pricingState.list = source.filter(item => pricingItemHasSkc(item, terms));
  pricingState.total = pricingState.list.length;
  pricingState.pageNum = 1;
  pricingState.pageSize = pricingState.list.length || 100;
  pricingState.selected.clear();
  rebuildPricingRefPriceMap(pricingState.list);
  renderPricingTable();
  renderPricingPagination();
  if (autoSelect) selectAllVisiblePricingRows();
  if (terms.length) {
    showToast(`已筛选出 ${pricingState.list.length} 条匹配 SKC，并自动勾选`, pricingState.list.length ? 'success' : 'warning');
    addPricingLog(`按 SKC 查询：${terms.join(', ')}，匹配 ${pricingState.list.length} 条`, pricingState.list.length ? 'success' : 'warning');
  }
}

// 加载核价商品列表（只获取有系统参考价的待核价商品）
async function refreshPricingList() {
  const container = document.getElementById('pricingTableBody');
  if (!container) return;
  container.innerHTML = '<tr><td colspan="11" class="loading">加载中...</td></tr>';

  try {
    // 获取"价格申报中"的商品，然后前端过滤出有系统参考价的
    const result = await chrome.runtime.sendMessage({
      action: 'getPricingList',
      params: {
        pageNum: pricingState.pageNum,
        pageSize: pricingState.pageSize,
        priceReviewStatusList: [1], // 只查价格申报中的
        keyword: document.getElementById('pricingSearchInput')?.value.trim() || ''
      }
    });

    if (!result) {
      container.innerHTML = '<tr><td colspan="11" class="empty">❌ 未收到响应，请检查插件是否已连接TEMU页面</td></tr>';
      console.error('[小青蛙电商助手] refreshPricingList: sendMessage 返回 null');
      return;
    }

    if (result && result.success) {
    // 过滤：只保留有核价需求的商品（supplierPriceReviewInfoList 不为空）
    let rawList = result.list || [];
    pricingState.list = rawList.filter(item => {
      if (!item.skcList || item.skcList.length === 0) return false;
      // 至少有一个 SKC 的 supplierPriceReviewInfoList 不为空
      return item.skcList.some(skc =>
        skc.supplierPriceReviewInfoList && skc.supplierPriceReviewInfoList.length > 0
      );
    });
    pricingState.allList = pricingState.list.slice();
    pricingState.total = result.total || rawList.length;
    // 如果过滤后为空但 total > 0，说明有数据但都不符合核价条件
    if (pricingState.list.length === 0 && pricingState.total > 0) {
      pricingState.total = 0; // 无有效核价数据
    }
    pricingState.selected.clear();

    console.log(`[小青蛙电商助手] 核价列表过滤: API返回${rawList.length}条, 过滤后${pricingState.list.length}条`);

    // 调试日志
    if (pricingState.list.length > 0) {
      const first = pricingState.list[0];
      console.log('[小青蛙电商助手] 核价列表第一条数据:', JSON.stringify(first).substring(0, 500));
      const firstSkc = first.skcList && first.skcList[0];
      if (firstSkc) {
        console.log('[小青蛙电商助手] 第一个SKC字段:', Object.keys(firstSkc).join(', '));
        const firstSku = firstSkc.skuList && firstSkc.skuList[0];
        if (firstSku) {
          console.log('[小青蛙电商助手] 第一个SKU字段:', Object.keys(firstSku).join(', '));
          console.log('[小青蛙电商助手] 第一个SKU数据:', JSON.stringify(firstSku).substring(0, 2000));
          console.log('[小青蛙电商助手] 第一个SKC的supplierPriceReviewInfoList:', JSON.stringify(firstSkc.supplierPriceReviewInfoList)?.substring(0, 1000));
        }
      }
    }

    rebuildPricingRefPriceMap(pricingState.list);

    renderPricingTable();
    renderPricingPagination();
    } else {
      // result 存在但 success 不为 true
      const errMsg = result?.error || '未知错误';
      container.innerHTML = `<tr><td colspan="11" class="empty">❌ 加载失败: ${escapeHtml(errMsg)}</td></tr>`;
      console.error('[小青蛙电商助手] refreshPricingList 失败:', errMsg);
    }
  } catch (err) {
    container.innerHTML = `<tr><td colspan="11" class="empty">❌ 加载异常: ${escapeHtml(err?.message || String(err))}</td></tr>`;
    console.error('[小青蛙电商助手] refreshPricingList 异常:', err);
  }
}

// 获取当前页所有 SKU 的最低价
async function fetchMinPricesForCurrentPage() {
  const allSkuIds = [];
  (pricingState.list || []).forEach(item => {
    (item.skcList || []).forEach(skc => {
      (skc.skuList || []).forEach(sku => {
        if (sku.skuId) allSkuIds.push(sku.skuId);
      });
    });
  });

  if (allSkuIds.length === 0) return;

  // 最多 200 个 SKU 一次查询
  const chunks = [];
  for (let i = 0; i < allSkuIds.length; i += 200) {
    chunks.push(allSkuIds.slice(i, i + 200));
  }

  for (const chunk of chunks) {
    const result = await chrome.runtime.sendMessage({
      action: 'getMinPrice',
      params: { skuIds: chunk }
    });
    if (result && result.success && result.minList) {
      // 打印第一条数据的字段名用于调试
      if (result.minList.length > 0) {
        console.log('[小青蛙电商助手] minList 第一条数据:', JSON.stringify(result.minList[0]).substring(0, 200));
      }
      result.minList.forEach(item => {
        pricingState.minPriceMap[item.skuid] = item.minprice;
      });
    }
  }
}

// 渲染核价表格（每个核价单一行，核价单 = supplierPriceReviewInfoList 中的 reviewInfo）
function renderPricingTable() {
  const tbody = document.getElementById('pricingTableBody');
  if (!tbody) { console.error('[小青蛙电商助手] 找不到 pricingTableBody'); return; }
  console.log('[小青蛙电商助手] renderPricingTable, list长度:', pricingState.list.length);

  if (pricingState.list.length === 0) {
    tbody.innerHTML = '<tr><td colspan="11" class="empty">📦 暂无待核价商品</td></tr>';
    return;
  }

  const rows = [];
  let globalIdx = 0;

  pricingState.list.forEach((item) => {
    const productName = item.productName || '-';
    const productId = item.productId || '-';
    (item.skcList || []).forEach(skc => {
      const skcId = skc.skcId || skc.goodsSkcId || '-';
      const mainImage = (skc.previewImgUrlList && skc.previewImgUrlList[0]) || '';
      (skc.supplierPriceReviewInfoList || []).forEach(reviewInfo => {
        const priceOrderId = String(reviewInfo.priceOrderId || '');  // 确保字符串
        const suggestPrice = reviewInfo.suggestSupplyPrice || 0; // 系统参考价（分）
        const times = reviewInfo.times || 0; // 已核价次数
        const productSkuList = reviewInfo.productSkuList || [];

        productSkuList.forEach((sku) => {
          const skuId = getPricingSkuId(sku) || '-';
          const specs = (sku.productPropertyList || []).map(p => p.value).join(' / ');
          const supplierPrice = sku.supplierPrice ? sku.supplierPrice.replace(/[¥￥]/g, '') : '-';
          const refPriceYuan = suggestPrice ? (suggestPrice / 100).toFixed(2) : '-';
          const skuImage = sku.skuPreviewImage || mainImage;
          const minPrice = pricingState.minPriceMap[skuId];
          const retryCount = pricingState.retryCounts[priceOrderId] || 0;

          rows.push(`
            <tr>
              <td><input type="checkbox" class="checkbox pricing-cb" data-sku="${skuId}" data-skcid="${skcId}" data-poid="${priceOrderId}" ${pricingState.selected.has(globalIdx) ? 'checked' : ''}></td>
              <td>${skuImage ? `<img src="${skuImage}" class="spu-img" style="width:44px;height:44px;object-fit:cover;border-radius:4px;border:1px solid #eee;">` : '<span style="color:#ccc;">无图</span>'}</td>
              <td style="font-size:11px;word-break:break-all;" title="${escapeHtml(skcId)}">${escapeHtml(skcId)}</td>
              <td style="font-size:11px;word-break:break-all;" title="${escapeHtml(skuId)}">${escapeHtml(skuId)}</td>
              <td style="font-size:12px;">${escapeHtml(specs || '-')}</td>
              <td style="font-size:12px;">¥${escapeHtml(supplierPrice)}</td>
              <td style="font-size:12px;">¥${refPriceYuan}</td>
              <td style="font-size:12px;">
                <input type="number" class="pricing-threshold-input" data-skuid="${skuId}" value="${minPrice !== undefined ? minPrice : ''}" placeholder="阈值" step="0.01" min="0" style="width:80px;padding:4px 6px;border:1px solid #d9d9d9;border-radius:4px;font-size:12px;">
              </td>
              <td style="font-size:12px;text-align:center;">${times}</td>
              <td style="font-size:10px;word-break:break-all;color:#888;" title="${escapeHtml(priceOrderId)}">${priceOrderId ? priceOrderId.substring(0, 10) + '...' : '-'}</td>
              <td style="font-size:12px;">
                <button class="btn btn-success btn-sm pricing-approve-btn" data-poid="${priceOrderId}" title="同意核价">✅</button>
                <button class="btn btn-danger btn-sm pricing-void-btn" data-poid="${priceOrderId}" title="拒绝核价">🗑️</button>
              </td>
            </tr>
          `);
          globalIdx++;
        });
      });
    });
  });

  console.log('[小青蛙电商助手] rows 数量:', rows.length);
  tbody.innerHTML = rows.join('');
  bindPricingTableEvents();
}

// 绑定核价表格事件
function bindPricingTableEvents() {
  // 全选
  document.getElementById('pricingSelectAll')?.addEventListener('change', (e) => {
    const cbs = document.querySelectorAll('.pricing-cb');
    cbs.forEach(cb => { cb.checked = e.target.checked; });
    pricingState.selected.clear();
    if (e.target.checked) {
      cbs.forEach((cb, i) => pricingState.selected.add(i));
    }
  });

  // 单选
  document.querySelectorAll('.pricing-cb').forEach((cb) => {
    cb.addEventListener('change', (e) => {
      const idx = Array.from(document.querySelectorAll('.pricing-cb')).indexOf(e.target);
      if (e.target.checked) pricingState.selected.add(idx);
      else pricingState.selected.delete(idx);
    });
  });

  // 阈值输入
  document.querySelectorAll('.pricing-threshold-input').forEach(input => {
    input.addEventListener('change', (e) => {
      const skuId = e.target.dataset.skuid;
      const val = parseFloat(e.target.value) || 0;
      pricingState.minPriceMap[skuId] = val;
    });
  });

  // 同意核价按钮（按 priceOrderId 匹配 reviewInfo，发送该核价单下所有 SKU）
  const handledApprove = new Set();
  document.querySelectorAll('.pricing-approve-btn').forEach(btn => {
    btn.addEventListener('click', async (e) => {
      const poid = e.target.dataset.poid;
      if (handledApprove.has(poid)) return;
      handledApprove.add(poid);

      // 从 supplierPriceReviewInfoList 中查找匹配的核价单
      let matchedReviewInfo = null;
      for (const item of pricingState.list) {
        for (const skc of (item.skcList || [])) {
          for (const ri of (skc.supplierPriceReviewInfoList || [])) {
            if (String(ri.priceOrderId) === String(poid)) {
              matchedReviewInfo = ri;
              break;
            }
          }
          if (matchedReviewInfo) break;
        }
        if (matchedReviewInfo) break;
      }
      if (!matchedReviewInfo) { handledApprove.delete(poid); return; }

      const productSkuList = matchedReviewInfo.productSkuList || [];
      const skuItems = productSkuList.map(s => {
        const skuId = getPricingSkuId(s);
        return skuId ? { ...s, skuId, productSkuId: s.productSkuId || skuId } : null;
      }).filter(Boolean);

      // 收集每个 SKU 的阈值价格；未设置时后台会使用 TEMU 返回的建议价直接接受。
      const customPrices = {};
      productSkuList.forEach(sku => {
        const skuId = getPricingSkuId(sku);
        if (skuId && pricingState.minPriceMap[skuId]) {
          customPrices[skuId] = pricingState.minPriceMap[skuId];
        }
      });

      const result = await chrome.runtime.sendMessage({
        action: 'approvePricing',
        params: { priceOrderId: poid, items: skuItems, customPrices }
      });
      showToast(result?.success ? '✅ 同意核价成功' : `❌ ${result?.error || '同意核价失败'}`, result?.success ? 'success' : 'error');
      if (result?.success) setTimeout(() => refreshPricingList(), 500);
      handledApprove.delete(poid);
    });
  });

  // 拒绝核价按钮（放弃调整报价，按 priceOrderId 去重）
  const handledVoid = new Set();
  document.querySelectorAll('.pricing-void-btn').forEach(btn => {
    btn.addEventListener('click', async (e) => {
      const poid = e.target.dataset.poid;
      if (handledVoid.has(poid)) return;
      handledVoid.add(poid);
      const result = await chrome.runtime.sendMessage({
        action: 'voidPricing',
        params: { priceOrderId: poid }
      });
      showToast(result?.success ? '🗑️ 拒绝核价成功（已放弃调整报价）' : `❌ ${result?.error || '拒绝核价失败'}`, result?.success ? 'success' : 'error');
      if (result?.success) setTimeout(() => refreshPricingList(), 500);
      handledVoid.delete(poid);
    });
  });
}

// 渲染核价分页
function renderPricingPagination() {
  const info = document.getElementById('pricingPaginationInfo');
  const btns = document.getElementById('pricingPaginationButtons');
  if (!info || !btns) return;

  const totalPages = Math.ceil(pricingState.total / pricingState.pageSize) || 1;
  info.textContent = `共 ${pricingState.total} 条，第 ${pricingState.pageNum}/${totalPages} 页`;

  let html = '';
  html += `<button class="btn btn-default btn-sm" ${pricingState.pageNum <= 1 ? 'disabled' : ''} data-page="${pricingState.pageNum - 1}">上一页</button>`;
  // 页码按钮（最多显示5个）
  const start = Math.max(1, pricingState.pageNum - 2);
  const end = Math.min(totalPages, start + 4);
  for (let i = start; i <= end; i++) {
    html += `<button class="btn ${i === pricingState.pageNum ? 'btn-primary' : 'btn-default'} btn-sm" data-page="${i}">${i}</button>`;
  }
  html += `<button class="btn btn-default btn-sm" ${pricingState.pageNum >= totalPages ? 'disabled' : ''} data-page="${pricingState.pageNum + 1}">下一页</button>`;

  btns.innerHTML = html;
  btns.querySelectorAll('button').forEach(btn => {
    btn.addEventListener('click', (e) => {
      const page = parseInt(e.target.dataset.page);
      if (page && page >= 1 && page <= totalPages) {
        pricingState.pageNum = page;
        refreshPricingList();
      }
    });
  });
}

// 开始自动核价执行
async function startPricingExecution() {
  if (pricingState.isRunning) {
    showToast('⚠️ 核价已在执行中，请先停止', 'warning');
    return;
  }

  // 检查是否已加载列表
  if (pricingState.list.length === 0) {
    showToast('⚠️ 请先点击「加载全部待核价商品」', 'warning');
    addPricingLog('列表为空，自动加载中...', 'info');
    await refreshPricingList();
    if (pricingState.list.length === 0) {
      showToast('❌ 没有待核价商品', 'error');
      addPricingLog('没有待核价商品，执行终止', 'error');
      return;
    }
  }

  // 先加载阈值
  await fetchMinPricesForCurrentPage();

  // 检查是否有阈值
  const hasAnyThreshold = Object.keys(pricingState.minPriceMap).length > 0;
  if (!hasAnyThreshold) {
    showToast('⚠️ 所有商品未设置阈值，请先设置阈值后再执行', 'warning');
    addPricingLog('未检测到阈值设置，请先设置阈值', 'warning');
    return;
  }

  pricingState.isRunning = true;
  updatePricingRunStatus();

  showToast('▶ 开始执行核价', 'success');
  addPricingLog(`开始执行核价 | 共${pricingState.list.length}条商品 | 阈值已设置${Object.keys(pricingState.minPriceMap).length}个SKU`, 'info');
  console.log('[小青蛙电商助手] 开始执行核价，列表长度:', pricingState.list.length);

  // 只执行一轮，完成后自动停止
  try {
    await runPricingCycle();
  } finally {
    // 一轮完成后自动停止
    stopPricingExecution();
    addPricingLog('✅ 一轮核价执行完毕，已自动停止', 'success');
    showToast('✅ 一轮核价执行完毕', 'success');
  }
}
// 执行一轮核价循环
// 逻辑：三层遍历 item -> skc -> reviewInfo (supplierPriceReviewInfoList)
// - priceReviewStatus != 1: 跳过（非卖家待确认）
// - 无阈值: 跳过
// - suggestSupplyPrice/100 >= 阈值 → 同意核价 supplierResult=1, 按阈值价报价
// - suggestSupplyPrice/100 < 阈值 → 拒绝核价 voidPricing（放弃调整报价）
async function runPricingCycle() {
  if (!pricingState.isRunning) return;

  addPricingLog('开始新一轮核价检查...', 'info');
  console.log('[小青蛙电商助手] runPricingCycle 开始, list长度:', pricingState.list.length);
  const selectedOrderIds = new Set(getSelectedPricingOrderIds());
  if (selectedOrderIds.size) {
    addPricingLog(`本轮只处理已勾选的 ${selectedOrderIds.size} 个核价单`, 'info');
  }

  // 如果列表为空，先加载
  if (pricingState.list.length === 0) {
    addPricingLog('列表为空，重新加载...', 'info');
    await refreshPricingList();
    console.log('[小青蛙电商助手] 刷新后列表长度:', pricingState.list.length);
    if (pricingState.list.length === 0) {
      addPricingLog('没有待核价商品，本轮跳过', 'warning');
      return;
    }
  } else {
    // 刷新列表获取最新数据
    await refreshPricingList();
  }

  console.log('[小青蛙电商助手] 开始遍历核价单...');
  let processedCount = 0, acceptCount = 0, rejectCount = 0;

  // 三层遍历：item -> skc -> reviewInfo
  for (const item of pricingState.list) {
    if (!pricingState.isRunning) break;

    for (const skc of (item.skcList || [])) {
      if (!pricingState.isRunning) break;

      for (const reviewInfo of (skc.supplierPriceReviewInfoList || [])) {
        if (!pricingState.isRunning) break;

        const priceOrderId = reviewInfo.priceOrderId;
        if (!priceOrderId) {
          addPricingLog(`商品 ${item.productId || '?'} SKC ${skc.skcId || '?'}: 跳过（无核价单ID）`, 'warning');
          continue;
        }
        if (selectedOrderIds.size && !selectedOrderIds.has(String(priceOrderId))) {
          continue;
        }

        const suggestSupplyPrice = reviewInfo.suggestSupplyPrice || 0; // 系统参考价（分）
        const productSkuList = reviewInfo.productSkuList || [];
        if (productSkuList.length === 0) continue;

        // 检查是否所有 SKU 都是待确认状态
        const allPendingConfirm = productSkuList.every(sku => sku.priceReviewStatus === 1);
        if (!allPendingConfirm) {
          addPricingLog(`核价单 ${priceOrderId}: 跳过（非卖家待确认状态）`, 'warning');
          continue;
        }

        // 检查是否所有 SKU 都设置了阈值
        let hasMissingThreshold = false;
        for (const sku of productSkuList) {
          const skuId = getPricingSkuId(sku);
          if (!pricingState.minPriceMap[skuId]) {
            hasMissingThreshold = true;
            break;
          }
        }
        if (hasMissingThreshold) {
          addPricingLog(`核价单 ${priceOrderId}: 跳过（未设置阈值）`, 'warning');
          continue;
        }

        // 获取该核价单下所有 SKU 的最低阈值（用最严格的阈值判断）
        let minThresholdFen = Infinity;
        for (const sku of productSkuList) {
          const skuId = getPricingSkuId(sku);
          const thresholdYuan = pricingState.minPriceMap[skuId] || 0;
          const thresholdFen = Math.round(thresholdYuan * 100);
          if (thresholdFen < minThresholdFen) minThresholdFen = thresholdFen;
        }

        const refPriceYuan = (suggestSupplyPrice / 100).toFixed(2);
        const thresholdYuan = (minThresholdFen / 100).toFixed(2);

        if (suggestSupplyPrice >= minThresholdFen) {
          // 同意核价：按阈值价提交
          const skuItems = productSkuList.map(s => ({ skuId: getPricingSkuId(s) })).filter(item => item.skuId);
          const customPrices = {};
          productSkuList.forEach(sku => {
            const skuId = getPricingSkuId(sku);
            customPrices[skuId] = pricingState.minPriceMap[skuId]; // 阈值价（元）
          });

          addPricingLog(`核价单 ${priceOrderId}: 参考${refPriceYuan} >= 阈值${thresholdYuan} -> 同意核价(${skuItems.length}个SKU)`, 'success');
          const result = await chrome.runtime.sendMessage({
            action: 'approvePricing',
            params: { priceOrderId, items: skuItems, customPrices }
          });
          if (result?.success) {
            addPricingLog(`核价单 ${priceOrderId}: 同意核价成功`, 'success');
            acceptCount++;
          } else {
            addPricingLog(`核价单 ${priceOrderId}: 同意核价失败: ${result?.error || '未知错误'}`, 'error');
          }
        } else {
          // 拒绝核价：放弃调整报价
          addPricingLog(`核价单 ${priceOrderId}: 参考${refPriceYuan} < 阈值${thresholdYuan} -> 拒绝核价`, 'error');
          const result = await chrome.runtime.sendMessage({
            action: 'voidPricing',
            params: { priceOrderId }
          });
          if (result?.success) {
            addPricingLog(`核价单 ${priceOrderId}: 拒绝核价成功`, 'info');
            rejectCount++;
          } else {
            addPricingLog(`核价单 ${priceOrderId}: 拒绝核价失败: ${result?.error || '未知错误'}`, 'error');
          }
        }

        // 防止请求过快
        await new Promise(r => setTimeout(r, 1000));
        processedCount++;
      }
    }
  }

  addPricingLog(`本轮完成 | 处理${processedCount}条 | 同意${acceptCount} | 拒绝${rejectCount}`, 'info');
  console.log('[小青蛙电商助手] 本轮核价完成, 处理:', processedCount, '同意:', acceptCount, '拒绝:', rejectCount);
}

// 停止自动核价
function stopPricingExecution() {
  pricingState.isRunning = false;
  if (pricingState.runTimer) {
    clearInterval(pricingState.runTimer);
    pricingState.runTimer = null;
  }
  updatePricingRunStatus();
  showToast('⏹ 自动核价已停止', 'warning');
  addPricingLog('自动核价已停止', 'warning');
}

// 更新执行状态显示
function updatePricingRunStatus() {
  const el = document.getElementById('pricingRunStatus');
  if (!el) return;
  if (pricingState.isRunning) {
    el.style.color = '#52c41a';
    el.textContent = `✅ 核价执行中 | ${new Date().toLocaleTimeString()}`;
  } else {
    el.style.color = '#ff4d4f';
    el.textContent = `核价已停止 | 最后执行时间: ${new Date().toLocaleString()}`;
  }
}

// 添加执行日志
function addPricingLog(message, type = 'info') {
  const section = document.getElementById('pricingLogSection');
  const list = document.getElementById('pricingLogList');
  if (!section || !list) return;

  section.style.display = 'block';
  const item = document.createElement('div');
  item.className = `result-item ${type === 'error' ? 'fail' : type === 'success' ? 'success' : ''}`;
  const icons = { info: 'ℹ️', success: '✅', error: '❌', warning: '⚠️' };
  item.innerHTML = `<span class="icon">${icons[type] || 'ℹ️'}</span><span class="reason">[${new Date().toLocaleTimeString()}] ${escapeHtml(message)}</span>`;
  list.prepend(item);

  // 最多保留 100 条日志
  while (list.children.length > 100) {
    list.removeChild(list.lastChild);
  }
}

// 批量设置阈值
async function batchSetThreshold() {
  const selectedIdxs = Array.from(pricingState.selected);
  if (selectedIdxs.length === 0) {
    showToast('请先勾选要设置阈值的商品', 'warning');
    return;
  }

  // 弹出输入框
  const threshold = prompt(`为 ${selectedIdxs.length} 个商品设置统一的阈值（最低供货价）：`);
  if (threshold === null || isNaN(parseFloat(threshold))) return;

  const val = parseFloat(threshold);
  if (val <= 0) { showToast('阈值必须大于0', 'error'); return; }

  // 收集需要更新阈值的 SKU
  const updateItems = [];
  selectedIdxs.forEach(idx => {
    // selectedIdx 对应的是 checkbox 的索引，需要找到对应的 SKU
    const cbs = document.querySelectorAll('.pricing-cb');
    const cb = cbs[idx];
    if (!cb) return;
    const skuId = cb.dataset.sku;
    if (skuId) {
      pricingState.minPriceMap[skuId] = val;
      updateItems.push({
        mallId: '',
        skuId: skuId,
        skcId: cb.dataset.skcid,
        minPrice: val
      });
    }
  });

  if (updateItems.length === 0) return;

  showToast('正在保存阈值...', 'info');
  const result = await chrome.runtime.sendMessage({
    action: 'batchSaveMinPrice',
    params: { items: updateItems }
  });

  if (result?.success) {
    showToast(`✅ 成功设置 ${updateItems.length} 个 SKU 的阈值`, 'success');
    renderPricingTable();
  } else {
    showToast(`❌ ${result?.error || '设置失败'}`, 'error');
  }
}

// 导出未审商品（Excel 格式，参考大白鹅）
async function exportPricingData() {
  if (pricingState.list.length === 0) {
    showToast('暂无数据可导出', 'warning');
    return;
  }

  // 构建 SKU 维度扁平数据（item 就是 SKC）
  const rows = [];
  pricingState.list.forEach(item => {
    const productName = item.productName || '-';
    const spuId = item.productId || '-';
    (item.skcList || []).forEach(skc => {
      const skcId = skc.skcId || skc.goodsSkcId || '-';
      const skcCode = skc.extCode || '';

      (skc.skuList || []).forEach(sku => {
        const skuId = getPricingSkuId(sku) || '-';
        const specs = (sku.productPropertyList || []).map(p => `${p.name || ''}: ${p.value || ''}`).filter(Boolean).join(', ');
        const skuCode = sku.extCode || '';
        const supplierPrice = sku.supplierPriceValue ? (sku.supplierPriceValue / 100).toFixed(2) : '';
        const refPrice = pricingState.refPriceMap[skuId] ? (pricingState.refPriceMap[skuId] / 100).toFixed(2) : '';
        const currentThreshold = pricingState.minPriceMap[skuId];
        const threshold = currentThreshold !== undefined ? currentThreshold : '';

        rows.push({
          '商品名称': productName,
          'SPU': String(spuId),
          'SKC': String(skcId),
          'SKU': String(skuId),
          'SKU属性': specs,
          'SKC货号': skcCode || '—',
          'SKU货号': skuCode,
          '原申报价': supplierPrice,
          '系统参考价': refPrice,
          '阈值': threshold
        });
      });
    });
  });

  const XLSX = await ensureXlsxLoaded();

  // 使用 SheetJS 生成 xlsx
  const ws = XLSX.utils.json_to_sheet(rows);

  // 设置列宽
  ws['!cols'] = [
    { wch: 40 }, // 商品名称
    { wch: 14 }, // SPU
    { wch: 14 }, // SKC
    { wch: 14 }, // SKU
    { wch: 30 }, // SKU属性
    { wch: 12 }, // SKC货号
    { wch: 12 }, // SKU货号
    { wch: 12 }, // 原申报价
    { wch: 12 }, // 系统参考价
    { wch: 12 }, // 阈值
  ];

  const wb = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws, '核价商品');

  const fileName = `核价商品_${new Date().toISOString().slice(0, 10)}.xlsx`;
  XLSX.writeFile(wb, fileName);
  showToast(`📥 导出成功，共 ${rows.length} 个 SKU`, 'success');
}

// 导入阈值表格
function importPricingThresholds() {
  if (pricingState.list.length === 0) {
    showToast('请先加载核价商品列表', 'warning');
    return;
  }

  document.getElementById('pricingImportFile').click();
}

async function handlePricingImport(e) {
  const file = e.target.files[0];
  if (!file) return;

  try {
    const XLSX = await ensureXlsxLoaded();
    const data = await file.arrayBuffer();
    const wb = XLSX.read(data, { type: 'array' });
    const ws = wb.Sheets[wb.SheetNames[0]];
    const rows = XLSX.utils.sheet_to_json(ws);

    if (rows.length === 0) {
      showToast('表格中没有数据', 'warning');
      return;
    }

    // 按 SKU 匹配阈值
    let matched = 0;
    rows.forEach(row => {
      const skuId = String(row['SKU'] || row['sku'] || '').trim();
      const threshold = parseFloat(row['阈值'] || row['threshold'] || 0);

      if (skuId && threshold > 0) {
        pricingState.minPriceMap[skuId] = threshold;
        matched++;
      }
    });

    if (matched > 0) {
      renderPricingTable();
      showToast(`✅ 成功导入 ${matched} 个 SKU 的阈值`, 'success');
    } else {
      showToast('未匹配到任何 SKU，请检查表格格式', 'warning');
    }
  } catch (err) {
    showToast(`导入失败: ${err.message}`, 'error');
    console.error('[小青蛙电商助手] 导入失败:', err);
  }

  // 清空 file input 以便再次选择同一文件
  e.target.value = '';
}

// 初始化核价 Tab 事件
function getSelectedPricingOrderIds() {
  const cbs = Array.from(document.querySelectorAll('.pricing-cb:checked'));
  return Array.from(new Set(cbs.map(cb => String(cb.dataset.poid || '').trim()).filter(Boolean)));
}

function getSelectedPricingRows() {
  return Array.from(document.querySelectorAll('.pricing-cb:checked'))
    .map(cb => ({
      priceOrderId: String(cb.dataset.poid || '').trim(),
      skuId: String(cb.dataset.sku || '').trim()
    }))
    .filter(row => row.priceOrderId && row.skuId && row.skuId !== '-');
}

function groupSelectedPricingRowsByOrder(rows = []) {
  const grouped = new Map();
  for (const row of rows) {
    if (!grouped.has(row.priceOrderId)) grouped.set(row.priceOrderId, new Set());
    grouped.get(row.priceOrderId).add(row.skuId);
  }
  return Array.from(grouped.entries()).map(([priceOrderId, skuIds]) => ({
    priceOrderId,
    skuIds: Array.from(skuIds)
  }));
}

function syncPricingThresholdInputsFromDom() {
  document.querySelectorAll('.pricing-threshold-input').forEach(input => {
    const skuId = String(input.dataset.skuid || '').trim();
    const price = Number(input.value);
    if (skuId && Number.isFinite(price) && price > 0) {
      pricingState.minPriceMap[skuId] = price;
    }
  });
}

function findPricingReviewInfoByOrderId(priceOrderId) {
  const target = String(priceOrderId || '');
  for (const item of pricingState.list || []) {
    for (const skc of item.skcList || []) {
      for (const reviewInfo of skc.supplierPriceReviewInfoList || []) {
        if (String(reviewInfo.priceOrderId || '') === target) return reviewInfo;
      }
    }
  }
  return null;
}

function buildPricingApprovalPayload(reviewInfo, selectedSkuIds = null) {
  const productSkuList = Array.isArray(reviewInfo?.productSkuList) ? reviewInfo.productSkuList : [];
  const selectedSet = selectedSkuIds ? new Set(selectedSkuIds.map(id => String(id || '').trim()).filter(Boolean)) : null;
  const skuItems = [];
  const customPrices = {};

  for (const sku of productSkuList) {
    const skuId = getPricingSkuId(sku);
    if (!skuId) continue;
    if (selectedSet && !selectedSet.has(skuId)) continue;
    skuItems.push({
      ...sku,
      skuId,
      productSkuId: sku.productSkuId || skuId
    });
    const price = Number(pricingState.minPriceMap[skuId]);
    if (Number.isFinite(price) && price > 0) {
      customPrices[skuId] = price;
    }
  }

  return { skuItems, customPrices };
}

async function handleBatchApprovePricing(event) {
  event?.preventDefault?.();
  event?.stopImmediatePropagation?.();
  syncPricingThresholdInputsFromDom();

  const selectedGroups = groupSelectedPricingRowsByOrder(getSelectedPricingRows());
  if (!selectedGroups.length) {
    showToast('请先勾选要同意核价的商品', 'warning');
    return;
  }

  let success = 0;
  let fail = 0;
  for (const { priceOrderId, skuIds } of selectedGroups) {
    const reviewInfo = findPricingReviewInfoByOrderId(priceOrderId);
    if (!reviewInfo) {
      fail++;
      addPricingLog(`核价单 ${priceOrderId}: 未找到核价明细`, 'error');
      continue;
    }

    const { skuItems, customPrices } = buildPricingApprovalPayload(reviewInfo, skuIds);
    if (!skuItems.length) {
      fail++;
      addPricingLog(`核价单 ${priceOrderId}: 缺少SKU明细`, 'error');
      continue;
    }

    const result = await safeRuntimeSendMessage({
      action: 'approvePricing',
      params: { priceOrderId, items: skuItems, customPrices }
    }, { success: false, error: getRuntimeReconnectMessage('核价批量同意') }, 30000);
    if (result?.success) {
      success++;
      addPricingLog(`核价单 ${priceOrderId}: 已按勾选同意 ${skuItems.length} 个SKU`, 'success');
    } else {
      fail++;
      addPricingLog(`核价单 ${priceOrderId}: 批量同意失败: ${result?.error || '未知错误'}`, 'error');
    }
    await new Promise(resolve => setTimeout(resolve, 500));
  }

  showToast(`批量同意完成：${success} 成功 / ${fail} 失败`, success ? 'success' : 'error');
  refreshPricingList();
}

async function handleBatchVoidPricing(event) {
  event?.preventDefault?.();
  event?.stopImmediatePropagation?.();

  const selectedGroups = groupSelectedPricingRowsByOrder(getSelectedPricingRows());
  if (!selectedGroups.length) {
    showToast('请先勾选要作废核价的商品', 'warning');
    return;
  }

  let success = 0;
  let fail = 0;
  for (const { priceOrderId, skuIds } of selectedGroups) {
    const result = await safeRuntimeSendMessage({
      action: 'voidPricing',
      params: { priceOrderId }
    }, { success: false, error: getRuntimeReconnectMessage('核价批量作废') }, 30000);
    if (result?.success) {
      success++;
      addPricingLog(`核价单 ${priceOrderId}: 批量拒绝成功（勾选 ${skuIds.length} 个SKU）`, 'success');
    } else {
      fail++;
      addPricingLog(`核价单 ${priceOrderId}: 批量拒绝失败: ${result?.error || '未知错误'}`, 'error');
    }
    await new Promise(resolve => setTimeout(resolve, 500));
  }

  showToast(`批量拒绝完成：${success} 成功 / ${fail} 失败`, success ? 'success' : 'error');
  refreshPricingList();
}

function initPricingTab() {
  document.getElementById('pricingBatchSetBtn')?.addEventListener('click', batchSetThreshold, true);
  document.getElementById('pricingBatchApproveBtn')?.addEventListener('click', handleBatchApprovePricing, true);
  document.getElementById('pricingBatchVoidBtn')?.addEventListener('click', handleBatchVoidPricing, true);
  // 加载全部（循环加载所有页，合并结果）
  document.getElementById('pricingLoadAllBtn')?.addEventListener('click', async () => {
    const btn = document.getElementById('pricingLoadAllBtn');
    const originalText = btn?.textContent;
    if (btn) btn.textContent = '加载中...';
    const container = document.getElementById('pricingTableBody');
    if (container) container.innerHTML = '<tr><td colspan="11" class="loading">正在加载所有待核价商品...</td></tr>';

    try {
      const allItems = [];
      let page = 1;
      const pageSize = 100;
      let total = 0;

      while (true) {
        const result = await chrome.runtime.sendMessage({
          action: 'getPricingList',
          params: { pageNum: page, pageSize: pageSize, priceReviewStatusList: [1] }
        });

        if (!result?.success || !result.list || result.list.length === 0) break;

        // 过滤：只保留有核价需求的商品
        const filtered = result.list.filter(item =>
          item.skcList && item.skcList.length > 0 &&
          item.skcList.some(skc => skc.supplierPriceReviewInfoList && skc.supplierPriceReviewInfoList.length > 0)
        );

        allItems.push(...filtered);

        if (total === 0) total = result.total || 0;
        addPricingLog(`已加载第${page}页，本页${filtered.length}条，累计${allItems.length}条`, 'info');

        // 判断是否还有下一页
        if (allItems.length >= total || result.list.length < pageSize) break;
        page++;
        await new Promise(r => setTimeout(r, 300));
      }

      pricingState.list = allItems;
      pricingState.allList = allItems.slice();
      pricingState.total = allItems.length;
      pricingState.pageNum = 1;
      pricingState.pageSize = allItems.length || 100;
      pricingState.selected.clear();

      rebuildPricingRefPriceMap(pricingState.list);

      renderPricingTable();
      renderPricingPagination();

      // 自动加载阈值
      if (allItems.length > 0) {
        addPricingLog(`共${allItems.length}条商品，正在获取阈值...`, 'info');
        await fetchMinPricesForCurrentPage();
        addPricingLog(`阈值加载完毕，已设置${Object.keys(pricingState.minPriceMap).length}个SKU`, 'info');
        renderPricingTable();
      }

      if (getPricingSearchTerms().length) {
        applyPricingSearchFilter({ autoSelect: true });
      }

      showToast(`✅ 加载完成，共${allItems.length}条商品`, 'success');
    } catch (err) {
      showToast(`❌ 加载失败: ${err.message}`, 'error');
      addPricingLog(`加载失败: ${err.message}`, 'error');
    }

    if (btn) btn.textContent = originalText || '加载全部';
  });

  const runPricingSearch = async () => {
    if (!getPricingSearchTerms().length) {
      applyPricingSearchFilter({ autoSelect: false });
      return;
    }
    if (!Array.isArray(pricingState.allList) || pricingState.allList.length === 0) {
      document.getElementById('pricingLoadAllBtn')?.click();
      return;
    }
    applyPricingSearchFilter({ autoSelect: true });
  };

  document.getElementById('pricingSearchBtn')?.addEventListener('click', runPricingSearch, true);
  document.getElementById('pricingSearchInput')?.addEventListener('keydown', (event) => {
    if (event.key !== 'Enter') return;
    event.preventDefault();
    runPricingSearch();
  });
  document.getElementById('pricingClearSearchBtn')?.addEventListener('click', () => {
    const input = document.getElementById('pricingSearchInput');
    if (input) input.value = '';
    applyPricingSearchFilter({ autoSelect: false });
  }, true);

  // 导出
  document.getElementById('pricingExportBtn')?.addEventListener('click', exportPricingData);

  // 导入阈值
  document.getElementById('pricingImportBtn')?.addEventListener('click', importPricingThresholds);
  document.getElementById('pricingImportFile')?.addEventListener('change', handlePricingImport);

  // 开始/停止执行
  document.getElementById('pricingStartBtn')?.addEventListener('click', startPricingExecution);
  document.getElementById('pricingStopBtn')?.addEventListener('click', stopPricingExecution);
}

// ========== 定制品信息填写功能 ==========

// 定制品商品列表状态
const customizedProductState = {
  products: [],
  allProducts: [],
  viewProducts: [],
  pageNum: 1,
  pageSize: 20,
  totalPages: 0,
  total: 0,
  sourceTotal: 0,
  cacheLoaded: false,
  syncedAt: '',
  statusCounts: {},
  cacheKey: '',
  cacheVersion: 10
};

function getCustomizedSearchQuery() {
  return document.getElementById('customizedProductSearch')?.value?.trim() || '';
}

function getCustomizedStatusFilter() {
  return document.getElementById('customizedProductStatusFilter')?.value || '';
}

function getCustomizedStatusTopStatus(value = getCustomizedStatusFilter()) {
  const raw = String(value ?? '').trim();
  if (!raw) return null;
  const num = Number(raw);
  if ([0, 100, 200, 300].includes(num)) return num;
  if (raw.includes('在售')) return 100;
  if (raw.includes('未发布')) return 0;
  if (raw.includes('下架') || raw.includes('终止')) return 200;
  if (raw.includes('删除')) return 300;
  return null;
}

function getCustomizedStatusLabel(topStatus) {
  const map = {
    0: '未发布到站点',
    100: '在售中',
    200: '已下架/已终止',
    300: '已删除'
  };
  return map[Number(topStatus)] || '全部';
}

function getCustomizedProductCacheKey(mallId = state.mallId) {
  return mallId ? `temuCustomizedProductCache_${mallId}` : 'temuCustomizedProductCache';
}

function buildCustomizedStatusBadgesFromRecord(product = {}) {
  const badges = [];
  const isTruthyFlag = value => value === true || value === 1 || value === '1' || value === 'true';
  const isOnSale = Number(product.skcTopStatus) === 100;
  const orangeBadgeStyle = { color: '#ff6800', backgroundColor: '#fff4e6', borderColor: '#ff6800' };
  const blueBadgeStyle = { color: '#0071f3', backgroundColor: '#e6f6ff', borderColor: '#0071f3' };
  const redBadgeStyle = { color: '#dc2626', backgroundColor: '#fee2e2', borderColor: '#dc2626' };
  const skuList = Array.isArray(product.skuList) ? product.skuList : [];
  const hasCustomizationTemplate = skuList.some(sku => {
    const tmpl = sku?.productSkuCustomizationTmpl || sku?.productSkuSaleExtAttr?.productSkuCustomizationTmpl;
    return Boolean(tmpl && (!Array.isArray(tmpl.templateIds) || tmpl.templateIds.length > 0));
  });
  const personalization = product.productPersonalization || {};
  const hasPersonalizationEntry = Number(personalization.personalizationSwitch) === 1 || hasCustomizationTemplate;
  const autoDeleteDays = Number(product.autoDeleteRemainingDays);

  if (Number.isFinite(autoDeleteDays) && autoDeleteDays > 0) {
    badges.push({ text: `${autoDeleteDays}天后自动废弃`, ...orangeBadgeStyle });
  } else if (Number(product.productCertAuditStatus) === 1 || isTruthyFlag(product.offSaleByNoCert) || isTruthyFlag(product.noCertPunishRecord)) {
    badges.push({ text: '资质待上传', ...orangeBadgeStyle });
  }

  if (isOnSale) {
    badges.push({ text: '在售', ...blueBadgeStyle });
  }

  if (hasPersonalizationEntry) {
    badges.push({ text: '定制商品', ...(hasCustomizationTemplate ? blueBadgeStyle : orangeBadgeStyle) });
  }

  if (Number(product.skcTopStatus) === 200 && !(Number.isFinite(autoDeleteDays) && autoDeleteDays > 0)) {
    badges.push({ text: '已下架/已终止', ...redBadgeStyle });
  }

  if (Number(product.skcTopStatus) === 300) {
    badges.push({ text: '已删除', ...redBadgeStyle });
  }

  return badges;
}

function normalizeCustomizedProductRecord(product = {}) {
  const topStatus = Number(product.skcTopStatus ?? product.topStatus ?? '');
  const statusName = getCustomizedStatusLabel(topStatus);
  const skuList = Array.isArray(product.skuList) ? product.skuList : [];
  const skuQuantityDetailList = Array.isArray(product.skuQuantityDetailList) ? product.skuQuantityDetailList : [];
  return {
    goodsId: product.goodsId || product.spuId || '',
    productId: product.productId || product.skcId || '',
    productName: product.productName || '-',
    mainImage: product.mainImage || '',
    productStatus: product.productStatus ?? '',
    skcStatus: Number(product.skcStatus ?? 0),
    skcSiteStatus: Number(product.skcSiteStatus ?? 0),
    removeStatus: Number(product.removeStatus ?? 0),
    skcTopStatus: Number.isFinite(topStatus) ? topStatus : 0,
    lifecycleStage: Number(product.lifecycleStage ?? topStatus ?? 0),
    lifecycleStageName: product.lifecycleStageName || statusName,
    productStatusName: product.productStatusName || statusName,
    statusName,
    price: product.price || '',
    skcId: product.skcId || '',
    skcExtCode: product.skcExtCode || '',
    skuCount: Number(product.skuCount || skuList.length || skuQuantityDetailList.length || 0),
    categoryId: product.categoryId || '',
    categoryName: product.categoryName || '',
    createTime: product.createTime || '',
    updateTime: product.updateTime || '',
    onlineTime: product.onlineTime || '',
    score: product.score || '',
    skuDisplayName: product.skuDisplayName || '',
    skuCode: product.skuCode || '',
    productPropertyList: Array.isArray(product.productPropertyList) ? product.productPropertyList : [],
    skuName: product.skuName || product.productName || '-',
    skuSpecText: product.skuSpecText || '',
    saleStatusText: product.saleStatusText || '',
    onSalesDuration: Number(product.onSalesDuration || 0),
    productCertAuditStatus: Number(product.productCertAuditStatus || 0),
    offSaleByNoCert: product.offSaleByNoCert ?? '',
    noCertPunishRecord: product.noCertPunishRecord ?? '',
    productPersonalization: product.productPersonalization || {},
    autoDeleteRemainingDays: product.autoDeleteRemainingDays ?? null,
    skuList,
    skuQuantityDetailList,
    warehouseInfoList: Array.isArray(product.warehouseInfoList) ? product.warehouseInfoList : [],
    statusBadges: Array.isArray(product.statusBadges) ? product.statusBadges : []
  };
}

function mergeCustomizedExactBadges(products = [], exactProducts = []) {
  const exactMap = new Map();
  exactProducts.forEach(product => {
    const normalized = normalizeCustomizedProductRecord(product);
    const key = String(normalized.goodsId || '').trim();
    if (key && Array.isArray(normalized.statusBadges)) {
      exactMap.set(key, normalized);
    }
  });

  return products.map(product => {
    const key = String(product.goodsId || '').trim();
    const exact = exactMap.get(key);
    if (!exact) return product;
    return {
      ...product,
      mainImage: exact.mainImage || product.mainImage,
      productName: exact.productName || product.productName,
      statusBadges: exact.statusBadges || [],
      badgeSource: 'temu-goods-list-dom'
    };
  });
}

function findCustomizedTemplateDataLocally(spuId) {
  const product = (customizedProductState.allProducts || []).find(item => (
    String(item.goodsId || '') === String(spuId)
    || String(item.productId || '') === String(spuId)
    || String(item.skcId || '') === String(spuId)
  ));
  if (!product) return null;
  const skuList = Array.isArray(product.skuList) ? product.skuList : [];
  const allSkuIds = skuList.map(sku => sku.skuId || sku.productSkuId || '').filter(Boolean);
  const foundSkus = skuList
    .map(sku => {
      const tmpl = sku.productSkuCustomizationTmpl || sku.productSkuSaleExtAttr?.productSkuCustomizationTmpl || null;
      const templateIds = Array.isArray(tmpl?.templateIds)
        ? tmpl.templateIds
        : [tmpl?.templateId, tmpl?.template_id, tmpl?.id, sku.templateId].filter(Boolean);
      return {
        productSkuId: sku.skuId || sku.productSkuId || '',
        templateId: templateIds[0] || '',
        templateIds,
        specInfo: sku.skuSpec || sku.specInfo || ''
      };
    })
    .filter(sku => sku.productSkuId && (sku.templateId || sku.templateIds.length));
  if (!allSkuIds.length && !foundSkus.length) return null;
  return {
    productId: product.productId || product.skcId || product.goodsId || spuId,
    goodsId: product.goodsId || spuId,
    allSkuIds,
    foundSkus
  };
}

function computeCustomizedStatusCounts(products = []) {
  const counts = { all: products.length, 0: 0, 100: 0, 200: 0, 300: 0 };
  products.forEach(product => {
    const topStatus = Number(product.skcTopStatus ?? product.topStatus ?? 0);
    if (topStatus === 0 || topStatus === 100 || topStatus === 200 || topStatus === 300) {
      counts[topStatus] += 1;
    }
  });
  return counts;
}

function renderCustomizedStatusTabCounts() {
  const counts = customizedProductState.statusCounts || {};
  const tabs = document.querySelectorAll('#customizedProductStatusTabs .status-tab');
  tabs.forEach(tab => {
    const label = tab.dataset.label || tab.textContent.replace(/\s*\d+$/, '').trim();
    const stage = tab.dataset.stage || '';
    const count = stage === ''
      ? Number(counts.all || customizedProductState.sourceTotal || customizedProductState.allProducts.length || 0)
      : Number(counts[stage] || 0);
    tab.textContent = `${label}${count ? ` ${count}` : ''}`;
  });
}

function compactCustomizedCacheText(value, maxLength = 160) {
  const text = String(value ?? '').trim();
  return text.length > maxLength ? text.slice(0, maxLength) : text;
}

function compactCustomizedProductForCache(product = {}) {
  const skuList = Array.isArray(product.skuList) ? product.skuList : [];
  return {
    goodsId: compactCustomizedCacheText(product.goodsId, 40),
    productId: compactCustomizedCacheText(product.productId, 40),
    productName: compactCustomizedCacheText(product.productName, 180),
    mainImage: compactCustomizedCacheText(product.mainImage, 260),
    skcTopStatus: Number(product.skcTopStatus || 0),
    lifecycleStageName: compactCustomizedCacheText(product.lifecycleStageName || product.statusName, 40),
    productStatusName: compactCustomizedCacheText(product.productStatusName || product.statusName, 40),
    statusName: compactCustomizedCacheText(product.statusName, 40),
    skuCount: Number(product.skuCount || skuList.length || 0),
    createTime: compactCustomizedCacheText(product.createTime, 40),
    statusBadges: (Array.isArray(product.statusBadges) ? product.statusBadges : []).slice(0, 6).map(badge => ({
      text: compactCustomizedCacheText(badge?.text, 30),
      color: compactCustomizedCacheText(badge?.color, 40),
      backgroundColor: compactCustomizedCacheText(badge?.backgroundColor, 40),
      borderColor: compactCustomizedCacheText(badge?.borderColor, 40)
    })),
    skuList: skuList.slice(0, 20).map(sku => ({
      skuId: compactCustomizedCacheText(sku?.skuId, 40),
      skuSpec: compactCustomizedCacheText(sku?.skuSpec, 120),
      price: compactCustomizedCacheText(sku?.price, 40),
      stockText: compactCustomizedCacheText(sku?.stockText, 40),
      showStockText: compactCustomizedCacheText(sku?.showStockText, 40),
      createTime: compactCustomizedCacheText(sku?.createTime || product.createTime, 40)
    }))
  };
}

async function persistCustomizedProductCache() {
  const key = getCustomizedProductCacheKey();
  const payload = {
    badgeRuleVersion: customizedProductState.cacheVersion || 2,
    syncedAt: customizedProductState.syncedAt || '',
    sourceTotal: customizedProductState.sourceTotal || 0,
    statusCounts: customizedProductState.statusCounts || {},
    items: (customizedProductState.allProducts || []).map(compactCustomizedProductForCache)
  };
  try {
    await safeStorageSet({ [key]: payload }, '定制品缓存');
    return true;
  } catch (err) {
    console.warn('[小青蛙电商助手] 定制品缓存超过浏览器配额，改为只保存同步摘要:', err);
    try {
      await chrome.storage.local.remove(key);
      await safeStorageSet({
        [key]: {
          badgeRuleVersion: customizedProductState.cacheVersion || 2,
          syncedAt: customizedProductState.syncedAt || '',
          sourceTotal: customizedProductState.sourceTotal || 0,
          statusCounts: customizedProductState.statusCounts || {},
          items: []
        }
      }, '定制品同步摘要');
    } catch (summaryErr) {
      console.warn('[小青蛙电商助手] 定制品同步摘要保存失败，本次仅保留内存数据:', summaryErr);
    }
    return false;
  }
}

async function loadCustomizedProductCache() {
  const key = getCustomizedProductCacheKey();
  const result = await chrome.storage.local.get(key);
  const cache = result[key];
  if (!cache?.items?.length || Number(cache.badgeRuleVersion || 0) !== Number(customizedProductState.cacheVersion || 2)) {
    customizedProductState.cacheLoaded = true;
    customizedProductState.cacheKey = key;
    customizedProductState.allProducts = [];
    customizedProductState.products = [];
    customizedProductState.viewProducts = [];
    customizedProductState.sourceTotal = 0;
    customizedProductState.syncedAt = '';
    customizedProductState.statusCounts = {};
    return false;
  }

  customizedProductState.cacheLoaded = true;
  customizedProductState.cacheKey = key;
  customizedProductState.allProducts = cache.items.map(normalizeCustomizedProductRecord);
  customizedProductState.products = customizedProductState.allProducts.slice();
  customizedProductState.viewProducts = customizedProductState.allProducts.slice();
  customizedProductState.sourceTotal = Number(cache.sourceTotal || customizedProductState.allProducts.length || 0);
  customizedProductState.syncedAt = cache.syncedAt || '';
  customizedProductState.statusCounts = cache.statusCounts || computeCustomizedStatusCounts(customizedProductState.allProducts);
  customizedProductState.total = customizedProductState.viewProducts.length;
  customizedProductState.totalPages = Math.max(1, Math.ceil(customizedProductState.total / Math.max(1, customizedProductState.pageSize || 20)));
  return true;
}

function matchesCustomizedProduct(product = {}, query = '') {
  if (!query) return true;
  const key = query.toLowerCase();
  const statusBadgeText = Array.isArray(product.statusBadges)
    ? product.statusBadges.map(b => b.text).join(' ')
    : '';
  return [
    product.goodsId,
    product.productId,
    product.productName,
    product.skuName,
    product.skuId,
    product.skuSpec,
    product.skuCode,
    product.statusName,
    product.lifecycleStageName,
    product.skcTopStatus,
    product.skcStatus,
    product.skcSiteStatus,
    product.removeStatus,
    statusBadgeText,
    product.mainImage,
    product.skuCount,
    product.createTime
  ].some(value => normalizeSalesText(value).toLowerCase().includes(key));
}

function getCustomizedFilteredProducts() {
  const source = Array.isArray(customizedProductState.viewProducts)
    ? customizedProductState.viewProducts
    : (Array.isArray(customizedProductState.allProducts) ? customizedProductState.allProducts : customizedProductState.products);
  return source || [];
}

function getCustomizedDisplayProducts() {
  const source = getCustomizedFilteredProducts();
  const pageNum = Math.max(1, customizedProductState.pageNum || 1);
  const pageSize = Math.max(1, customizedProductState.pageSize || 20);
  const start = (pageNum - 1) * pageSize;
  return source.slice(start, start + pageSize);
}

const CUSTOMIZED_EXPORT_FIELDS = [
  { key: 'productName', label: '商品名称' },
  { key: 'spuId', label: 'SPU ID' },
  { key: 'skcImage', label: 'SKC图片' },
  { key: 'skcId', label: 'SKC ID' },
  { key: 'skcCode', label: 'SKC货号' },
  { key: 'skuImage', label: 'SKU图片' },
  { key: 'skuId', label: 'SKU ID', default: true },
  { key: 'skuCode', label: 'SKU货号' },
  { key: 'skuSpec', label: '商品规格' },
  { key: 'category', label: '类目' },
  { key: 'stock', label: '库存' },
  { key: 'price', label: '申报价格' },
  { key: 'status', label: '商品状态' },
  { key: 'onlineTime', label: '加站时间' },
  { key: 'createTime', label: '创建时间' }
];

function getCustomizedSkuImage(sku = {}, product = {}) {
  return sku.skuImage || sku.imageUrl || sku.thumbUrl || sku.productSkuImage || sku.mainImage || product.mainImage || '';
}

function getCustomizedSkuCode(sku = {}, product = {}) {
  return sku.skuCode || sku.extCode || sku.productSkuCode || sku.skuExtCode || product.skuCode || '';
}

function getCustomizedExportRows(products = []) {
  const rows = [];
  products.forEach(product => {
    const skuList = Array.isArray(product.skuList) && product.skuList.length ? product.skuList : [null];
    skuList.forEach(sku => {
      const rowSku = sku || {};
      rows.push({
        product,
        sku: rowSku,
        values: {
          productName: product.productName || '',
          spuId: product.goodsId || product.spuId || '',
          skcImage: product.mainImage || '',
          skcId: product.productId || product.skcId || '',
          skcCode: product.skcExtCode || product.skuCode || '',
          skuImage: getCustomizedSkuImage(rowSku, product),
          skuId: rowSku.skuId || rowSku.productSkuId || '',
          skuCode: getCustomizedSkuCode(rowSku, product),
          skuSpec: rowSku.skuSpec || rowSku.specInfo || product.skuSpecText || '',
          category: product.categoryName || product.categoryId || '',
          stock: rowSku.stockText || rowSku.showStockText || '',
          price: rowSku.price || product.price || '',
          status: product.lifecycleStageName || product.productStatusName || product.statusName || '',
          onlineTime: product.onlineTime || '',
          createTime: rowSku.createTime || product.createTime || ''
        }
      });
    });
  });
  return rows;
}

function csvEscape(value) {
  const text = String(value ?? '').replace(/\r?\n/g, ' ').trim();
  return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
}

function triggerBrowserDownload(filename, blob) {
  if (!(blob instanceof Blob)) {
    throw new Error('导出文件生成失败');
  }
  const url = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = url;
  link.download = filename;
  link.rel = 'noopener';
  link.style.display = 'none';
  document.body.appendChild(link);
  link.click();
  window.setTimeout(() => {
    link.remove();
    URL.revokeObjectURL(url);
  }, 1500);
}

function downloadTextFile(filename, content, mime = 'text/csv;charset=utf-8') {
  const blob = new Blob([content], { type: mime });
  triggerBrowserDownload(filename, blob);
}

function exportCustomizedProducts(selectedFieldKeys = []) {
  const fields = CUSTOMIZED_EXPORT_FIELDS.filter(field => selectedFieldKeys.includes(field.key));
  if (!fields.length) {
    showToast('请至少选择一个导出项', 'error');
    return;
  }
  const checkedKeys = uniqueStringList(Array.from(document.querySelectorAll('.customized-product-checkbox:checked'))
    .map(cb => cb.dataset.spu || cb.dataset.skc || ''));
  const sourceProducts = getCustomizedFilteredProducts();
  const products = checkedKeys.length
    ? sourceProducts.filter(product => checkedKeys.includes(String(product.goodsId || product.spuId || '')) || checkedKeys.includes(String(product.productId || product.skcId || '')))
    : sourceProducts;
  if (!products.length) {
    showToast('暂无可导出的定制品数据，请先同步商品列表', 'error');
    return;
  }
  const rows = getCustomizedExportRows(products);
  const header = fields.map(field => csvEscape(field.label)).join(',');
  const body = rows.map(row => fields.map(field => csvEscape(row.values[field.key])).join(',')).join('\n');
  const date = new Date();
  const pad = n => String(n).padStart(2, '0');
  const filename = `定制品导出_${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}_${pad(date.getHours())}${pad(date.getMinutes())}.csv`;
  downloadTextFile(filename, `\uFEFF${header}\n${body}`);
  showToast(`已导出 ${rows.length} 行定制品数据`, 'success');
}

function showCustomizedExportDialog(event) {
  event?.preventDefault?.();
  event?.stopPropagation?.();
  const existingModal = document.getElementById('customizedExportModal');
  if (existingModal) return;
  const modal = document.createElement('div');
  modal.className = 'modal';
  modal.id = 'customizedExportModal';
  modal.innerHTML = `
    <div class="modal-content" style="max-width:860px;">
      <h2 style="margin:0 0 14px;font-size:16px;">导出项选择</h2>
      <label class="customized-export-option" style="margin-bottom:4px;">
        <input type="checkbox" id="customizedExportSelectAll">
        <span>全部选择</span>
      </label>
      <div class="customized-export-grid">
        ${CUSTOMIZED_EXPORT_FIELDS.map(field => `
          <label class="customized-export-option">
            <input type="checkbox" class="customized-export-field" value="${escapeHtml(field.key)}" ${field.default ? 'checked' : ''}>
            <span>${escapeHtml(field.label)}</span>
          </label>
        `).join('')}
      </div>
      <div style="display:flex;justify-content:flex-end;gap:8px;margin-top:18px;">
        <button type="button" class="btn btn-default" id="customizedExportCancelBtn">取消</button>
        <button type="button" class="btn btn-primary" id="customizedExportConfirmBtn">导出</button>
      </div>
    </div>
  `;
  document.body.appendChild(modal);
  const close = () => {
    if (modal.parentNode) modal.parentNode.removeChild(modal);
  };
  const selectAll = modal.querySelector('#customizedExportSelectAll');
  const fields = Array.from(modal.querySelectorAll('.customized-export-field'));
  const syncSelectAll = () => {
    const checkedCount = fields.filter(input => input.checked).length;
    selectAll.checked = checkedCount === fields.length;
    selectAll.indeterminate = checkedCount > 0 && checkedCount < fields.length;
  };
  fields.forEach(input => input.addEventListener('change', syncSelectAll));
  selectAll.addEventListener('change', () => {
    fields.forEach(input => { input.checked = selectAll.checked; });
    syncSelectAll();
  });
  modal.querySelector('#customizedExportCancelBtn')?.addEventListener('click', close);
  modal.addEventListener('click', (e) => { if (e.target === modal) close(); });
  modal.querySelector('#customizedExportConfirmBtn')?.addEventListener('click', () => {
    const selected = fields.filter(input => input.checked).map(input => input.value);
    exportCustomizedProducts(selected);
    close();
  });
  syncSelectAll();
}

if (!window.__CUSTOMIZED_EXPORT_CLICK_BOUND__) {
  window.__CUSTOMIZED_EXPORT_CLICK_BOUND__ = true;
  document.addEventListener('click', (event) => {
    const exportBtn = event.target?.closest?.('#customizedExportBtn');
    if (!exportBtn) return;
    showCustomizedExportDialog(event);
  }, true);
}

function bindCustomizedExportButton() {
  const exportBtn = document.getElementById('customizedExportBtn');
  if (!exportBtn || exportBtn.dataset.customizedExportBound === '1') return;
  exportBtn.dataset.customizedExportBound = '1';
  exportBtn.addEventListener('click', showCustomizedExportDialog, true);
  exportBtn.onclick = showCustomizedExportDialog;
}

bindCustomizedExportButton();
document.addEventListener('DOMContentLoaded', bindCustomizedExportButton);
window.setTimeout(bindCustomizedExportButton, 500);
window.setTimeout(bindCustomizedExportButton, 1500);

async function fetchCustomizedProductsAllViaBackground(params = {}) {
  const pageSize = Math.max(1, params.pageSize || customizedProductState.pageSize || 20);
  const lifecycleStage = params.lifecycleStage || '';
  let pageNo = 1;
  let totalPages = 1;
  let total = 0;
  const products = [];

  while (pageNo <= totalPages) {
    const result = await safeRuntimeSendMessage({
      action: 'fetchCustomizedProductList',
      params: {
        pageNo,
        pageSize,
        lifecycleStage: lifecycleStage || undefined
      }
    }, { success: false, error: '获取商品管理列表失败' }, 30000);

    if (!result?.success) {
      throw new Error(result?.error || '获取商品管理列表失败');
    }

    const pageProducts = Array.isArray(result.products) ? result.products : [];
    products.push(...pageProducts);
    total = result.total || total || products.length;
    totalPages = Math.max(1, result.totalPages || Math.ceil(total / pageSize));
    pageNo += 1;
  }

  return {
    success: true,
    products,
    total,
    totalPages
  };
}

async function fetchCustomizedProductsFromTemuListPage(params = {}) {
  return await safeRuntimeSendMessage(
    { action: 'fetchCustomizedProductsFromTemuListPage', params },
    { success: false, error: '读取 TEMU 商品管理列表失败' },
    20000
  );
}

function parseSpuIdList(raw = '') {
  return String(raw || '')
    .split(/[,，\s]+/)
    .map(s => s.trim())
    .filter(s => s && /^\d+$/.test(s));
}

function uniqueStringList(list = []) {
  const seen = new Set();
  const result = [];
  list.forEach(item => {
    const value = String(item || '').trim();
    if (!value || seen.has(value)) return;
    seen.add(value);
    result.push(value);
  });
  return result;
}

function getCustomizedProductSpu(product = {}) {
  return String(product.goodsId || product.spuId || product.productId || product.skcId || '').trim();
}

function isCustomizedProductPending(product = {}) {
  const topStatus = Number(product.skcTopStatus);
  if (topStatus === 0) return true;
  if ([200, 300].includes(topStatus)) return false;

  const badgeText = (Array.isArray(product.statusBadges) ? product.statusBadges : [])
    .map(badge => String(badge?.text || ''))
    .join(' ');
  if (/待|未配置|资质待上传|即将废弃/.test(badgeText)) return true;

  const skuList = Array.isArray(product.skuList) ? product.skuList : [];
  if (!skuList.length) return false;
  return !skuList.some(sku => {
    const tmpl = sku?.productSkuCustomizationTmpl || sku?.productSkuSaleExtAttr?.productSkuCustomizationTmpl;
    const templateIds = Array.isArray(tmpl?.templateIds)
      ? tmpl.templateIds
      : [tmpl?.templateId, tmpl?.template_id, tmpl?.id, sku.templateId].filter(Boolean);
    return Boolean(tmpl && templateIds.length);
  });
}

function getCustomizedSelectedSpuIds() {
  return uniqueStringList(Array.from(document.querySelectorAll('.customized-product-checkbox:checked'))
    .map(cb => cb.dataset.spu || cb.dataset.skc || ''));
}

function resolveCustomizedTargetSpuIds(scope = 'selected') {
  const manualSpuIds = parseSpuIdList(document.getElementById('customizedTargetSpuId')?.value || '');
  if (manualSpuIds.length) return uniqueStringList(manualSpuIds);

  if (scope === 'selected') {
    return getCustomizedSelectedSpuIds();
  }

  const products = Array.isArray(customizedProductState.allProducts) ? customizedProductState.allProducts : [];
  const scopedProducts = scope === 'pending'
    ? products.filter(isCustomizedProductPending)
    : products;
  return uniqueStringList(scopedProducts.map(getCustomizedProductSpu));
}

function applyCustomizedProductFilter() {
  const query = getCustomizedSearchQuery();
  const statusTop = getCustomizedStatusTopStatus();
  const source = Array.isArray(customizedProductState.allProducts) ? customizedProductState.allProducts : [];

  const filtered = source.filter(product => {
    if (!matchesCustomizedProduct(product, query)) return false;
    if (statusTop === null) return true;
    return Number(product.skcTopStatus ?? product.topStatus ?? -1) === statusTop;
  });

  customizedProductState.products = source.slice();
  customizedProductState.viewProducts = filtered;
  customizedProductState.total = filtered.length;
  customizedProductState.totalPages = Math.max(1, Math.ceil(filtered.length / Math.max(1, customizedProductState.pageSize || 20)));
  if (customizedProductState.pageNum > customizedProductState.totalPages) customizedProductState.pageNum = 1;
  renderCustomizedProductTable();
  renderCustomizedProductPagination();
  renderCustomizedStatusTabCounts();
}

async function syncCustomizedProducts(params = {}) {
  const container = document.getElementById('customizedProductListContainer');
  const loadBtn = document.getElementById('customizedProductLoadBtn');
  const originalText = loadBtn?.textContent || '';
  if (loadBtn) {
    loadBtn.disabled = true;
    loadBtn.textContent = '同步中...';
  }
  container.innerHTML = '<div class="loading">同步中...</div>';

  try {
    const pageSize = Math.max(100, customizedProductState.pageSize || 20);
    const result = await fetchCustomizedProductsAllViaBackground({ pageSize });
    if (!result?.success || !Array.isArray(result.products)) {
      throw new Error(result?.error || '获取商品列表失败');
    }
    let normalized = result.products.map(normalizeCustomizedProductRecord);
    try {
      const exactListResult = await fetchCustomizedProductsFromTemuListPage({});
      if (exactListResult?.success && Array.isArray(exactListResult.products)) {
        normalized = mergeCustomizedExactBadges(normalized, exactListResult.products);
      }
    } catch (exactErr) {
      console.warn('[小青蛙电商助手] 未能读取 TEMU 商品列表 DOM 标签，保持 API 数据不伪造标签:', exactErr);
    }
    customizedProductState.allProducts = normalized;
    customizedProductState.products = normalized.slice();
    customizedProductState.sourceTotal = Number(result.sourceTotal || result.total || normalized.length || 0);
    customizedProductState.syncedAt = new Date().toLocaleString('zh-CN', { hour12: false });
    customizedProductState.statusCounts = computeCustomizedStatusCounts(normalized);
    customizedProductState.cacheLoaded = true;
    customizedProductState.cacheKey = getCustomizedProductCacheKey();
    customizedProductState.pageNum = 1;

    try {
      const countRes = await safeRuntimeSendMessage({ action: 'fetchCustomizedProductStatusCount', params: {} }, { success: false, error: '获取定制品状态统计失败' }, 15000);
      const list = countRes?.result?.skcTopStatusCountList || countRes?.skcTopStatusCountList || [];
      if (Array.isArray(list) && list.length) {
        const counts = { all: customizedProductState.sourceTotal || normalized.length || 0, 0: 0, 100: 0, 200: 0, 300: 0 };
        list.forEach(entry => {
          const topStatus = Number(entry.skcTopStatus);
          if ([0, 100, 200, 300].includes(topStatus)) counts[topStatus] = Number(entry.skcTotal || 0);
        });
        customizedProductState.statusCounts = counts;
      }
    } catch (countErr) {
      console.warn('[小青蛙电商助手] 定制品状态统计获取失败，使用本地计算:', countErr);
    }

    applyCustomizedProductFilter();
    await persistCustomizedProductCache();
  } catch (err) {
    console.error('[小青蛙电商助手] syncCustomizedProducts 异常:', err);
    container.innerHTML = `<div class="empty"><div class="empty-icon">❌</div><p>同步失败: ${escapeHtml(err?.message || String(err))}</p></div>`;
  } finally {
    if (loadBtn) {
      loadBtn.disabled = false;
      loadBtn.textContent = originalText || '🔄 同步商品列表';
    }
  }
}

async function loadCustomizedProducts(params = {}) {
  const cacheKey = getCustomizedProductCacheKey();
  if (!customizedProductState.cacheLoaded || customizedProductState.cacheKey !== cacheKey) {
    await loadCustomizedProductCache();
  }
  if (typeof params.pageNum === 'number' && params.pageNum > 0) {
    customizedProductState.pageNum = params.pageNum;
  }
  if (typeof params.pageSize === 'number' && params.pageSize > 0) {
    customizedProductState.pageSize = params.pageSize;
  }
  if (!customizedProductState.allProducts.length) {
    const container = document.getElementById('customizedProductListContainer');
    container.innerHTML = '<div class="empty"><div class="empty-icon">📦</div><p>暂无已同步的定制品数据，请点击「同步商品列表」</p></div>';
    renderCustomizedProductPagination();
    renderCustomizedStatusTabCounts();
    return;
  }
  applyCustomizedProductFilter();
}

// 渲染定制品商品列表
function renderCustomizedProductTable() {
  const container = document.getElementById('customizedProductListContainer');
  const products = getCustomizedDisplayProducts();

  if (products.length === 0) {
    container.innerHTML = '<div class="empty"><div class="empty-icon">📦</div><p>暂无商品数据</p></div>';
    return;
  }

  const escapeAttr = value => escapeHtml(value ?? '');
  const fallbackBadgeStyles = {
    warn: { color: '#ff6800', backgroundColor: '#fff4e6', borderColor: '#ff6800' },
    danger: { color: '#dc2626', backgroundColor: '#fee2e2', borderColor: '#dc2626' },
    info: { color: '#0071f3', backgroundColor: '#e6f6ff', borderColor: '#0071f3' }
  };

  container.innerHTML = `
    <div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;padding:8px 10px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;">
      <label style="display:inline-flex;align-items:center;gap:6px;font-size:13px;font-weight:700;color:#334155;cursor:pointer;">
        <input type="checkbox" class="checkbox" id="customizedSelectPage">
        <span>全选本页</span>
      </label>
      <span id="customizedPageSelectInfo" style="font-size:12px;color:#64748b;">本页 ${products.length} 条</span>
    </div>
    <div class="customized-card-list">
      ${products.map(p => {
        const imgHtml = p.mainImage
          ? `<img src="${escapeAttr(p.mainImage)}" class="customized-product-image img-fallback" data-fallback-size="72">`
          : '<div class="customized-product-image" style="display:flex;align-items:center;justify-content:center;background:#f8fafc;color:#cbd5e1;font-size:20px;">📷</div>';
        const badges = Array.isArray(p.statusBadges) ? p.statusBadges : [];
        const skuList = Array.isArray(p.skuList) ? p.skuList : [];

        return `
          <div class="customized-product-card">
            <div class="customized-product-select">
              <input type="checkbox" class="checkbox product-checkbox customized-product-checkbox" data-spu="${escapeAttr(p.goodsId || p.spuId || '')}" data-skc="${escapeAttr(p.productId || p.skcId || '')}">
            </div>
            <div class="customized-product-left">
              ${imgHtml}
              <div class="customized-product-meta">
                <div class="customized-product-title" title="${escapeAttr(p.productName || '-') }">${escapeHtml(p.productName || '-')}</div>
                <div class="customized-badge-row">
                  ${badges.map((badge, idx) => {
                    const fallback = /资质待上传/.test(badge.text)
                      ? fallbackBadgeStyles.warn
                      : /在售|定制商品/.test(badge.text)
                        ? fallbackBadgeStyles.info
                        : /下架|终止|删除/.test(badge.text)
                          ? fallbackBadgeStyles.danger
                          : fallbackBadgeStyles.info;
                    const style = `background:${badge.backgroundColor || fallback.backgroundColor};color:${badge.color || fallback.color};border:1px solid ${badge.borderColor || fallback.borderColor};`;
                    return `<span class="customized-badge" style="${style}">${escapeHtml(badge.text)}</span>`;
                  }).join('')}
                </div>
                <div class="customized-meta-row">
                  <span class="customized-meta-label">SPU ID</span>
                  <span class="customized-meta-value">${escapeHtml(p.goodsId || '—')}</span>
                </div>
                <div class="customized-meta-row">
                  <span class="customized-meta-label">SKC ID</span>
                  <span class="customized-meta-value">${escapeHtml(p.productId || '—')}</span>
                </div>
                <div class="customized-meta-row">
                  <span class="customized-meta-label">SKU数</span>
                  <span class="customized-meta-value">${escapeHtml(String(p.skuCount || skuList.length || 0))}</span>
                </div>
              </div>
            </div>
            <div class="customized-sku-panel">
              <div class="customized-sku-head">
                <span>SKU ID</span>
                <span>商品规格</span>
                <span>申报价格(CNY)</span>
                <span>库存</span>
                <span>创建时间</span>
              </div>
              ${skuList.length > 0 ? skuList.map(sku => {
                return `
                  <div class="customized-sku-row">
                    <span class="customized-sku-cell customized-sku-id" title="${escapeAttr(sku.skuId || '—')}">${escapeHtml(sku.skuId || '—')}</span>
                    <span class="customized-sku-cell customized-sku-spec" title="${escapeAttr(sku.skuSpec || '—')}">${escapeHtml(sku.skuSpec || '—')}</span>
                    <span class="customized-sku-cell customized-sku-price" title="${sku.price ? `¥${escapeAttr(sku.price)}` : '—'}">${sku.price ? `¥${escapeHtml(sku.price)}` : '—'}</span>
                    <span class="customized-sku-cell customized-sku-stock" title="${escapeAttr(sku.stockText || sku.showStockText || '—')}">${escapeHtml(sku.stockText || sku.showStockText || '—')}</span>
                    <span class="customized-sku-cell" title="${escapeAttr(sku.createTime || p.createTime || '—')}">${escapeHtml(sku.createTime || p.createTime || '—')}</span>
                  </div>
                `;
              }).join('') : `
                <div class="customized-sku-row" style="grid-template-columns:1fr;">
                  <span class="customized-sku-cell" style="color:#94a3b8;">暂无 SKU 明细</span>
                </div>
              `}
            </div>
          </div>
        `;
      }).join('')}
    </div>
  `;

  const pageSelect = document.getElementById('customizedSelectPage');
  const pageChecks = Array.from(container.querySelectorAll('.customized-product-checkbox'));
  const syncPageSelect = () => {
    const checked = pageChecks.filter(cb => cb.checked).length;
    if (pageSelect) {
      pageSelect.checked = pageChecks.length > 0 && checked === pageChecks.length;
      pageSelect.indeterminate = checked > 0 && checked < pageChecks.length;
    }
    updateSelectedCountInfo();
  };
  pageSelect?.addEventListener('change', () => {
    pageChecks.forEach(cb => { cb.checked = pageSelect.checked; });
    syncPageSelect();
  });
  pageChecks.forEach(cb => cb.addEventListener('change', syncPageSelect));
  syncPageSelect();
}

// 渲染定制品商品列表分页
function renderCustomizedProductPagination() {
  const info = document.getElementById('customizedProductPaginationInfo');
  const btns = document.getElementById('customizedProductPaginationButtons');
  const container = document.getElementById('customizedProductPaginationContainer');
  if (!info || !btns) return;

  const filteredProducts = getCustomizedFilteredProducts();
  const totalPages = Math.max(1, Math.ceil(filteredProducts.length / Math.max(1, customizedProductState.pageSize || 20)));
  if (container) container.style.display = filteredProducts.length > 0 ? 'flex' : 'none';

  const s = customizedProductState;
  if (filteredProducts.length > 0) {
    const hasFilter = Boolean(getCustomizedSearchQuery()) || getCustomizedStatusTopStatus() !== null;
    const totalLabel = hasFilter
      ? `共 <strong>${filteredProducts.length}</strong> 条`
      : `共 <strong>${Number(s.sourceTotal || filteredProducts.length || 0)}</strong> 条`;
    info.innerHTML = `${totalLabel}，第 <strong>${s.pageNum}</strong>/<strong>${totalPages}</strong> 页${s.syncedAt ? `，上次同步时间 <strong>${escapeHtml(s.syncedAt)}</strong>` : ''}`;
  } else {
    info.innerHTML = s.syncedAt ? `暂无数据，上次同步时间 <strong>${escapeHtml(s.syncedAt)}</strong>` : '暂无数据';
  }

  // 分页按钮
  btns.innerHTML = '';
  if (s.pageNum > 1) {
    const prev = document.createElement('button');
    prev.className = 'btn btn-default btn-sm';
    prev.textContent = '上一页';
    prev.addEventListener('click', () => loadCustomizedProducts({ pageNum: s.pageNum - 1 }));
    btns.appendChild(prev);
  }
  if (s.pageNum < totalPages) {
    const next = document.createElement('button');
    next.className = 'btn btn-default btn-sm';
    next.textContent = '下一页';
    next.addEventListener('click', () => loadCustomizedProducts({ pageNum: s.pageNum + 1 }));
    btns.appendChild(next);
  }

  if (filteredProducts.length > 0) {
    const jumpWrap = document.createElement('div');
    jumpWrap.style.display = 'flex';
    jumpWrap.style.alignItems = 'center';
    jumpWrap.style.gap = '6px';
    jumpWrap.style.marginLeft = '8px';
    jumpWrap.innerHTML = `
      <span style="font-size:12px;color:#64748b;">跳转</span>
      <input type="number" min="1" max="${totalPages}" value="${s.pageNum}" style="width:72px;padding:4px 8px;border:1px solid #cbd5e1;border-radius:8px;font-size:12px;">
      <button class="btn btn-default btn-sm" type="button">确定</button>
    `;
    const input = jumpWrap.querySelector('input');
    const button = jumpWrap.querySelector('button');
    button.addEventListener('click', () => {
      const pageNum = Math.max(1, Math.min(totalPages, parseInt(input.value, 10) || 1));
      loadCustomizedProducts({ pageNum });
    });
    btns.appendChild(jumpWrap);
  }
}

const customizedState = {
  templates: [],       // 定制品模板列表
  isRunning: false,
  isDelisting: false
};

function normalizeCustomizedTemplateRecord(template = {}) {
  const templateIds = [...new Set(
    (Array.isArray(template.templateIds) ? template.templateIds : [template.templateId, template.template_id])
      .map(item => String(item || '').trim())
      .filter(Boolean)
  )];
  const surfaces = Array.isArray(template.surfaces)
    ? template.surfaces.filter(item => item && typeof item === 'object')
    : [];
  const primaryTemplateId = String(template.templateId || template.template_id || templateIds[0] || '').trim();
  const skuId = String(template.skuId || template.productSkuId || '').trim();
  const spuId = String(template.spuId || template.goodsId || '').trim();
  const createdAt = template.createdAt || '';

  return {
    ...template,
    id: String(template.id || (template.serverId ? `server_customized_${template.serverId}` : `${primaryTemplateId || 'template'}__${skuId || 'sku'}__${spuId || 'spu'}__${createdAt || 'legacy'}`)),
    templateId: primaryTemplateId,
    templateIds: templateIds.length ? templateIds : (primaryTemplateId ? [primaryTemplateId] : []),
    surfaces,
    skuId,
    spuId,
    sourceProductId: String(template.sourceProductId || template.productId || template.goodsId || '').trim(),
    sourceGoodsId: String(template.sourceGoodsId || template.goodsId || template.spuId || '').trim(),
    sourceTemplateSkuId: String(template.sourceTemplateSkuId || template.skuId || template.productSkuId || '').trim()
  };
}

function normalizeServerCustomizedTemplate(row = {}) {
  const data = row.data && typeof row.data === 'object' ? row.data : {};
  return normalizeCustomizedTemplateRecord({
    ...data,
    id: data.id || `server_customized_${row.id}`,
    serverId: row.id,
    name: data.name || row.name || '未命名模板',
    createdAt: data.createdAt || row.createdAt || row.updatedAt || ''
  });
}

function mergeCustomizedTemplates(...lists) {
  const merged = new Map();
  lists.flat().forEach(template => {
    if (!template || typeof template !== 'object') return;
    const normalized = normalizeCustomizedTemplateRecord(template);
    const fingerprintParts = [
      (normalized.templateIds || [normalized.templateId]).join('|'),
      normalized.sourceTemplateSkuId || normalized.skuId || '',
      normalized.spuId || '',
      normalized.name || ''
    ].map(item => String(item || '').trim()).filter(Boolean);
    const fingerprint = fingerprintParts.length ? `fp:${fingerprintParts.join('::')}` : '';
    const key = normalized.serverId ? `server:${normalized.serverId}` : (fingerprint || `local:${normalized.id}`);
    const existing = fingerprint ? merged.get(fingerprint) : null;
    if (existing && !existing.serverId && normalized.serverId) {
      merged.delete(fingerprint);
    } else if (existing && existing.serverId && !normalized.serverId) {
      return;
    }
    merged.set(key, normalized);
    if (fingerprint && key !== fingerprint && !merged.has(fingerprint)) merged.set(fingerprint, normalized);
  });
  return Array.from(new Map(Array.from(merged.values()).map(item => [item.serverId ? `server:${item.serverId}` : item.id, item])).values());
}

// 定制品模板 storage key
function getCustomizedTemplateKey(mallId) {
  return mallId ? `temuCustomizedTemplates_${mallId}` : 'temuCustomizedTemplates';
}

// 初始化定制品 Tab
function initCustomizedTab() {
  document.querySelectorAll('#customized button').forEach(btn => {
    if (!btn.getAttribute('type')) btn.setAttribute('type', 'button');
  });
  bindCustomizedExportButton();
  // 加载模板
  loadCustomizedTemplates();

  // 状态Tab点击
  document.getElementById('customizedProductStatusTabs')?.addEventListener('click', (e) => {
    const tab = e.target.closest('.status-tab');
    if (!tab) return;
    const stage = tab.dataset.stage;
    // 切换active样式
    document.querySelectorAll('#customizedProductStatusTabs .status-tab').forEach(t => {
      t.style.background = '#f1f5f9';
      t.style.color = '#64748b';
      t.classList.remove('active');
    });
    tab.style.background = 'var(--primary)';
    tab.style.color = 'white';
    tab.classList.add('active');
    // 同步下拉框
    const statusFilter = document.getElementById('customizedProductStatusFilter');
    if (statusFilter) statusFilter.value = stage;
    customizedProductState.pageNum = 1;
    loadCustomizedProducts();
  });

  // 商品列表
  document.getElementById('customizedProductLoadBtn')?.addEventListener('click', (event) => {
    event.preventDefault();
    void syncCustomizedProducts();
  });
  document.getElementById('customizedExportBtn')?.addEventListener('click', showCustomizedExportDialog);
  document.getElementById('customizedProductSearchBtn')?.addEventListener('click', (event) => {
    event.preventDefault();
    customizedProductState.pageNum = 1;
    loadCustomizedProducts();
  });
  document.getElementById('customizedProductSearch')?.addEventListener('keydown', (e) => { if (e.key === 'Enter') loadCustomizedProducts(); });
  document.getElementById('customizedProductPageSize')?.addEventListener('change', () => {
    customizedProductState.pageSize = parseInt(document.getElementById('customizedProductPageSize')?.value) || 20;
    customizedProductState.pageNum = 1;
    loadCustomizedProducts();
  });
  document.getElementById('customizedProductStatusFilter')?.addEventListener('change', () => {
    customizedProductState.pageNum = 1;
    loadCustomizedProducts();
  });

  // 切换到定制品tab时自动加载商品列表
  document.querySelector('[data-tab="customized"]')?.addEventListener('click', (event) => {
    event.preventDefault();
    loadCustomizedProducts();
  });

  // 新增模板
  document.getElementById('customizedAddTemplateBtn')?.addEventListener('click', (event) => {
    event.preventDefault();
    showAddCustomizedTemplateDialog(event);
  });

  document.getElementById('customizedTemplateSelect')?.addEventListener('change', (event) => {
    renderCustomizedTemplates();
  });
  document.getElementById('refreshCustomizedTemplatesBtn')?.addEventListener('click', refreshCustomizedTemplatesFromButton);

  document.getElementById('customizedApplyStock')?.addEventListener('change', (event) => {
    const input = document.getElementById('customizedStockQuantity');
    if (!input) return;
    input.disabled = !event.target.checked;
    input.style.background = event.target.checked ? 'var(--surface)' : '#f8fafc';
    if (event.target.checked) input.focus();
  });

  document.getElementById('customizedStartBtn')?.addEventListener('click', startCustomizedApply);
  document.getElementById('customizedDelistBtn')?.addEventListener('click', startCustomizedBatchDelist);
  document.getElementById('customizedDelistClearBtn')?.addEventListener('click', (event) => {
    event.preventDefault();
    const input = document.getElementById('customizedDelistSpuIds');
    if (input) input.value = '';
  });

  const initialContainer = document.getElementById('customizedProductListContainer');
  if (initialContainer && !customizedProductState.cacheLoaded) {
    initialContainer.innerHTML = '<div class="loading">读取上次同步数据...</div>';
  }
  loadCustomizedProductCache().then(async (loaded) => {
    if (loaded) {
      loadCustomizedProducts();
      return;
    }
    if (initialContainer) {
      initialContainer.innerHTML = '<div class="empty"><div class="empty-icon">📦</div><p>暂无同步缓存，请点击“同步商品列表”后再选择商品。</p></div>';
    }
  });
}

async function deleteSelectedCustomizedTemplate() {
    const select = document.getElementById('customizedTemplateSelect');
    const templateKey = select?.value;
    if (!templateKey) { showToast('请先选择要删除的模板', 'error'); return; }
    const template = customizedState.templates.find(t => t.id === templateKey);
    if (!template) return;
    if (!confirm(`确定删除模板「${template.name}」吗？`)) return;

    if (template.serverId) {
      const result = await deleteUserTemplate(template.serverId);
      if (!result.success) {
        showToast(`删除失败：${result.error || '服务器删除失败'}`, 'error');
        return;
      }
    }

    customizedState.templates = customizedState.templates.filter(t => t.id !== templateKey);
    const mallId = await resolveCurrentMallIdForStorage();
    await safeStorageSet({ [getCustomizedTemplateKey(mallId)]: customizedState.templates }, '定制品模板');
    renderCustomizedTemplates();
    showToast(`模板「${template.name}」已删除`, 'info');
}

// 加载定制品模板
async function loadCustomizedTemplates() {
  const mallId = await resolveCurrentMallIdForStorage();
  const key = getCustomizedTemplateKey(mallId);
  const [localResult, serverResult] = await Promise.all([
    chrome.storage.local.get(key),
    fetchUserTemplates('customized', mallId).catch(err => ({ success: false, error: err.message }))
  ]);
  const localTemplates = (Array.isArray(localResult[key]) ? localResult[key] : []).map(normalizeCustomizedTemplateRecord);
  const serverTemplates = serverResult?.success
    ? serverResult.templates.map(normalizeServerCustomizedTemplate)
    : [];
  customizedState.templates = mergeCustomizedTemplates(serverTemplates, localTemplates);
  if (customizedState.templates.length) {
    await safeStorageSet({ [key]: customizedState.templates }, '定制品模板');
  }
  renderCustomizedTemplates();
}

async function refreshCustomizedTemplatesFromButton(event) {
  const btn = event?.currentTarget;
  const oldText = btn?.textContent || '刷新';
  try {
    if (btn) {
      btn.disabled = true;
      btn.textContent = '刷新中...';
    }
    await loadCustomizedTemplates();
    showToast(`定制品模板已刷新，共 ${customizedState.templates.length} 个`, 'success');
  } catch (error) {
    showToast(`刷新定制品模板失败：${error?.message || error || '未知错误'}`, 'error');
  } finally {
    if (btn) {
      btn.disabled = false;
      btn.textContent = oldText;
    }
  }
}

// 渲染模板列表
function renderCustomizedTemplates() {
  const selectEl = document.getElementById('customizedTemplateSelect');
  const inlineList = document.getElementById('customizedTemplateInlineList');
  if (!selectEl) return;

  // 更新下拉选择
  const currentVal = selectEl.value;
  selectEl.innerHTML = '<option value="">-- 请选择模板 --</option>';
  if (inlineList) inlineList.innerHTML = '';
  customizedState.templates.forEach(tpl => {
    const opt = document.createElement('option');
    opt.value = tpl.id;
    opt.textContent = `${tpl.name}（SKU: ${tpl.skuId}）`;
    if (tpl.id === currentVal) opt.selected = true;
    selectEl.appendChild(opt);

    if (inlineList) {
      const item = document.createElement('div');
      item.tabIndex = 0;
      item.setAttribute('role', 'button');
      item.className = 'template-inline-item';
      item.dataset.templateId = String(tpl.id);
      item.title = tpl.name;
      if (tpl.id === currentVal) item.classList.add('active');

      const name = document.createElement('span');
      name.className = 'template-inline-name';
      name.textContent = `${tpl.name}（SKU: ${tpl.skuId}）`;

      const remove = document.createElement('button');
      remove.type = 'button';
      remove.className = 'template-inline-delete';
      remove.textContent = 'X';
      remove.title = `删除模板「${tpl.name}」`;
      remove.addEventListener('click', (event) => {
        event.preventDefault();
        event.stopPropagation();
        selectEl.value = String(tpl.id);
        void deleteSelectedCustomizedTemplate();
      });

      item.append(name, remove);
      item.addEventListener('click', () => {
        selectEl.value = String(tpl.id);
        renderCustomizedTemplates();
      });
      item.addEventListener('keydown', (event) => {
        if (event.key !== 'Enter' && event.key !== ' ') return;
        event.preventDefault();
        selectEl.value = String(tpl.id);
        renderCustomizedTemplates();
      });
      inlineList.appendChild(item);
    }
  });
}

function showCenterConfirm({ title = '确认操作', message = '', confirmText = '确认', cancelText = '取消' } = {}) {
  return new Promise((resolve) => {
    const modal = document.createElement('div');
    modal.className = 'center-confirm-mask';
    modal.innerHTML = `
      <div class="center-confirm-card">
        <div class="center-confirm-title">${escapeHtml(title)}</div>
        <div class="center-confirm-body">${escapeHtml(message)}</div>
        <div class="center-confirm-actions">
          <button class="btn btn-default btn-sm" type="button" data-action="cancel">${escapeHtml(cancelText)}</button>
          <button class="btn btn-primary btn-sm" type="button" data-action="confirm">${escapeHtml(confirmText)}</button>
        </div>
      </div>
    `;
    document.body.appendChild(modal);
    const close = (value) => {
      if (modal.parentNode) modal.parentNode.removeChild(modal);
      resolve(value);
    };
    modal.addEventListener('click', (event) => {
      if (event.target === modal) return close(false);
      const action = event.target?.closest?.('button[data-action]')?.dataset?.action;
      if (action === 'cancel') close(false);
      if (action === 'confirm') close(true);
    });
  });
}

// 显示新增模板弹窗
function showAddCustomizedTemplateDialog() {
  const modal = document.createElement('div');
  modal.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:10000;';
  modal.innerHTML = `
    <div style="background:white;padding:24px;border-radius:12px;width:460px;max-width:90vw;max-height:80vh;overflow-y:auto;box-shadow:0 8px 24px rgba(0,0,0,0.2);">
      <h2 style="margin:0 0 16px;font-size:16px;">➕ 新增定制品模板</h2>
      <div style="margin-bottom:14px;">
        <label style="display:block;margin-bottom:6px;font-size:13px;font-weight:500;">SPU ID（商品 ID）<span style="color:#ff4d4f;">*</span></label>
        <input type="text" id="newTemplateSpuId" placeholder="输入商品的 SPU ID（商品 ID）" style="width:100%;padding:8px 12px;border:1px solid #d9d9d9;border-radius:6px;font-size:14px;" autofocus>
        <div style="font-size:11px;color:#999;margin-top:4px;">💡 在 TEMU 商品列表的「商品ID」列可以找到。该商品下至少有一个 SKU 已配置定制品信息。</div>
      </div>
      <div style="margin-bottom:14px;">
        <label style="display:block;margin-bottom:6px;font-size:13px;font-weight:500;">模板名称 <span style="color:#ff4d4f;">*</span></label>
        <input type="text" id="newTemplateName" placeholder="例如：宠物照片毯-模板1" style="width:100%;padding:8px 12px;border:1px solid #d9d9d9;border-radius:6px;font-size:14px;" value="">
      </div>
      <div id="templateSkuList" style="display:none;margin-bottom:14px;">
        <label style="display:block;margin-bottom:6px;font-size:13px;font-weight:500;">选择已配置定制品的 SKU <span style="color:#ff4d4f;">*</span></label>
        <div id="templateSkuOptions" style="max-height:200px;overflow-y:auto;border:1px solid #d9d9d9;border-radius:6px;padding:4px;"></div>
      </div>
      <div style="display:flex;gap:10px;justify-content:flex-end;">
        <button class="btn btn-default" id="closeTemplateDialogBtn">取消</button>
        <button class="btn btn-primary" id="queryTemplateBtn">🔍 查询已配置的 SKU</button>
        <button class="btn btn-primary" id="confirmAddTemplateBtn" style="display:none;">✅ 确认保存模板</button>
      </div>
    </div>
  `;
  document.body.appendChild(modal);

  const close = () => document.body.removeChild(modal);
  modal.querySelector('#closeTemplateDialogBtn').addEventListener('click', close);
  modal.addEventListener('click', (e) => { if (e.target === modal) close(); });

  let foundSkus = []; // 存储查到的已配置定制品的 SKU
  let templateSourceData = null;

  // 查询按钮
  modal.querySelector('#queryTemplateBtn').addEventListener('click', async () => {
    const spuId = modal.querySelector('#newTemplateSpuId')?.value?.trim() || '';
    const name = modal.querySelector('#newTemplateName')?.value?.trim() || '';

    if (!spuId || !/^\d+$/.test(spuId)) { showToast('请输入有效的 SPU ID', 'error'); return; }
    if (!name) { showToast('请输入模板名称', 'error'); return; }

    const queryBtn = modal.querySelector('#queryTemplateBtn');
    queryBtn.disabled = true;
    queryBtn.textContent = '⏳ 查询中...';

    try {
      let data = findCustomizedTemplateDataLocally(spuId);
      if (!data?.foundSkus?.length) {
        const listResult = await safeRuntimeSendMessage({
          action: 'fetchCustomizedProductList',
          params: { pageNo: 1, pageSize: 100, productId: parseInt(spuId) }
        }, { success: false, error: '后台查询商品失败' });
        if (listResult?.success && Array.isArray(listResult.products) && listResult.products.length > 0) {
          const match = listResult.products.find(item => (
            String(item.goodsId || '') === String(spuId)
            || String(item.productId || '') === String(spuId)
            || String(item.skcId || '') === String(spuId)
          )) || listResult.products[0];
          const skuList = Array.isArray(match?.skuList) ? match.skuList : [];
          const allSkuIds = skuList.map(sku => sku.skuId || sku.productSkuId || '').filter(Boolean);
          const foundSkus = skuList
            .map(sku => {
              const tmpl = sku.productSkuCustomizationTmpl || sku.productSkuSaleExtAttr?.productSkuCustomizationTmpl || null;
              const templateIds = Array.isArray(tmpl?.templateIds)
                ? tmpl.templateIds
                : [tmpl?.templateId, tmpl?.template_id, tmpl?.id, sku.templateId].filter(Boolean);
              return {
                productSkuId: sku.skuId || sku.productSkuId || '',
                templateId: templateIds[0] || '',
                templateIds,
                specInfo: sku.skuSpec || sku.specInfo || ''
              };
            })
            .filter(sku => sku.productSkuId && (sku.templateId || sku.templateIds.length));
          data = allSkuIds.length || foundSkus.length ? {
            productId: match.productId || match.skcId || match.goodsId || spuId,
            goodsId: match.goodsId || spuId,
            allSkuIds,
            foundSkus
          } : null;
        }
      }

      queryBtn.disabled = false;
      queryBtn.textContent = '🔍 查询已配置的 SKU';

      if (data && data.foundSkus) {
        foundSkus = Array.isArray(data.foundSkus) ? data.foundSkus : [];
        templateSourceData = data;

        if (foundSkus.length === 0) {
          showToast('该商品下没有 SKU 配置了定制品信息', 'error');
          return;
        }

        // 显示 SKU 选择列表
        const skuListDiv = modal.querySelector('#templateSkuList');
        const skuOptionsDiv = modal.querySelector('#templateSkuOptions');
        if (!skuListDiv || !skuOptionsDiv) throw new Error('模板弹窗未初始化完成');
        skuListDiv.style.display = 'block';

        skuOptionsDiv.innerHTML = foundSkus.map((sku, idx) => `
          <label style="display:flex;align-items:center;gap:8px;padding:8px;border-radius:4px;cursor:pointer;${idx === 0 ? 'background:#e6f7ff;' : ''}" class="sku-option-label">
            <input type="radio" name="selectedSkuForTemplate" value="${idx}" ${idx === 0 ? 'checked' : ''}>
            <div style="flex:1;">
              <div style="font-size:13px;font-weight:500;">SKU ID: ${escapeHtml(String(sku.productSkuId || '-'))}</div>
              <div style="font-size:11px;color:#999;">规格: ${escapeHtml(String(sku.specInfo || '-'))} | 模板ID: ${escapeHtml(String((sku.templateIds || [sku.templateId]).filter(Boolean).join(' / ') || '-'))}</div>
            </div>
          </label>
        `).join('');

        // 选中高亮
        skuOptionsDiv.querySelectorAll('.sku-option-label').forEach(label => {
          label.addEventListener('click', () => {
            skuOptionsDiv.querySelectorAll('.sku-option-label').forEach(l => l.style.background = 'none');
            label.style.background = '#e6f7ff';
          });
        });

        // 显示确认按钮
        modal.querySelector('#confirmAddTemplateBtn').style.display = 'inline-block';

        showToast(`找到 ${foundSkus.length} 个已配置定制品的 SKU`, 'success');
      } else {
        throw new Error('未找到该商品已配置定制品的 SKU，请先同步定制品商品列表');
      }
    } catch (err) {
      queryBtn.disabled = false;
      queryBtn.textContent = '🔍 查询已配置的 SKU';
      showToast(`查询失败: ${err?.message || String(err)}`, 'error');
    }
  });

  // 确认保存
  modal.querySelector('#confirmAddTemplateBtn').addEventListener('click', async () => {
    const name = modal.querySelector('#newTemplateName')?.value?.trim() || '';
    const spuId = modal.querySelector('#newTemplateSpuId')?.value?.trim() || '';

    // 获取选中的 SKU
    const selectedIdx = modal.querySelector('input[name="selectedSkuForTemplate"]:checked');
    if (!selectedIdx) { showToast('请选择一个 SKU', 'error'); return; }

    const selectedSku = foundSkus[parseInt(selectedIdx.value)];
    if (!selectedSku) { showToast('选择的 SKU 无效', 'error'); return; }

    const newTemplate = {
      id: `customized_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
      templateId: String(selectedSku.templateId || selectedSku.templateIds?.[0] || ''),
      skuId: String(selectedSku.productSkuId),
      spuId: spuId,
      name: name,
      specInfo: selectedSku.specInfo || '',
      templateIds: selectedSku.templateIds || [selectedSku.templateId].filter(Boolean),
      surfaces: Array.isArray(selectedSku.surfaces) ? selectedSku.surfaces : [],
      sourceProductId: String(templateSourceData?.productId || ''),
      sourceGoodsId: String(templateSourceData?.goodsId || ''),
      sourceTemplateSkuId: String(selectedSku.productSkuId || ''),
      createdAt: new Date().toISOString()
    };

    const serverResult = await saveUserTemplate('customized', newTemplate);
    if (!serverResult.success) {
      showToast(`模板保存失败：${serverResult.error || '服务器保存失败'}`, 'error');
      return;
    }
    if (serverResult.id) newTemplate.serverId = serverResult.id;

    customizedState.templates.push(normalizeCustomizedTemplateRecord(newTemplate));
    const mallId = await resolveCurrentMallIdForStorage();
    await safeStorageSet({
      [getCustomizedTemplateKey(mallId)]: customizedState.templates
    }, '定制品模板');

    renderCustomizedTemplates();
    close();
    showToast(`保存成功：模板「${name}」已保存，来源 SKU: ${selectedSku.productSkuId}`, 'success');
  });
}

function getCustomizedDelistTargetSkcIds() {
  const fromInput = document.getElementById('customizedDelistSpuIds')?.value || '';
  const fromChecked = Array.from(document.querySelectorAll('.product-checkbox:checked'))
    .map(cb => String(cb.dataset.skc || cb.dataset.spu || '').trim())
    .filter(Boolean);
  const fromTyped = fromInput
    .split(/[,，\s\n]+/)
    .map(spu => spu.trim())
    .filter(spu => /^\d+$/.test(spu));
  return Array.from(new Set([...fromChecked, ...fromTyped]));
}

async function startCustomizedBatchDelist() {
  const event = arguments[0];
  if (event?.preventDefault) event.preventDefault();
  if (event?.stopPropagation) event.stopPropagation();

  if (customizedState.isDelisting) {
    showToast('批量下架正在执行，请稍候', 'info');
    return;
  }

  const skcIds = getCustomizedDelistTargetSkcIds();
  if (skcIds.length === 0) {
    showToast('请先勾选要下架的商品，或手动输入 SKC', 'error');
    return;
  }

  const preview = skcIds.slice(0, 10).join('、');
  const confirmed = await showCenterConfirm({
    title: '确认批量下架',
    message: `将尝试下架 ${skcIds.length} 个 SKC：\n\n${preview}${skcIds.length > 10 ? ' ...' : ''}`,
    confirmText: '确认下架',
    cancelText: '取消'
  });
  if (!confirmed) {
    return;
  }

  customizedState.isDelisting = true;
  const delistBtn = document.getElementById('customizedDelistBtn');
  const originalText = delistBtn?.textContent || '⛔ 批量下架';
  if (delistBtn) {
    delistBtn.disabled = true;
    delistBtn.textContent = '⏳ 执行中...';
  }

  try {
    const result = await safeRuntimeSendMessage({
      action: 'batchDelistCustomizedSkc',
      params: { skcIds }
    }, { success: false, error: '后台下架请求失败' }, Math.max(45000, skcIds.length * 4000));

    if (result?.success) {
      const failText = result.failCount ? `，失败 ${result.failCount} 个` : '';
      showToast(`已提交下架申请：成功 ${result.successCount || skcIds.length} 个${failText}`, result.failCount ? 'warning' : 'success');
    } else {
      throw new Error(result?.error || '未能完成下架操作');
    }
  } catch (error) {
    showToast(`批量下架失败：${error.message}`, 'error');
  } finally {
    customizedState.isDelisting = false;
    if (delistBtn) {
      delistBtn.disabled = false;
      delistBtn.textContent = originalText;
    }
  }
}
// 开始批量填写定制品信息
async function startCustomizedApply() {
  const event = arguments[0];
  if (event?.preventDefault) event.preventDefault();
  if (event?.stopPropagation) event.stopPropagation();

  if (customizedState.isRunning) { showToast('正在填写中，请稍候', 'info'); return; }

  const templateSelect = document.getElementById('customizedTemplateSelect');
  const selectedTemplateKey = templateSelect?.value || '';
  if (!selectedTemplateKey) { showToast('请先选择一个定制品模板', 'error'); return; }

  const template = customizedState.templates.find(t => t.id === selectedTemplateKey);
  if (!template) { showToast('模板不存在', 'error'); return; }

  const uploadScope = document.getElementById('customizedUploadScope')?.value || 'selected';
  const targetSpuIds = resolveCustomizedTargetSpuIds(uploadScope);
  if (targetSpuIds.length === 0) {
    const hasSyncedProducts = Array.isArray(customizedProductState.allProducts) && customizedProductState.allProducts.length > 0;
    if (uploadScope === 'selected') {
      showToast('请先在左侧勾选商品，或手动输入 SPU ID', 'warning');
    } else if (!hasSyncedProducts) {
      showToast('请先点击「同步商品列表」，再按上传范围批量填写', 'warning');
    } else if (uploadScope === 'pending') {
      showToast('当前没有待处理商品', 'warning');
    } else {
      showToast('当前没有可填写的商品', 'warning');
    }
    return;
  }

  const shouldApplyStock = Boolean(document.getElementById('customizedApplyStock')?.checked);
  const stockInputValue = document.getElementById('customizedStockQuantity')?.value?.trim() || '';
  let stockQuantity = null;
  if (shouldApplyStock) {
    if (stockInputValue === '') {
      showToast('请输入商品库存数量', 'error');
      return;
    }
    stockQuantity = Number(stockInputValue);
    if (!Number.isInteger(stockQuantity) || stockQuantity < 0) {
      showToast('商品库存必须是大于等于 0 的整数', 'error');
      return;
    }
  }

  customizedState.isRunning = true;
  const startBtn = document.getElementById('customizedStartBtn');
  if (startBtn) {
    startBtn.disabled = true;
    startBtn.textContent = '⏳ 填写中...';
  }

  const progressSection = document.getElementById('customizedProgressSection');
  const fill = document.getElementById('customizedProgressFill');
  const text = document.getElementById('customizedProgressText');
  const list = document.getElementById('customizedResultList');
  if (!progressSection || !fill || !text || !list) {
    customizedState.isRunning = false;
    if (startBtn) {
      startBtn.disabled = false;
      startBtn.textContent = '🚀 开始自动填写';
    }
    showToast('定制品填写界面未初始化完成，请刷新插件页后重试', 'error');
    return;
  }
  progressSection.style.display = 'block';
  fill.style.width = '0%'; fill.className = 'progress-fill blue';
  text.textContent = `共 ${targetSpuIds.length} 个商品，开始填写...`;
  list.innerHTML = '';

  let successCount = 0, failCount = 0;
  const delay = parseInt(document.getElementById('uploadDelay')?.value) || 1000;

  try {
    for (let i = 0; i < targetSpuIds.length; i++) {
      const spuId = targetSpuIds[i];
      const pct = Math.round(((i + 1) / targetSpuIds.length) * 100);
      fill.style.width = `${pct}%`;
      text.textContent = `正在处理 SPU ${spuId}（${i + 1}/${targetSpuIds.length}）...`;

      try {
      const selectedTemplateIds = (template.templateIds || [template.templateId].filter(Boolean))
        .map(id => String(id || '').trim())
        .filter(Boolean);
      if (selectedTemplateIds.length === 0) {
        throw new Error('模板中没有可用的 templateId');
      }

      // 提交前由后台实时查询模板 SKU 和目标商品 SKU，避免使用本地缓存导致 TEMU 报 SKU 数据变更。
      const editResult = await safeRuntimeSendMessage({
        action: 'applyCustomizedTemplateToSkus',
        params: {
          productId: spuId,
          templateId: selectedTemplateIds[0],
          templateIds: selectedTemplateIds,
          templateSkuId: template.sourceTemplateSkuId || template.skuId,
          sourceTemplateSkuId: template.sourceTemplateSkuId || template.skuId,
          applyMode: 'all',
          applyStock: shouldApplyStock,
          stockQuantity: shouldApplyStock ? stockQuantity : null
        }
      }, { success: false, error: '后台填写定制品失败' }, 45000);

        if (editResult && editResult.success) {
          const cls = 'success';
          const icon = '✅';
          const appliedCount = Number(editResult.appliedSkuCount || editResult.editCount || 0);
          const msg = `${appliedCount || '目标商品'} 个 SKU 填写成功`;
          const item = document.createElement('div');
          item.className = `result-item ${cls}`;
          item.innerHTML = `<span class="icon">${icon}</span><span class="reason"><strong>SPU ${spuId}</strong> — ${msg}</span>`;
          list.appendChild(item);
          if (editResult.stockError) {
            const stockItem = document.createElement('div');
            stockItem.className = 'result-item warning';
            stockItem.innerHTML = `<span class="icon">⚠️</span><span class="reason"><strong>SPU ${spuId}</strong> — ${escapeHtml(editResult.stockError)}</span>`;
            list.appendChild(stockItem);
          } else if (editResult.stockApplied === true) {
            const stockItem = document.createElement('div');
            stockItem.className = 'result-item success';
            const stockModeText = editResult.stockMode === 'semiManaged' ? '半托管' : editResult.stockMode === 'fullManaged' ? '全托管' : '';
            const noChangeText = editResult.stockNoChange ? '，库存无需修改' : '';
            stockItem.innerHTML = `<span class="icon">✅</span><span class="reason"><strong>SPU ${spuId}</strong> — ${stockModeText ? `${stockModeText} ` : ''}库存 ${escapeHtml(editResult.stockQuantity ?? stockQuantity)} 已同步${noChangeText}</span>`;
            list.appendChild(stockItem);
          } else if (shouldApplyStock) {
            const stockItem = document.createElement('div');
            stockItem.className = 'result-item warning';
            stockItem.innerHTML = `<span class="icon">⚠️</span><span class="reason"><strong>SPU ${spuId}</strong> — 库存接口未返回成功，请在 TEMU 后台核对</span>`;
            list.appendChild(stockItem);
          }
          successCount++;
        } else {
          throw new Error(editResult?.error || '填写失败');
        }
      } catch (err) {
        const item = document.createElement('div');
        item.className = 'result-item fail';
        item.innerHTML = `<span class="icon">❌</span><span class="reason"><strong>SPU ${spuId}</strong> — ${escapeHtml(err.message)}</span>`;
        list.appendChild(item);
        failCount++;
      }

      if (i < targetSpuIds.length - 1) await new Promise(r => setTimeout(r, delay));
    }
  } catch (err) {
    const item = document.createElement('div');
    item.className = 'result-item fail';
    item.innerHTML = `<span class="icon">❌</span><span class="reason"><strong>任务中断</strong> — ${escapeHtml(err.message || String(err))}</span>`;
    list.appendChild(item);
    failCount++;
  } finally {
    fill.style.width = '100%';
    if (failCount === 0) fill.className = 'progress-fill green';
    else if (successCount === 0) fill.className = 'progress-fill red';
    else fill.className = 'progress-fill blue';
    text.textContent = `填写完成！✅ ${successCount} 成功, ❌ ${failCount} 失败`;

    customizedState.isRunning = false;
    if (startBtn) {
      startBtn.disabled = false;
      startBtn.textContent = '🚀 开始自动填写';
    }
    showToast(`定制品信息填写完成！成功: ${successCount}, 失败: ${failCount}`, successCount > 0 ? 'success' : 'error');
  }
}

// ========== 销售数据 Tab ==========
const salesDataState = {
  pageNum: 1,
  pageSize: 20,
  totalPages: 0,
  total: 0,
  products: [],
  viewProducts: null,
  isExporting: false,
  allProducts: [],  // 所有商品（全量数据）
  loading: false,
  lastSyncAt: 0,
  sortBy: 'todayDesc'
};

function resetSalesDataState() {
  salesDataState.mallId = null;
  salesDataState.pageNum = 1;
  salesDataState.totalPages = 0;
  salesDataState.total = 0;
  salesDataState.products = [];
  salesDataState.viewProducts = null;
  salesDataState.allProducts = [];
  salesDataState.loading = false;
  salesDataState.lastSyncAt = 0;
  clearDashboardSalesVisuals();
}

function getSalesCacheKey() {
  return state.mallId ? `temuSalesDataCache_${state.mallId}` : 'temuSalesDataCache';
}

async function saveSalesDataCache() {
  const mallId = state.mallId || salesDataState.mallId || null;
  const cacheProducts = (Array.isArray(salesDataState.allProducts) ? salesDataState.allProducts : [])
    .filter(hasSalesProductSales)
    .map(product => {
      const skuRows = getSalesProductSkuRows(product);
      return {
        mallId,
        productId: product.productId || '',
        productCNName: product.productCNName || product.productName || '',
        productName: product.productName || product.productCNName || '',
        mainImage: product.mainImage || '',
        skcId: product.skcId || '',
        skcExtCode: product.skcExtCode || '',
        onSalesDuration: product.onSalesDuration || 0,
        createTime: product.createTime || '',
        score: product.score ?? '',
        todaySaleVolume: Number(product.todaySaleVolume || 0),
        sevenDaysSaleVolume: Number(product.sevenDaysSaleVolume || 0),
        thirtyDaysSaleVolume: Number(product.thirtyDaysSaleVolume || 0),
        skuQuantityDetailList: skuRows.map(row => ({
          productSkuId: row.skuId,
          className: row.skuName,
          skuExtCode: row.skuCode,
          supplierPrice: row.amountValue !== null && row.amountValue !== undefined ? Math.round(Number(row.amountValue) * 100) : null,
          todaySaleVolume: Number(row.todaySaleVolume || 0),
          lastSevenDaysSaleVolume: Number(row.sevenDaysSaleVolume || 0),
          lastThirtyDaysSaleVolume: Number(row.thirtyDaysSaleVolume || 0),
          availableSaleDays: row.availableSaleDays,
          stockDays: row.stockDays,
          adviceQuantity: row.adviceQuantity,
          purchaseConfig: row.purchaseConfig,
          warehouseAvailableSaleDays: row.availableSaleDays,
          shippedInventoryNum: Number(row.shippedInventoryNum || 0),
          inventoryNumInfo: {
            warehouseInventoryNum: Number(row.availableInventoryNum || 0),
            expectedOccupiedInventoryNum: Number(row.reservedInventoryNum || 0),
            unavailableWarehouseInventoryNum: Number(row.unavailableInventoryNum || 0),
            shippedInventoryNum: Number(row.shippedInventoryNum || 0)
          },
          warehouseInfoList: []
        }))
      };
    });
  const payload = {
    mallId,
    products: cacheProducts,
    total: cacheProducts.length,
    sourceTotal: cacheProducts.length,
    pageSize: salesDataState.pageSize,
    lastSyncAt: salesDataState.lastSyncAt,
    syncedAt: salesDataState.lastSyncAt,
    sortBy: salesDataState.sortBy || 'todayDesc'
  };
  const legacyKey = mallId ? `temuSalesCache_${mallId}` : 'temuSalesCache';
  const modernKey = mallId ? `temuSalesDataCache_${mallId}` : getSalesCacheKey();
  await chrome.storage.local.remove([modernKey]);
  const saved = await safeStorageSet({ [legacyKey]: payload }, '销量缓存');
  if (!saved) {
    await safeStorageSet({
      [legacyKey]: {
        ...payload,
        products: cacheProducts.slice(0, 200),
        total: Math.min(cacheProducts.length, 200),
        sourceTotal: cacheProducts.length
      }
    }, '销量缓存摘要');
  }
}

async function loadSalesDataCache() {
  const stored = await chrome.storage.local.get(null);
  const currentMallId = state.mallId || stored?.temuAuth?.mallId || stored?.temuLocalMallId || null;
  const candidateMallIds = currentMallId ? [currentMallId] : [];
  const candidateKeys = currentMallId
    ? [`temuSalesDataCache_${currentMallId}`, `temuSalesCache_${currentMallId}`]
    : [getSalesCacheKey(), 'temuSalesCache'];

  let cache = null;
  for (const key of candidateKeys) {
    const current = stored?.[key];
    if (current && Array.isArray(current.products) && current.products.length > 0) {
      cache = current;
      break;
    }
  }

  if (!cache && !currentMallId) {
    const latest = Object.entries(stored || {})
      .filter(([key, value]) => (/^temuSales(Data)?Cache(_|$)/.test(key)) && Array.isArray(value?.products) && value.products.length > 0)
      .sort((a, b) => Number(b[1]?.lastSyncAt || b[1]?.syncedAt || 0) - Number(a[1]?.lastSyncAt || a[1]?.syncedAt || 0))[0];
    cache = latest?.[1] || null;
  }

  if (!cache || !Array.isArray(cache.products) || cache.products.length === 0) {
    resetSalesDataState();
    updateSalesSyncDisplay();
    renderSalesDataTable();
    return false;
  }
  salesDataState.mallId = cache.mallId || candidateMallIds[0] || null;
  salesDataState.allProducts = cache.products;
  salesDataState.products = cache.products;
  salesDataState.total = cache.total || cache.sourceTotal || cache.products.length;
  salesDataState.pageSize = cache.pageSize || salesDataState.pageSize;
  salesDataState.lastSyncAt = cache.lastSyncAt || cache.syncedAt || 0;
  salesDataState.sortBy = cache.sortBy || 'todayDesc';
  salesDataState.pageNum = 1;
  sortSalesProducts();
  applySalesSearchFilter();
  updateSalesSyncDisplay();
  const sortEl = document.getElementById('salesSortSelect');
  if (sortEl) sortEl.value = salesDataState.sortBy;
  const pageSizeEl = document.getElementById('salesPageSizeSelect');
  if (pageSizeEl) pageSizeEl.value = String(salesDataState.pageSize);
  return true;
}

// 后台全量加载所有页面，更新仪表盘汇总（照抄大白鹅：逐页加载汇总）
async function loadAllPagesForDashboard(requestParams) {
  // 如果有搜索条件（搜SPU/名称），不需要全量加载
  if (requestParams.productSpuId || requestParams.productName) return;

  const total = salesDataState.total || 0;
  // 少于50条不需要全量加载，当前页就是全部
  if (total <= 50) return;

  // 显示加载提示
  const hint = document.getElementById('salesAllLoadingHint');
  if (hint) hint.style.display = 'block';

  try {
    const result = await safeRuntimeSendMessage({
      action: 'fetchSalesDataAll',
      params: { pageSize: 40 }  // 大白鹅用40
    }, { success: false, error: '全量销量数据加载失败' }, 60000);

    if (result?.success && result.products?.length > 0) {
      salesDataState.allProducts = result.products;
      updateSalesDashboardAll(result.products);
      // 同时更新柱状图（用全量数据）
      renderSalesBarChart(result.products);
      if (getSalesSearchQuery()) {
        applySalesSearchFilter();
      }
    }
  } catch (err) {
    console.error('[小青蛙电商助手] 全量加载失败:', err);
  } finally {
    if (hint) hint.style.display = 'none';
  }
}

// 用全量数据更新仪表盘汇总
function updateSalesDashboardAll(products) {
  if (!products || products.length === 0) return;

  let todayTotal = 0, sevenDayTotal = 0, thirtyDayTotal = 0;
  let todayAmount = 0, sevenDayAmount = 0, thirtyDayAmount = 0;
  products.forEach(p => {
    const amountSummary = getSalesProductAmountSummary(p);
    todayTotal += p.todaySaleVolume || 0;
    sevenDayTotal += p.sevenDaysSaleVolume || 0;
    thirtyDayTotal += p.thirtyDaysSaleVolume || 0;
    todayAmount += amountSummary.todayAmount || 0;
    sevenDayAmount += amountSummary.sevenDayAmount || 0;
    thirtyDayAmount += amountSummary.thirtyDayAmount || 0;
  });

  // 更新销量卡片（销量查询 Tab）
  const todayEl = document.getElementById('salesTodayCount');
  const sevenEl = document.getElementById('salesSevenDayCount');
  const thirtyEl = document.getElementById('salesThirtyDayCount');
  if (todayEl) todayEl.textContent = todayTotal.toLocaleString();
  if (sevenEl) sevenEl.textContent = sevenDayTotal.toLocaleString();
  if (thirtyEl) thirtyEl.textContent = thirtyDayTotal.toLocaleString();

  // 更新销售额（销量查询 Tab）
  const todayAmtEl = document.getElementById('salesTodayAmount');
  const sevenAmtEl = document.getElementById('salesSevenDayAmount');
  const thirtyAmtEl = document.getElementById('salesThirtyDayAmount');
  if (todayAmtEl) todayAmtEl.textContent = '\u00a5' + formatAmount(todayAmount);
  if (sevenAmtEl) sevenAmtEl.textContent = '\u00a5' + formatAmount(sevenDayAmount);
  if (thirtyAmtEl) thirtyAmtEl.textContent = '\u00a5' + formatAmount(thirtyDayAmount);

  // 更新独立面板（销量查询 Tab）
  const todayAmtStand = document.getElementById('salesTodayAmountStandalone');
  const sevenAmtStand = document.getElementById('salesSevenDayAmountStandalone');
  const thirtyAmtStand = document.getElementById('salesThirtyDayAmountStandalone');
  if (todayAmtStand) todayAmtStand.textContent = '¥' + formatAmount(todayAmount);
  if (sevenAmtStand) sevenAmtStand.textContent = '¥' + formatAmount(sevenDayAmount);
  if (thirtyAmtStand) thirtyAmtStand.textContent = '¥' + formatAmount(thirtyDayAmount);

  // 首页概览以 TEMU 后台首页实时指标为准，销量查询 Tab 只更新本 Tab 的汇总。
  refreshDashboardHomeSales().catch(error => {
    console.warn('[小青蛙电商助手] 销量查询后刷新首页实时销量失败:', error?.message || error);
  });
  // 30天数据需要点击刷新才显示，全量加载后解锁
  unlockDashboardThirty(thirtyDayTotal, thirtyDayAmount);
}

// 更新销售统计卡片（当前页数据）
function updateSalesDashboard() {
  const products = salesDataState.products;
  if (!products || products.length === 0) return;

  // 销量汇总
  let todayTotal = 0, sevenDayTotal = 0, thirtyDayTotal = 0;
  // 销售额汇总（单价 * 销量）
  let todayAmount = 0, sevenDayAmount = 0, thirtyDayAmount = 0;
  products.forEach(p => {
    const amountSummary = getSalesProductAmountSummary(p);
    todayTotal += p.todaySaleVolume || 0;
    sevenDayTotal += p.sevenDaysSaleVolume || 0;
    thirtyDayTotal += p.thirtyDaysSaleVolume || 0;
    todayAmount += amountSummary.todayAmount || 0;
    sevenDayAmount += amountSummary.sevenDayAmount || 0;
    thirtyDayAmount += amountSummary.thirtyDayAmount || 0;
  });

  // 更新销量卡片
  const todayEl = document.getElementById('salesTodayCount');
  const sevenEl = document.getElementById('salesSevenDayCount');
  const thirtyEl = document.getElementById('salesThirtyDayCount');

  if (todayEl) todayEl.textContent = todayTotal.toLocaleString();
  if (sevenEl) sevenEl.textContent = sevenDayTotal.toLocaleString();
  if (thirtyEl) thirtyEl.textContent = thirtyDayTotal.toLocaleString();

  // 更新销售额（卡片内）
  const todayAmtEl = document.getElementById('salesTodayAmount');
  const sevenAmtEl = document.getElementById('salesSevenDayAmount');
  const thirtyAmtEl = document.getElementById('salesThirtyDayAmount');
  if (todayAmtEl) todayAmtEl.textContent = '\u00a5' + formatAmount(todayAmount);
  if (sevenAmtEl) sevenAmtEl.textContent = '\u00a5' + formatAmount(sevenDayAmount);
  if (thirtyAmtEl) thirtyAmtEl.textContent = '\u00a5' + formatAmount(thirtyDayAmount);

  // 更新销售额独立面板
  const todayAmtStand = document.getElementById('salesTodayAmountStandalone');
  const sevenAmtStand = document.getElementById('salesSevenDayAmountStandalone');
  const thirtyAmtStand = document.getElementById('salesThirtyDayAmountStandalone');
  if (todayAmtStand) todayAmtStand.textContent = '¥' + formatAmount(todayAmount);
  if (sevenAmtStand) sevenAmtStand.textContent = '¥' + formatAmount(sevenDayAmount);
  if (thirtyAmtStand) thirtyAmtStand.textContent = '¥' + formatAmount(thirtyDayAmount);

  // 渲染柱状图
  renderSalesBarChart(products);
  unlockDashboardThirty(thirtyDayTotal, thirtyDayAmount);

}
function formatAmount(amount) {
  if (!amount || amount === 0) return '0.00';
  const abs = Math.abs(amount);
  if (abs >= 10000) return (amount / 10000).toFixed(2) + '\u4e07';
  return amount.toFixed(2);
}

function normalizeSalesText(value) {
  if (value === null || value === undefined) return '';
  const text = String(value).trim();
  return text;
}

function normalizeSalesNumber(value) {
  if (value === null || value === undefined || value === '') return null;
  if (typeof value === 'number') {
    return Number.isFinite(value) ? value : null;
  }
  const text = String(value).trim();
  if (!text) return null;
  const cleaned = text.replace(/,/g, '').replace(/[^\d.-]/g, '');
  if (!cleaned || cleaned === '-' || cleaned === '.' || cleaned === '-.') return null;
  const num = Number(cleaned);
  return Number.isFinite(num) ? num : null;
}

function pickSalesField(source, fields) {
  if (!source || !fields) return '';
  for (const field of fields) {
    const value = source[field];
    if (value !== null && value !== undefined && String(value).trim() !== '') {
      return value;
    }
  }
  return '';
}

function parseSalesTimestamp(value) {
  if (value === null || value === undefined || value === '') return null;
  if (value instanceof Date) {
    return Number.isNaN(value.getTime()) ? null : value;
  }
  if (typeof value === 'number') {
    const ms = value < 1e12 ? value * 1000 : value;
    const date = new Date(ms);
    return Number.isNaN(date.getTime()) ? null : date;
  }

  const text = String(value).trim();
  if (!text) return null;
  if (/^\d{10}$/.test(text)) {
    const date = new Date(Number(text) * 1000);
    return Number.isNaN(date.getTime()) ? null : date;
  }
  if (/^\d{13}$/.test(text)) {
    const date = new Date(Number(text));
    return Number.isNaN(date.getTime()) ? null : date;
  }

  const date = new Date(text);
  return Number.isNaN(date.getTime()) ? null : date;
}

function getSalesDurationSeconds(product = {}) {
  const normalizeDurationSeconds = (value) => {
    const num = normalizeSalesNumber(value);
    if (num === null || num < 0) return null;
    // TEMU 销量接口里该字段常直接返回“天数”
    if (num > 0 && num < 1000) return num * 86400;
    return num;
  };
  const numericFields = [
    'onSalesDuration',
    'onSalesDurationOffline',
    'on_sales_duration',
    'onlineDuration',
    'inShopDuration',
    'joinSiteDuration',
    'siteDuration',
    'duration',
    'shelfDuration',
    'shopDuration'
  ];

  for (const field of numericFields) {
    const num = normalizeDurationSeconds(product[field]);
    if (num !== null && num >= 0) return num;
  }

  const dateFields = [
    'createTime',
    'createDate',
    'publishTime',
    'joinTime',
    'addTime',
    'firstOnlineTime',
    'shelveTime',
    'onlineTime',
    'onShelvesTime'
  ];
  const now = Date.now();
  for (const field of dateFields) {
    const date = parseSalesTimestamp(product[field]);
    if (!date) continue;
    const seconds = Math.floor((now - date.getTime()) / 1000);
    if (seconds >= 0) return seconds;
  }
  return 0;
}

function getSalesSearchQuery() {
  return document.getElementById('salesTableSearchInput')?.value?.trim() || '';
}

function getSalesSkuId(sku = {}) {
  return normalizeSalesText(pickSalesField(sku, [
    'skuId',
    'productSkuId',
    'goodsSkuId',
    'id',
    'sku_id',
    'skuCodeId'
  ]));
}

function getSalesSkuName(sku = {}) {
  return normalizeSalesText(pickSalesField(sku, [
    'className',
    'class_name',
    'skuClassName',
    'sku_class_name',
    'skuDisplayName',
    'skuName',
    'skuSpecText',
    'specText',
    'specification',
    'propValue'
  ]));
}

function getSalesSkuCode(sku = {}) {
  return normalizeSalesText(pickSalesField(sku, [
    'skuCode',
    'sku_code',
    'productSkuCode',
    'goodsSkuCode',
    'skuNo',
    'skuNoText',
    'itemNo',
    'code'
  ]));
}

function getSalesSkuAmountValue(sku = {}) {
  const fields = [
    'supplierPrice',
    'skuPrice',
    'priceValue',
    'declaredPrice',
    'amount',
    'price',
    'salesPrice',
    'salePrice',
    'currentPrice',
    'unitPrice',
    'sellingPrice',
    'retailPrice',
    'marketPrice',
    'goodsPrice',
    'productPrice',
    'minPrice',
    'maxPrice',
    'linePrice',
    'sale_price',
    'current_price',
    'line_price',
    'min_price'
  ];
  const centBasedFields = new Set([
    'supplierPrice',
    'skuPrice',
    'priceValue',
    'declaredPrice',
    'salesPrice',
    'salePrice',
    'currentPrice',
    'unitPrice',
    'sellingPrice',
    'retailPrice',
    'marketPrice',
    'goodsPrice',
    'productPrice',
    'minPrice',
    'maxPrice',
    'linePrice',
    'sale_price',
    'current_price',
    'line_price',
    'min_price'
  ]);

  for (const field of fields) {
    const raw = sku?.[field];
    const num = normalizeSalesNumber(raw);
    if (num === null || num <= 0) continue;
    if (centBasedFields.has(field) && num >= 100) return num / 100;
    return num;
  }
  return null;
}

function hasSalesSkuAmount(sku = {}) {
  if (sku && typeof sku === 'object' && sku.amountValue !== undefined && sku.amountValue !== null) {
    return Number(sku.amountValue) > 0;
  }
  return getSalesSkuAmountValue(sku) !== null;
}

function getSalesSkuWarehouseList(sku = {}) {
  const list = [];
  const sources = [
    sku.warehouseInfoList,
    sku.warehouseList,
    sku.stockList,
    sku.inventoryList,
    sku.warehouseInventoryList,
    sku.inventoryInfoList,
    sku.warehouseInventoryInfoList,
    sku.stockInfoList
  ];

  sources.forEach(source => {
    if (!Array.isArray(source)) return;
    source.forEach(item => {
      if (item && typeof item === 'object') list.push(item);
    });
  });

  return list;
}

function getSalesInventorySummary(sku = {}) {
  const warehouses = getSalesSkuWarehouseList(sku);
  const directValues = source => normalizeSalesNumber(pickSalesField(sku, source)) || 0;
  const warehouseValues = source => warehouses.reduce((sum, warehouse) => {
    const data = warehouse && typeof warehouse === 'object' && warehouse.inventoryNumInfo && typeof warehouse.inventoryNumInfo === 'object'
      ? warehouse.inventoryNumInfo
      : warehouse;
    const value = normalizeSalesNumber(pickSalesField(data, source));
    return sum + (value || 0);
  }, 0);
  const sumOrDirect = (warehouseFields, directFields) => {
    const total = warehouseValues(warehouseFields);
    return total || directValues(directFields);
  };

  return {
    availableInventoryNum: sumOrDirect([
      'warehouseInventoryNum',
      'availableInventoryNum',
      'availableNum',
      'stockQuantity'
    ], [
      'availableInventoryNum',
      'inWarehouseAvailableInventoryNum',
      'warehouseInventoryNum',
      'stockQuantity'
    ]),
    reservedInventoryNum: sumOrDirect([
      'expectedOccupiedInventoryNum',
      'preOccupyInventoryNum',
      'reservedInventoryNum',
      'inWarehouseReservedInventoryNum',
      'waitDeliveryInventoryNum'
    ], [
      'reservedInventoryNum',
      'inWarehouseReservedInventoryNum',
      'preOccupyInventoryNum',
      'expectedOccupiedInventoryNum',
      'waitDeliveryInventoryNum'
    ]),
    unavailableInventoryNum: sumOrDirect([
      'unavailableWarehouseInventoryNum',
      'inWarehouseUnavailableInventoryNum',
      'waitQcNum',
      'waitOnShelfNum',
      'waitInStock',
      'waitReceiveNum',
      'waitApproveInventoryNum'
    ], [
      'unavailableInventoryNum',
      'inWarehouseUnavailableInventoryNum',
      'unavailableWarehouseInventoryNum'
    ]),
    shippedInventoryNum: sumOrDirect([
      'shippedInventoryNum',
      'shippedNum',
      'sentInventoryNum',
      'deliveredInventoryNum'
    ], [
      'shippedInventoryNum',
      'shippedNum',
      'sentInventoryNum',
      'deliveredInventoryNum'
    ])
  };
}

function normalizeSalesWarehouseName(name, index) {
  const text = normalizeSalesText(name);
  if (!text) return '';
  if (text === '仓库1' || text === '仓库 1') return '';
  if (/^仓库\s*1$/.test(text)) return '';
  return text;
}

function getSalesInventoryHtml(sku = {}) {
  const summary = getSalesInventorySummary(sku);
  const warehouses = getSalesSkuWarehouseList(sku);
  const formatCard = (label, value, bg, color) => `
    <div style="padding:8px 10px;border-radius:10px;background:${bg};border:1px solid rgba(148,163,184,0.18);min-width:0;">
      <div style="font-size:11px;line-height:1.2;color:#64748b;margin-bottom:4px;">${label}</div>
      <div style="font-size:14px;line-height:1.2;font-weight:800;color:${color};word-break:break-all;">${Number(value || 0).toLocaleString()}</div>
    </div>
  `;
  return `
    <div style="margin-top:8px;padding:10px 10px 9px;border-radius:12px;border:1px solid #e8edf5;background:linear-gradient(180deg,#fbfdff 0%,#f8fafc 100%);box-shadow:0 1px 2px rgba(15,23,42,0.03);">
      <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
        <div style="font-size:11px;font-weight:700;color:#475569;">库存数据</div>
        <div style="font-size:11px;color:#94a3b8;">${warehouses.length ? `${warehouses.length} 个仓库明细` : '暂无仓库明细'}</div>
      </div>
      <div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;">
        ${formatCard('仓内可用库存', summary.availableInventoryNum, '#eff6ff', '#1d4ed8')}
        ${formatCard('仓内预占用库存', summary.reservedInventoryNum, '#fff7ed', '#c2410c')}
        ${formatCard('仓内暂不可用库存', summary.unavailableInventoryNum, '#fdf2f8', '#be185d')}
        ${formatCard('已发货库存', summary.shippedInventoryNum, '#ecfdf5', '#047857')}
      </div>
    </div>
  `;
}

function normalizeSalesSkuRow(sku = {}, fallbackIndex = 0) {
  const skuId = getSalesSkuId(sku);
  const skuName = getSalesSkuName(sku);
  const skuCode = getSalesSkuCode(sku);
  const amountValue = getSalesSkuAmountValue(sku);
  const inventorySummary = getSalesInventorySummary(sku);

  const todaySaleVolume = normalizeSalesNumber(pickSalesField(sku, [
    'todaySaleVolume',
    'todaySalesVolume',
    'todaySales',
    'todaySale',
    'todayPlatformPurchaseNum',
    'todayPlatformSalesNum',
    'dailySaleVolume',
    'saleVolumeToday'
  ])) || 0;
  const sevenDaysSaleVolume = normalizeSalesNumber(pickSalesField(sku, [
    'lastSevenDaysSaleVolume',
    'sevenDaysSaleVolume',
    'sevenDaySaleVolume',
    'lastSevenDaysSale',
    'last7DaysSaleVolume',
    'sevenDaysSalesVolume',
    'recent7DaysSaleVolume',
    'sevenSalesVolume',
    'weekSaleVolume'
  ])) || 0;
  const thirtyDaysSaleVolume = normalizeSalesNumber(pickSalesField(sku, [
    'lastThirtyDaysSaleVolume',
    'thirtyDaysSaleVolume',
    'thirtyDaySaleVolume',
    'lastThirtyDaysSale',
    'last30DaysSaleVolume',
    'recent30DaysSaleVolume',
    'thirtyDaysSalesVolume',
    'monthSaleVolume'
  ])) || 0;

  const warehouses = getSalesSkuWarehouseList(sku);
  const stockFromWarehouses = warehouses.reduce((sum, warehouse) => {
    const qty = normalizeSalesNumber(pickSalesField(warehouse, [
      'warehouseInventoryNum',
      'inventoryNum',
      'quantity',
      'stockQuantity'
    ]));
    return sum + (qty || 0);
  }, 0);

  const stockQuantity = stockFromWarehouses || normalizeSalesNumber(pickSalesField(sku, [
    'stockQuantity',
    'inventory',
    'stock',
    'warehouseInventoryNum'
  ])) || 0;

  const availableSaleDays = normalizeSalesNumber(pickSalesField(sku, [
    'availableSaleDays',
    'availableSaleDaysFromInventory',
    'warehouseAvailableSaleDays',
    'predictSaleAvailableDays',
    'predictSaleInventoryAvailableDays',
    'predictSaleWarehouseAvailableDays',
    'safeInventoryDays',
    'stockDays'
  ]));
  const stockDays = normalizeSalesNumber(pickSalesField(sku, [
    'stockDays',
    'safeInventoryDays',
    'availableSaleDaysFromInventory',
    'warehouseAvailableSaleDays'
  ]));
  const adviceQuantity = normalizeSalesNumber(pickSalesField(sku, [
    'adviceQuantity',
    'predictSaleAdviceQuantity',
    'adviceProduceNum',
    'availableProduceNum',
    'lackQuantity'
  ]));
  const purchaseConfig = normalizeSalesText(pickSalesField(sku, [
    'purchaseConfig',
    'purchaseLabel'
  ]));

  return {
    raw: sku,
    skuId: skuId || `sku-${fallbackIndex + 1}`,
    skuName: skuName || skuCode || `SKU ${fallbackIndex + 1}`,
    skuClassName: skuName,
    skuCode,
    amountValue,
    todaySaleVolume,
    sevenDaysSaleVolume,
    thirtyDaysSaleVolume,
    availableSaleDays,
    stockDays,
    adviceQuantity,
    purchaseConfig,
    stockQuantity: stockQuantity || inventorySummary.availableInventoryNum || inventorySummary.reservedInventoryNum || inventorySummary.unavailableInventoryNum || inventorySummary.shippedInventoryNum || 0,
    ...inventorySummary,
    warehouseInfoList: warehouses
  };
}

function getSalesProductSkuRows(product = {}) {
  const sources = [
    product.skuQuantityDetailList,
    product.skuList,
    product.skuListInfo,
    product.skuInfoList,
    product.skuDetails
  ];

  const rows = [];
  const seen = new Set();

  sources.forEach(source => {
    if (!Array.isArray(source)) return;
    source.forEach((item, index) => {
      if (!item || typeof item !== 'object') return;
      const row = normalizeSalesSkuRow(item, rows.length + index);
      const dedupeKey = [row.skuId, row.skuName, row.amountValue, row.todaySaleVolume, row.sevenDaysSaleVolume, row.thirtyDaysSaleVolume].join('|');
      if (seen.has(dedupeKey)) return;
      seen.add(dedupeKey);
      rows.push(row);
    });
  });

  return rows;
}

function getSalesVisibleSkuRows(product = {}) {
  return getSalesProductSkuRows(product)
    .filter(row => row.amountValue !== null && row.amountValue !== undefined && row.amountValue > 0)
    .sort((a, b) => {
      const salesA = (a.todaySaleVolume || 0) + (a.sevenDaysSaleVolume || 0) + (a.thirtyDaysSaleVolume || 0);
      const salesB = (b.todaySaleVolume || 0) + (b.sevenDaysSaleVolume || 0) + (b.thirtyDaysSaleVolume || 0);
      if (salesB !== salesA) return salesB - salesA;
      return (b.amountValue || 0) - (a.amountValue || 0);
    });
}

function getSalesProductSortMetric(product = {}) {
  const skuRows = getSalesProductSkuRows(product);
  const sumBy = field => skuRows.reduce((sum, row) => sum + Number(row[field] || 0), 0);
  const today = Math.max(Number(product.todaySaleVolume || 0), sumBy('todaySaleVolume'));
  const seven = Math.max(Number(product.sevenDaysSaleVolume || 0), sumBy('sevenDaysSaleVolume'));
  const thirty = Math.max(Number(product.thirtyDaysSaleVolume || 0), sumBy('thirtyDaysSaleVolume'));
  return { today, seven, thirty };
}

function getSalesProductAmountSummary(product = {}) {
  const skuRows = getSalesProductSkuRows(product);
  const sumByPeriod = (field) => skuRows.reduce((sum, row) => {
    const unitPrice = Number(row.amountValue || 0);
    const volume = Number(row[field] || 0);
    return sum + (unitPrice > 0 && volume > 0 ? unitPrice * volume : 0);
  }, 0);
  const directTodayAmount = normalizeSalesNumber(pickSalesField(product, [
    'todaySaleAmount',
    'todaySalesAmount',
    'dailySaleAmount',
    'saleAmountToday',
    'todayTurnover',
    'todayGmv',
    'todayRevenue'
  ])) || 0;
  const directSevenAmount = normalizeSalesNumber(pickSalesField(product, [
    'sevenDaysSaleAmount',
    'sevenDaySaleAmount',
    'lastSevenDaysSaleAmount',
    'last7DaysSaleAmount',
    'sevenDayAmount',
    'sevenTurnover',
    'sevenGmv',
    'sevenRevenue'
  ])) || 0;
  const directThirtyAmount = normalizeSalesNumber(pickSalesField(product, [
    'thirtyDaysSaleAmount',
    'thirtyDaySaleAmount',
    'lastThirtyDaysSaleAmount',
    'last30DaysSaleAmount',
    'thirtyDayAmount',
    'thirtyTurnover',
    'thirtyGmv',
    'thirtyRevenue'
  ])) || 0;
  const fallbackUnitPrice = getSalesSkuAmountValue(product) || 0;
  const fallbackToday = fallbackUnitPrice > 0 ? fallbackUnitPrice * Number(product.todaySaleVolume || 0) : 0;
  const fallbackSeven = fallbackUnitPrice > 0 ? fallbackUnitPrice * Number(product.sevenDaysSaleVolume || 0) : 0;
  const fallbackThirty = fallbackUnitPrice > 0 ? fallbackUnitPrice * Number(product.thirtyDaysSaleVolume || 0) : 0;
  return {
    todayAmount: Math.max(sumByPeriod('todaySaleVolume'), fallbackToday, directTodayAmount),
    sevenDayAmount: Math.max(sumByPeriod('sevenDaysSaleVolume'), fallbackSeven, directSevenAmount),
    thirtyDayAmount: Math.max(sumByPeriod('thirtyDaysSaleVolume'), fallbackThirty, directThirtyAmount)
  };
}

function sortSalesProducts() {
  const source = Array.isArray(salesDataState.allProducts) ? [...salesDataState.allProducts] : [];
  const sortBy = salesDataState.sortBy || 'todayDesc';
  source.sort((a, b) => {
    const sa = getSalesProductSortMetric(a);
    const sb = getSalesProductSortMetric(b);
    if (sortBy === 'todayAsc') return sa.today - sb.today || sa.seven - sb.seven || sa.thirty - sb.thirty;
    if (sortBy === 'sevenDesc') return sb.seven - sa.seven || sb.today - sa.today || sb.thirty - sa.thirty;
    if (sortBy === 'sevenAsc') return sa.seven - sb.seven || sa.today - sb.today || sa.thirty - sb.thirty;
    if (sortBy === 'thirtyDesc') return sb.thirty - sa.thirty || sb.seven - sa.seven || sb.today - sa.today;
    if (sortBy === 'thirtyAsc') return sa.thirty - sb.thirty || sa.seven - sb.seven || sa.today - sb.today;
    if (sortBy === 'default') return 0;
    return sb.today - sa.today || sb.seven - sa.seven || sb.thirty - sa.thirty;
  });
  salesDataState.allProducts = source;
  salesDataState.products = source;
}

function hasSalesProductSales(product = {}) {
  if (Number(product.todaySaleVolume || 0) > 0) return true;
  if (Number(product.sevenDaysSaleVolume || 0) > 0) return true;
  if (Number(product.thirtyDaysSaleVolume || 0) > 0) return true;
  return getSalesProductSkuRows(product).some(row =>
    Number(row.todaySaleVolume || 0) > 0
    || Number(row.sevenDaysSaleVolume || 0) > 0
    || Number(row.thirtyDaysSaleVolume || 0) > 0
  );
}

function formatSalesSyncTime(value) {
  if (!value) return '—';
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return '—';
  const pad = n => String(n).padStart(2, '0');
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
}

function updateSalesSyncDisplay() {
  const el = document.getElementById('salesLastSyncTime');
  if (!el) return;
  el.textContent = `上次同步时间：${formatSalesSyncTime(salesDataState.lastSyncAt)}`;
}

async function getSalesAuth() {
  if (state.mallId && state.antiContent) {
    const auth = normalizeTemuAuth({ mallId: state.mallId, antiContent: state.antiContent });
    state.mallId = auth.mallId;
    state.antiContent = auth.antiContent;
    return auth;
  }
  const stored = await chrome.storage.local.get('temuAuth');
  const auth = normalizeTemuAuth(stored?.temuAuth || {});
  if (auth.mallId && auth.antiContent) return auth;
  return null;
}

function buildSalesProductFromApi(item = {}) {
  const product = {
    productId: item.productId || item.product_id || item.goodsId || item.goods_id || item.spuId || '',
    productCNName: item.productCNName || item.productName || item.product_name || item.goodsName || item.name || '',
    productName: item.productName || item.productCNName || item.goodsName || item.name || '',
    mainImage: item.productSkcPicture || item.goodsImg || item.mainImageUrl || item.mainImage || item.imageUrl || '',
    skcId: item.productSkcId || item.skcId || item.goodsSkcId || '',
    skcExtCode: item.skcExtCode || item.skcCode || item.productSkcCode || '',
    onSalesDuration: item.onSalesDurationOffline || item.onSalesDuration || item.on_sales_duration || item.productCycleDays || item.productDays || 0,
    createTime: item.createTime || item.createDate || item.publishTime || item.joinTime || item.addTime || item.firstOnlineTime || item.shelveTime || '',
    score: item.score ?? item.asfScore ?? item.goodsScore ?? item.productDsrScore ?? item.rating ?? item.reviewScore ?? item.starRating ?? item.scoreValue ?? item.mallDeliverRate ?? item.qualityAfterSalesRate ?? '',
    skuList: Array.isArray(item.skuList) ? item.skuList : [],
    skuQuantityDetailList: Array.isArray(item.skuQuantityDetailList) && item.skuQuantityDetailList.length > 0
      ? item.skuQuantityDetailList
      : (Array.isArray(item.skuQuantityTotalInfo?.skuQuantityDetailList) ? item.skuQuantityTotalInfo.skuQuantityDetailList : []),
    warehouseInfoList: Array.isArray(item.warehouseInfoList) ? item.warehouseInfoList : [],
    raw: item
  };
  const skuRows = getSalesProductSkuRows(product);
  const sumBy = field => skuRows.reduce((sum, row) => sum + Number(row[field] || 0), 0);
  product.todaySaleVolume = Math.max(sumBy('todaySaleVolume'), normalizeSalesNumber(pickSalesField(item, [
    'todaySaleVolume',
    'todaySale',
    'todayPlatformPurchaseNum',
    'todayPlatformSalesNum',
    'saleVolumeToday',
    'todaySalesVolume',
    'todayOrderNum',
    'todaySales'
  ])) || 0);
  product.sevenDaysSaleVolume = Math.max(sumBy('sevenDaysSaleVolume'), normalizeSalesNumber(pickSalesField(item, [
    'lastSevenDaysSaleVolume',
    'sevenDaysSaleVolume',
    'lastSevenDaysSale',
    'sevenDaySaleVolume',
    'last7DaysSaleVolume',
    'sevenDaysSalesVolume',
    'weekSaleVolume',
    'recent7DaysSaleVolume'
  ])) || 0);
  product.thirtyDaysSaleVolume = Math.max(sumBy('thirtyDaysSaleVolume'), normalizeSalesNumber(pickSalesField(item, [
    'lastThirtyDaysSaleVolume',
    'thirtyDaysSaleVolume',
    'lastThirtyDaysSale',
    'thirtyDaySaleVolume',
    'last30DaysSaleVolume',
    'thirtyDaysSalesVolume',
    'monthSaleVolume',
    'recent30DaysSaleVolume'
  ])) || 0);
  return product;
}

async function fetchSalesDataAllDirect() {
  const auth = await getSalesAuth();
  if (!auth?.mallId || !auth?.antiContent) {
    throw new Error('请先登录 TEMU 店铺并完成认证');
  }

  const pageSize = 100;
  const backgroundResult = await safeRuntimeSendMessage({
    action: 'fetchSalesDataAll',
    params: {
      mallId: auth.mallId,
      antiContent: auth.antiContent,
      pageSize,
      maxPages: 80
    }
  }, null, 70000);

  if (backgroundResult?.success) {
    return {
      success: true,
      products: Array.isArray(backgroundResult.products) ? backgroundResult.products : [],
      total: backgroundResult.total || backgroundResult.products?.length || 0,
      totalPages: backgroundResult.totalPages || 1
    };
  }

  const backgroundError = backgroundResult?.error || backgroundResult?.message || '';
  const products = [];
  let pageNo = 1;
  let total = 0;
  let totalPages = 1;
  while (pageNo <= totalPages && pageNo <= 80) {
    let response;
    try {
      response = await fetch('https://agentseller.temu.com/mms/venom/api/supplier/sales/management/listWarehouse', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          mallid: String(auth.mallId),
          'anti-content': auth.antiContent
        },
        credentials: 'include',
        body: JSON.stringify({
          pageNo,
          pageSize,
          priceAdjustRecentDays: 7
        })
      });
    } catch (error) {
      if (isExtensionContextInvalidatedError(backgroundError)) {
        throw new Error(getRuntimeReconnectMessage('销量查询'));
      }
      throw new Error(backgroundError || error?.message || '销量查询请求失败，请确认执行插件已连接 TEMU 店铺');
    }

    const text = await response.text();
    let data = {};
    try {
      data = text ? JSON.parse(text) : {};
    } catch (error) {
      throw new Error(backgroundError || '销量查询接口返回异常，请刷新 TEMU 后台并重新连接店铺');
    }
    if (!response.ok || !data?.success || !data?.result) {
      throw new Error(data?.error_msg || data?.message || backgroundError || `销量查询接口请求失败 ${response.status}`);
    }

    const result = data.result || {};
    const list = result.subOrderList || result.productList || result.list || result.items || result.records || [];
    const pageProducts = Array.isArray(list) ? list.map(buildSalesProductFromApi) : [];
    products.push(...pageProducts);
    total = result.total || result.totalCount || total || products.length;
    totalPages = Math.max(1, Math.ceil(total / pageSize));
    const syncEl = document.getElementById('salesLastSyncTime');
    if (syncEl) {
      syncEl.textContent = `正在同步销量：第 ${pageNo}/${totalPages} 页，已读取 ${products.length} 条`;
    }
    if (!pageProducts.length || pageProducts.length < pageSize) break;
    pageNo += 1;
    await new Promise(resolve => setTimeout(resolve, 80));
  }

  return {
    success: true,
    products,
    total: total || products.length,
    totalPages
  };
}

function renderSalesInventoryCell(row = {}) {
  const pairs = [
    `可用 ${Number(row.availableInventoryNum || 0).toLocaleString()}`,
    `预占 ${Number(row.reservedInventoryNum || 0).toLocaleString()}`,
    `暂不可用 ${Number(row.unavailableInventoryNum || 0).toLocaleString()}`,
    `已发货 ${Number(row.shippedInventoryNum || 0).toLocaleString()}`
  ];
  const bgColors = ['#eff6ff', '#ecfeff', '#fefce8', '#f1f5f9'];
  const borderColors = ['#bfdbfe', '#a5f3fc', '#fde68a', '#cbd5e1'];
  return `<div style="display:flex;flex-wrap:wrap;gap:6px 8px;align-items:flex-start;min-width:0;white-space:normal;font-size:12px;color:#334155;font-weight:700;line-height:1.35;">${pairs.map((text, index) => `<span style="display:inline-flex;align-items:center;white-space:nowrap;padding:2px 7px;border-radius:999px;background:${bgColors[index]};border:1px solid ${borderColors[index]};">${text}</span>`).join('')}</div>`;
}

function matchesSalesProduct(product = {}, query = '') {
  if (!query) return true;
  const key = query.toLowerCase();
  const fields = [
    product.productId,
    product.productCNName,
    product.productName,
    product.goodsName,
    product.skuClassName,
    product.skuDisplayName,
    product.skuName,
    product.skuCode,
    product.skuId,
    product.skcId,
    product.skcExtCode
  ];

  if (fields.some(value => normalizeSalesText(value).toLowerCase().includes(key))) {
    return true;
  }

  const skuRows = getSalesProductSkuRows(product);
  return skuRows.some(row => [
    row.skuId,
    row.skuName,
    row.skuClassName,
    row.skuCode
  ].some(value => normalizeSalesText(value).toLowerCase().includes(key)));
}

function getSalesDisplayProducts() {
  const source = getSalesFilteredProducts();
  const pageNum = Math.max(1, salesDataState.pageNum || 1);
  const pageSize = Math.max(1, salesDataState.pageSize || 20);
  const start = (pageNum - 1) * pageSize;
  return source.slice(start, start + pageSize);
}

function getSalesFilteredProducts() {
  const source = Array.isArray(salesDataState.viewProducts)
    ? salesDataState.viewProducts
    : (Array.isArray(salesDataState.allProducts) && salesDataState.allProducts.length > 0
      ? salesDataState.allProducts
      : salesDataState.products);
  return source.filter(hasSalesProductSales);
}

function applySalesSearchFilter() {
  const query = getSalesSearchQuery();
  const source = salesDataState.allProducts && salesDataState.allProducts.length > 0
    ? salesDataState.allProducts
    : salesDataState.products;

  const filtered = query
    ? source.filter(product => matchesSalesProduct(product, query))
    : salesDataState.products;

  salesDataState.viewProducts = filtered.filter(hasSalesProductSales);
  const visibleCount = salesDataState.viewProducts.length;
  const totalPages = Math.max(1, Math.ceil(visibleCount / Math.max(1, salesDataState.pageSize || 20)));
  if (salesDataState.pageNum > totalPages) salesDataState.pageNum = 1;
  renderSalesDataTable();
  renderSalesDataPagination();
}

function goToSalesPage(pageNum = 1) {
  const totalPages = Math.max(1, Math.ceil(getSalesFilteredProducts().length / Math.max(1, salesDataState.pageSize || 20)));
  salesDataState.pageNum = Math.min(Math.max(1, pageNum), totalPages);
  renderSalesDataTable();
  renderSalesDataPagination();
}

// ===== 首页概览相关函数 =====

const PROD_API_BASE = 'https://xiaoqingwa.vip/api';
const DEFAULT_LOCAL_API_BASE = 'http://127.0.0.1:3001/api';
const LOGIN_REQUEST_TIMEOUT_MS = 12000;
let SERVER_URL = getDefaultApiBase();

function isLocalApiBase(base) {
  return /^https?:\/\/(127\.0\.0\.1|localhost)(:\d+)?/i.test(String(base || '').trim());
}

function normalizeApiBase(base) {
  return String(base || '').trim().replace(/\/$/, '');
}

function getDefaultApiBase() {
  try {
    const origin = window.location?.origin || '';
    const host = window.location?.hostname || '';
    if (/^(www\.)?xiaoqingwa\.vip$/i.test(host) && /^https?:\/\//i.test(origin)) {
      return `${origin}/api`;
    }
  } catch (_) {}
  return PROD_API_BASE;
}

function isProductionApiBase(base) {
  try {
    const url = new URL(normalizeApiBase(base));
    return /^(www\.)?xiaoqingwa\.vip$/i.test(url.hostname) && url.pathname.replace(/\/+$/, '') === '/api';
  } catch (_) {
    return false;
  }
}

function normalizeRuntimeApiBase(base) {
  const normalized = normalizeApiBase(base);
  if (isProductionApiBase(normalized)) return getDefaultApiBase();
  return normalized;
}

function isLocalDevPage() {
  const protocol = window.location?.protocol || '';
  const hostname = window.location?.hostname || '';
  return protocol === 'file:' || hostname === '127.0.0.1' || hostname === 'localhost';
}

function getQueryApiBase() {
  try {
    return new URLSearchParams(window.location.search).get('apiBase');
  } catch {
    return '';
  }
}

function isAllowedApiBase(base) {
  const normalized = normalizeApiBase(base);
  if (isProductionApiBase(normalized)) return true;
  return isLocalDevPage() && isLocalApiBase(normalized);
}

function describeApiFetchError(base, err) {
  if (err?.name === 'AbortError') return '服务器响应超时，请稍后重试';
  const normalized = normalizeApiBase(base);
  if (isLocalDevPage() && normalized === PROD_API_BASE) {
    return '本地打开页面不能直连正式登录接口。请访问 https://xiaoqingwa.vip 登录，或启动本地后端后使用 ?apiBase=http://127.0.0.1:3001/api';
  }
  if (isLocalApiBase(normalized)) {
    return '无法连接本地后端服务，请确认本地服务器已启动，并且地址为 http://127.0.0.1:3001/api';
  }
  return err?.message === 'Failed to fetch'
    ? '无法连接登录服务器，请检查网络或后端接口地址'
    : (err?.message || String(err));
}

function getApiBaseCandidates(preferCurrent = true) {
  const seen = new Set();
  const bases = [];
  const push = (base) => {
    if (!base) return;
    const normalized = normalizeApiBase(base);
    if (!isAllowedApiBase(normalized)) return;
    if (seen.has(normalized)) return;
    seen.add(normalized);
    bases.push(normalized);
  };
  const queryBase = getQueryApiBase();
  push(queryBase);
  const storedBase = localStorage.getItem('temuApiBase');
  const defaultBase = getDefaultApiBase();
  if (isLocalDevPage()) {
    if (storedBase && isLocalApiBase(storedBase)) push(storedBase);
    push(DEFAULT_LOCAL_API_BASE);
    if (preferCurrent && isLocalApiBase(SERVER_URL)) push(SERVER_URL);
    return bases.length ? bases : [DEFAULT_LOCAL_API_BASE];
  }
  push(isProductionApiBase(storedBase) ? defaultBase : storedBase);
  if (preferCurrent) push(SERVER_URL);
  push(defaultBase);
  push(PROD_API_BASE);
  return bases;
}

async function postJsonWithFallback(path, payload, preferCurrent = true) {
  const bases = getApiBaseCandidates(preferCurrent);
  let lastError = null;
  const timeoutMs = path === '/user/login' ? LOGIN_REQUEST_TIMEOUT_MS : 8000;
  for (const base of bases) {
    const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
    const timeoutId = controller ? window.setTimeout(() => controller.abort(), timeoutMs) : null;
    try {
      const resp = await fetch(`${base}${path}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload),
        signal: controller?.signal
      });
      const result = await resp.json().catch(() => ({}));
      if (resp.ok && result.ok) {
        return { ok: true, base, result };
      }
      lastError = { status: resp.status, message: result.error || result.message || `HTTP ${resp.status}` };
      if (![401, 403].includes(resp.status) && resp.status < 500) {
        return { ok: false, base, result, error: lastError.message };
      }
    } catch (err) {
      lastError = { message: describeApiFetchError(base, err) };
    } finally {
      if (timeoutId) window.clearTimeout(timeoutId);
    }
  }
  return { ok: false, error: lastError?.message || '请求失败' };
}

function warmServerApi() {
  fetch(`${SERVER_URL}/health`, {
    cache: 'no-store',
    keepalive: true
  }).catch(() => {});
}

async function resolveServerUrl() {
  const queryBase = getQueryApiBase();
  if (queryBase && isAllowedApiBase(queryBase)) {
    SERVER_URL = normalizeRuntimeApiBase(queryBase);
    localStorage.setItem('temuApiBase', SERVER_URL);
    return;
  }

  const storedBase = localStorage.getItem('temuApiBase');
  if (isLocalDevPage()) {
    if (storedBase && isLocalApiBase(storedBase)) {
      SERVER_URL = normalizeRuntimeApiBase(storedBase);
      return;
    }
    SERVER_URL = DEFAULT_LOCAL_API_BASE;
    localStorage.setItem('temuApiBase', SERVER_URL);
    return;
  }

  if (storedBase && isAllowedApiBase(storedBase)) {
    SERVER_URL = normalizeRuntimeApiBase(storedBase);
    if (SERVER_URL !== normalizeApiBase(storedBase)) localStorage.setItem('temuApiBase', SERVER_URL);
    return;
  }
  if (storedBase) {
    localStorage.removeItem('temuApiBase');
  }

  SERVER_URL = getDefaultApiBase();
  localStorage.setItem('temuApiBase', SERVER_URL);
}
let dashThirtyUnlocked = false;
const dashboardSalesState = {
  todayTotal: 0,
  sevenDayTotal: 0,
  thirtyTotal: 0,
  todayAmount: 0,
  sevenDayAmount: 0,
  thirtyAmount: 0,
  source: 'unknown'
};

function setDashboardSalesSummary(summary = {}) {
  if (Number.isFinite(Number(summary.todayTotal))) dashboardSalesState.todayTotal = Number(summary.todayTotal);
  if (Number.isFinite(Number(summary.sevenDayTotal))) dashboardSalesState.sevenDayTotal = Number(summary.sevenDayTotal);
  if (Number.isFinite(Number(summary.thirtyTotal))) dashboardSalesState.thirtyTotal = Number(summary.thirtyTotal);
  if (Number.isFinite(Number(summary.todayAmount))) dashboardSalesState.todayAmount = Number(summary.todayAmount);
  if (Number.isFinite(Number(summary.sevenDayAmount))) dashboardSalesState.sevenDayAmount = Number(summary.sevenDayAmount);
  if (Number.isFinite(Number(summary.thirtyAmount))) dashboardSalesState.thirtyAmount = Number(summary.thirtyAmount);
  if (summary.source) dashboardSalesState.source = summary.source;
  syncDingHuiTemuHome();
}

function syncDingHuiTemuHome() {
  const hasHome = Boolean(document.getElementById('dhTemuMallName'));
  if (!hasHome) return;
  const connected = Boolean(state.mallId && state.antiContent);
  const pickName = (...values) => values
    .map(value => String(value ?? '').trim())
    .find(value => value && value !== '当前店铺' && value !== '当前店铺 ');
  const shopName = connected
    ? (pickName(state.mallName, getCachedShopName(state.mallId)) || `店铺 ${state.mallId}`)
    : '未连接店铺';
  const fullShopName = connected && state.mallId ? `${shopName} (${state.mallId})` : shopName;
  const setText = (id, value) => {
    const el = document.getElementById(id);
    if (el) el.textContent = value;
  };
  const setBadge = (targetId, sourceId, fallbackText, fallbackClass) => {
    const target = document.getElementById(targetId);
    if (!target) return;
    const source = document.getElementById(sourceId);
    target.textContent = source?.textContent || fallbackText;
    target.className = source?.className || fallbackClass;
    target.style.display = 'inline-flex';
  };
  const bridge = getBridgeRuntimeState();
  const connectionText = connected ? '已连接' : (bridge.active && bridge.ready ? '助手已连接' : '未连接助手');
  const connectionClass = connected
    ? 'status-badge status-connected'
    : `status-badge ${bridge.active && bridge.ready ? 'status-bridge-ready' : 'status-disconnected'}`;

  setText('dhTemuMallName', shopName);
  setText('dhTemuMallInfo', fullShopName);
  const reconnectBtn = document.getElementById('dhTemuReconnectBtn');
  if (reconnectBtn) reconnectBtn.style.display = connected ? 'none' : 'inline-flex';
  setBadge('dhTemuConnectionStatus', 'connectionStatus', connectionText, connectionClass);
  setBadge('dhTemuTopAuthStatus', 'topAuthStatus', getCurrentServerUser() ? '已登录' : '未绑定', getCurrentServerUser() ? 'status-badge status-authorized' : 'status-badge status-disconnected');
  setText('dhTemuDashAuthStatus', document.getElementById('dashAuthStatus')?.textContent || document.getElementById('loginAuthStatus')?.textContent || '未登录');

  const sourceResult = document.getElementById('dashAuthResult');
  const targetResult = document.getElementById('dhTemuDashAuthResult');
  if (targetResult) {
    targetResult.textContent = sourceResult?.textContent || '';
    targetResult.style.color = sourceResult?.style?.color || '#667085';
    targetResult.style.display = sourceResult?.style?.display === 'block' && sourceResult?.textContent ? 'block' : 'none';
  }

  const todayTotal = Number(dashboardSalesState.todayTotal || 0);
  const sevenDayTotal = Number(dashboardSalesState.sevenDayTotal || 0);
  const todayAmount = Number(dashboardSalesState.todayAmount || 0);
  const sevenDayAmount = Number(dashboardSalesState.sevenDayAmount || 0);
  setText('dhTemuSalesToday', Number.isFinite(todayTotal) ? todayTotal.toLocaleString() : '0');
  setText('dhTemuSalesSeven', Number.isFinite(sevenDayTotal) ? sevenDayTotal.toLocaleString() : '0');
  setText('dhTemuSalesAmount', `¥${formatAmount(sevenDayAmount)}`);
  setText('dhTemuSalesCost', `¥${formatAmount(todayAmount)}`);
  renderTemuShopSalesDistribution({ shopName, todayTotal, sevenDayTotal, connected });
}

function renderTemuShopSalesDistribution({ shopName = '未连接店铺', todayTotal = 0, sevenDayTotal = 0, connected = false } = {}) {
  const listEl = document.getElementById('dhTemuShopSalesList');
  const summaryEl = document.getElementById('dhTemuShopSalesSummary');
  if (!listEl) return;

  const currentMallId = String(state.mallId || '').trim();
  const shopMap = new Map();
  const addShop = (id, name, today = 0, seven = 0, synced = false) => {
    const key = String(id || name || `shop-${shopMap.size}`).trim();
    if (!key) return;
    shopMap.set(key, {
      id: String(id || '').trim(),
      name: name || (id ? `店铺 ${id}` : '未连接店铺'),
      today: Number(today || 0),
      seven: Number(seven || 0),
      synced
    });
  };

  if (connected) addShop(currentMallId, shopName, todayTotal, sevenDayTotal, true);
  (Array.isArray(state.boundShops) ? state.boundShops : []).forEach((shop) => {
    const mallId = String(shop?.mallId || '').trim();
    const name = pickShopName(shop?.shopName, shop?.mallName, shop?.storeName, shop?.name, getCachedShopName(mallId), mallId ? `店铺 ${mallId}` : '绑定店铺');
    if (mallId && mallId === currentMallId && connected) {
      addShop(mallId, shopName || name, todayTotal, sevenDayTotal, true);
    } else {
      addShop(mallId, name, 0, 0, false);
    }
  });
  if (shopMap.size === 0) addShop('', '未连接店铺', 0, 0, false);

  const shops = Array.from(shopMap.values());
  if (summaryEl) summaryEl.textContent = `${shops.length} 个店铺`;
  listEl.innerHTML = shops.map((shop) => `
    <div class="dh-shop-sales-row" title="${escapeHtml(shop.name)}">
      <div>
        <div class="dh-shop-sales-name">${escapeHtml(shop.name)}</div>
        <div class="dh-shop-sales-meta">${shop.synced ? `今日 ${Number(shop.today).toLocaleString()} · 7日 ${Number(shop.seven).toLocaleString()}` : '未同步销量数据'}</div>
      </div>
      <div class="dh-shop-sales-value">${Number(shop.seven || shop.today || 0).toLocaleString()}</div>
    </div>
  `).join('');
}

function pickTrendDateValue(item = {}) {
  const dateValue = pickSalesField(item, [
    'date',
    'saleDate',
    'salesDate',
    'day',
    'statDate',
    'time',
    'statTime',
    'dateStr',
    'dateString',
    'sale_date',
    'sale_time'
  ]);
  if (!dateValue) return '';
  const text = normalizeSalesText(dateValue);
  if (!text) return '';
  const date = new Date(text);
  if (!Number.isNaN(date.getTime())) {
    const pad = n => String(n).padStart(2, '0');
    return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
  }
  return text.slice(0, 10);
}

function pickTrendVolumeValue(item = {}) {
  return normalizeSalesNumber(pickSalesField(item, [
    'saleVolume',
    'salesVolume',
    'volume',
    'count',
    'num',
    'saleNum',
    'salesNum',
    'orderNum',
    'todaySaleVolume',
    'sevenDaysSaleVolume',
    'thirtyDaysSaleVolume',
    'dailySaleVolume'
  ])) || 0;
}

function getTrendPointArray(node, depth = 0) {
  if (!node || typeof node !== 'object' || depth > 3) return null;
  const candidateKeys = [
    'salesTrendList',
    'saleTrendList',
    'dailyTrendList',
    'dailySalesTrendList',
    'dailySaleTrendList',
    'trendList',
    'dayTrendList',
    'dailyList',
    'saleList',
    'chartList',
    'dateList',
    'salesList'
  ];
  for (const key of candidateKeys) {
    const value = node[key];
    if (Array.isArray(value) && value.some(item => item && typeof item === 'object')) {
      return value;
    }
  }
  for (const value of Object.values(node)) {
    if (Array.isArray(value) && value.some(item => item && typeof item === 'object')) {
      const hasTrendShape = value.some(item => {
        const date = pickTrendDateValue(item);
        const sale = pickTrendVolumeValue(item);
        return Boolean(date) && Number.isFinite(sale);
      });
      if (hasTrendShape) return value;
    }
  }
  for (const value of Object.values(node)) {
    if (value && typeof value === 'object') {
      const nested = getTrendPointArray(value, depth + 1);
      if (nested) return nested;
    }
  }
  return null;
}

function extractSalesTrendSeries(products = []) {
  const series = new Map();
  for (const product of products) {
    const source = product?.raw || product;
    const points = Array.isArray(source) ? source : getTrendPointArray(source);
    if (!Array.isArray(points) || points.length === 0) continue;
    for (const item of points) {
      if (!item || typeof item !== 'object') continue;
      const date = pickTrendDateValue(item);
      const value = pickTrendVolumeValue(item);
      if (!date || !Number.isFinite(value)) continue;
      series.set(date, (series.get(date) || 0) + value);
    }
  }

  const ordered = Array.from(series.entries())
    .map(([date, value]) => ({ date, value: Number(value) || 0 }))
    .sort((a, b) => a.date.localeCompare(b.date));

  return ordered;
}

function buildSevenDayTrendBars(products = []) {
  const trendSeries = extractSalesTrendSeries(products);
  if (trendSeries.length === 0) return [];
  const valueByDate = new Map(trendSeries.map(item => [item.date, Number(item.value) || 0]));
  const lastDate = new Date(trendSeries[trendSeries.length - 1].date);
  const baseDate = Number.isNaN(lastDate.getTime()) ? new Date() : lastDate;
  const pad = n => String(n).padStart(2, '0');
  const bars = [];
  for (let offset = 6; offset >= 0; offset--) {
    const date = new Date(baseDate);
    date.setDate(date.getDate() - offset);
    const key = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
    bars.push({ date: key, value: valueByDate.get(key) || 0 });
  }
  return bars;
}

function renderTrendBarChartMarkup(bars = []) {
  const labels = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
  const maxValue = Math.max(...bars.map(item => Number(item.value) || 0), 1);
  return bars.map(item => {
    const date = new Date(item.date);
    const label = Number.isNaN(date.getTime())
      ? item.date
      : `${labels[date.getDay()]}<br><span class="bar-chart-date">${date.getMonth() + 1}/${date.getDate()}</span>`;
    const heightPercent = Math.max(8, Math.round((Number(item.value) / maxValue) * 100));
    return `<div class="bar-chart-item"><div class="bar-chart-value">${Number(item.value).toLocaleString()}</div><div class="bar-chart-bar" style="height:${heightPercent}%;"><div class="bar-chart-fill" style="height:100%;"></div></div><div class="bar-chart-label">${label}</div></div>`;
  }).join('');
}

const dashboardStockReminderState = {
  mallId: '',
  loading: false,
  loadedAt: 0,
  payload: null,
  error: '',
  pendingPromise: null
};

function pickStockReminderField(source, keys = []) {
  if (!source || typeof source !== 'object') return null;
  for (const key of keys) {
    const value = source[key];
    if (value !== undefined && value !== null && value !== '') return value;
  }
  return null;
}

function parseStockReminderTime(value) {
  if (value === null || value === undefined || value === '') return null;
  if (value instanceof Date) {
    const time = value.getTime();
    return Number.isNaN(time) ? null : time;
  }
  if (typeof value === 'number' && Number.isFinite(value)) {
    return value > 1e12 ? value : value * 1000;
  }
  const text = String(value).trim();
  if (!text) return null;
  const numeric = Number(text);
  if (Number.isFinite(numeric)) {
    return numeric > 1e12 ? numeric : numeric * 1000;
  }
  let parsed = new Date(text);
  let time = parsed.getTime();
  if (Number.isNaN(time)) {
    parsed = new Date(text.replace(/-/g, '/'));
    time = parsed.getTime();
  }
  return Number.isNaN(time) ? null : time;
}

function formatStockReminderCountdown(ms) {
  if (!Number.isFinite(ms)) return '—';
  if (ms <= 0) return '已超时';
  const totalMinutes = Math.ceil(ms / 60000);
  const days = Math.floor(totalMinutes / 1440);
  const hours = Math.floor((totalMinutes % 1440) / 60);
  const minutes = totalMinutes % 60;
  const parts = [];
  if (days > 0) parts.push(`${days}天`);
  if (hours > 0 || days > 0) parts.push(`${hours}小时`);
  parts.push(`${minutes}分钟`);
  return parts.join('');
}

function formatStockReminderDate(value) {
  const time = parseStockReminderTime(value);
  if (!time) return '—';
  const date = new Date(time);
  return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
}

function buildStockReminderItem(row, type, deadline, thresholdHours) {
  const remainingMs = deadline - Date.now();
  const productName = pickStockReminderField(row, [
    'productName',
    'goodsName',
    'productTitle',
    'skuName',
    'productSkcName',
    'title'
  ]) || '备货单';
  const skuText = pickStockReminderField(row, ['productSkcId', 'skcId', 'skc', 'skuId']) || '—';
  const orderSn = pickStockReminderField(row, ['subPurchaseOrderSn', 'subOrderSn', 'purchaseOrderSn', 'orderSn']) || '—';
  const image = pickStockReminderField(row, ['productSkcPicture', 'img', 'imageUrl', 'mainImage']) || '';
  const typeLabel = type === 'shipping' ? '发货超时预警' : '到货超时预警';
  const tone = type === 'shipping'
    ? remainingMs <= 6 * 3600000 ? 'danger' : 'warning'
    : remainingMs <= 12 * 3600000 ? 'danger' : 'warning';

  return {
    id: `${type}:${orderSn}:${skuText}:${deadline}`,
    type,
    typeLabel,
    tone,
    productName,
    skuText,
    orderSn,
    image,
    deadline,
    deadlineText: formatStockReminderDate(deadline),
    remainingMs,
    remainingText: formatStockReminderCountdown(remainingMs),
    thresholdHours
  };
}

function buildDashboardStockReminderItems(rows = []) {
  const shipping = [];
  const arrival = [];
  const now = Date.now();
  const shippingThresholdMs = 24 * 3600000;
  const arrivalThresholdMs = 48 * 3600000;

  for (const row of Array.isArray(rows) ? rows : []) {
    const shippingDeadline = parseStockReminderTime(pickStockReminderField(row, [
      'expectLatestDeliverTime',
      'expectLatestDeliveryTime',
      'latestDeliverTime',
      'deliverDeadline',
      'deliveryDeadline'
    ]));
    if (shippingDeadline) {
      const remaining = shippingDeadline - now;
      if (remaining > 0 && remaining <= shippingThresholdMs) {
        shipping.push(buildStockReminderItem(row, 'shipping', shippingDeadline, 24));
      }
    }

    const arrivalDeadline = parseStockReminderTime(pickStockReminderField(row, [
      'expectLatestArriveTime',
      'expectLatestReceiveTime',
      'latestArriveTime',
      'arriveDeadline',
      'receiveDeadline'
    ]));
    if (arrivalDeadline) {
      const remaining = arrivalDeadline - now;
      if (remaining > 0 && remaining <= arrivalThresholdMs) {
        arrival.push(buildStockReminderItem(row, 'arrival', arrivalDeadline, 48));
      }
    }
  }

  const sorter = (a, b) => a.remainingMs - b.remainingMs || a.productName.localeCompare(b.productName, 'zh-Hans-CN');
  shipping.sort(sorter);
  arrival.sort(sorter);
  return { shipping, arrival };
}

function renderDashboardStockReminder(payload = {}) {
  const summaryEl = document.getElementById('dashStockReminderSummary');
  const listEl = document.getElementById('dashStockReminderList');
  if (!summaryEl || !listEl) return;

  const shipping = Array.isArray(payload.shipping) ? payload.shipping : [];
  const arrival = Array.isArray(payload.arrival) ? payload.arrival : [];
  const loading = Boolean(payload.loading);
  const error = String(payload.error || '').trim();
  const message = String(payload.message || '').trim();
  const updatedAt = payload.updatedAt ? formatStockReminderDate(payload.updatedAt) : '';
  const total = shipping.length + arrival.length;

  if (loading) {
    summaryEl.textContent = '正在加载备货单提醒...';
    listEl.innerHTML = '<div style="color:#999;text-align:center;padding:16px;">正在从 TEMU 读取备货单数据</div>';
    return;
  }

  if (error) {
    summaryEl.textContent = error;
    listEl.innerHTML = `<div style="color:#dc2626;text-align:center;padding:16px;">${escapeHtml(error)}</div>`;
    return;
  }

  if (total > 0) {
    summaryEl.textContent = `发货预警 ${shipping.length} 条 · 到货预警 ${arrival.length} 条${updatedAt ? ` · 更新于 ${updatedAt}` : ''}`;
  } else {
    summaryEl.textContent = message || '暂无即将超时的备货单';
  }

  const renderSection = (title, items, tone) => {
    if (!items || items.length === 0) return '';
    const headerBg = tone === 'danger' ? '#fff1f2' : '#eff6ff';
    const headerColor = tone === 'danger' ? '#be123c' : '#1d4ed8';
    return `
      <div style="display:grid;gap:10px;">
        <div style="display:flex;justify-content:space-between;align-items:center;gap:12px;padding:10px 12px;border-radius:12px;background:${headerBg};border:1px solid ${tone === 'danger' ? '#fecdd3' : '#dbeafe'};">
          <div style="font-weight:800;color:${headerColor};">${escapeHtml(title)}</div>
          <div style="font-size:12px;color:${headerColor};font-weight:700;">${items.length} 条</div>
        </div>
        <div style="display:flex;flex-direction:column;gap:10px;">
          ${items.map(item => {
            const severityBg = item.tone === 'danger' ? '#fff1f2' : '#f8fbff';
            const severityBorder = item.tone === 'danger' ? '#fecdd3' : '#dbeafe';
            const severityColor = item.tone === 'danger' ? '#be123c' : '#1d4ed8';
            return `
              <div style="display:grid;grid-template-columns:56px minmax(0,1fr) auto;gap:12px;padding:12px;border-radius:14px;background:${severityBg};border:1px solid ${severityBorder};align-items:center;">
                <div style="width:56px;height:56px;border-radius:12px;overflow:hidden;background:#fff;display:flex;align-items:center;justify-content:center;border:1px solid rgba(148,163,184,0.25);">
                  ${item.image ? `<img src="${escapeHtml(item.image)}" alt="" data-fallback-size="56" style="width:56px;height:56px;object-fit:cover;">` : '<div style="font-size:20px;color:#cbd5e1;">备</div>'}
                </div>
                <div style="min-width:0;">
                  <div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
                    <span style="font-weight:800;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;" title="${escapeHtml(item.productName)}">${escapeHtml(item.productName)}</span>
                    <span style="display:inline-flex;align-items:center;padding:3px 8px;border-radius:999px;background:#fff;color:${severityColor};font-size:11px;font-weight:800;border:1px solid ${severityBorder};">${escapeHtml(item.typeLabel)}</span>
                  </div>
                  <div style="margin-top:4px;color:#475569;font-size:12px;line-height:1.5;display:flex;flex-wrap:wrap;gap:10px;">
                    <span>备货单号：${escapeHtml(item.orderSn)}</span>
                    <span>SKC ID：${escapeHtml(item.skuText)}</span>
                    <span>截止时间：${escapeHtml(item.deadlineText)}</span>
                    <span>剩余：${escapeHtml(item.remainingText)}</span>
                  </div>
                </div>
                <div style="text-align:right;min-width:88px;">
                  <div style="font-size:12px;color:#64748b;">${escapeHtml(item.typeLabel)}</div>
                  <div style="margin-top:4px;font-size:13px;font-weight:800;color:${severityColor};">${escapeHtml(item.remainingText)}</div>
                </div>
              </div>
            `;
          }).join('')}
        </div>
      </div>
    `;
  };

  if (total === 0) {
    listEl.innerHTML = '<div style="color:#999;text-align:center;padding:16px;">当前没有即将超时的备货单</div>';
    return;
  }

  listEl.innerHTML = [renderSection('马上要超时发货的备货单（剩余24小时内）', shipping, 'danger'), renderSection('马上要到货超时的备货单（剩余48小时内）', arrival, 'warning')].filter(Boolean).join('<div style="height:12px;"></div>');
}

function clearDashboardStockReminder(message = '请先登录并连接店铺') {
  renderDashboardStockReminder({
    loading: false,
    shipping: [],
    arrival: [],
    message
  });
}

async function refreshDashboardStockReminder(force = false) {
  const summaryEl = document.getElementById('dashStockReminderSummary');
  const listEl = document.getElementById('dashStockReminderList');
  if (!summaryEl || !listEl) return null;

  if (!state.mallId || !state.antiContent) {
    dashboardStockReminderState.mallId = '';
    dashboardStockReminderState.payload = null;
    dashboardStockReminderState.error = '';
    clearDashboardStockReminder('请先连接店铺后再查看备货单提醒');
    return null;
  }

  if (!force
    && dashboardStockReminderState.payload
    && dashboardStockReminderState.mallId === state.mallId
    && Date.now() - dashboardStockReminderState.loadedAt < 5 * 60 * 1000) {
    renderDashboardStockReminder(dashboardStockReminderState.payload);
    return dashboardStockReminderState.payload;
  }

  if (dashboardStockReminderState.loading) return dashboardStockReminderState.pendingPromise || null;

  dashboardStockReminderState.loading = true;
  dashboardStockReminderState.error = '';
  dashboardStockReminderState.pendingPromise = (async () => {
    renderDashboardStockReminder({ loading: true });
    try {
      const result = await safeRuntimeSendMessage({
        action: 'getStockReminderData',
        data: {
          mallId: state.mallId
        }
      }, { success: false, error: '备货单提醒加载失败' }, 30000);

      if (!result || !result.success) {
        const error = result?.error || '备货单提醒加载失败';
        dashboardStockReminderState.payload = null;
        dashboardStockReminderState.error = error;
        renderDashboardStockReminder({ error });
        return null;
      }

      const payload = {
        shipping: Array.isArray(result.shipping) ? result.shipping : [],
        arrival: Array.isArray(result.arrival) ? result.arrival : [],
        updatedAt: result.updatedAt || Date.now(),
        message: result.message || ''
      };
      dashboardStockReminderState.mallId = state.mallId;
      dashboardStockReminderState.loadedAt = Date.now();
      dashboardStockReminderState.payload = payload;
      dashboardStockReminderState.error = '';
      renderDashboardStockReminder(payload);
      return payload;
    } catch (error) {
      const message = error?.message || String(error) || '备货单提醒加载失败';
      dashboardStockReminderState.payload = null;
      dashboardStockReminderState.error = message;
      renderDashboardStockReminder({ error: message });
      return null;
    } finally {
      dashboardStockReminderState.loading = false;
      dashboardStockReminderState.pendingPromise = null;
    }
  })();

  return dashboardStockReminderState.pendingPromise;
}

function clearDashboardSalesVisuals() {
  dashThirtyUnlocked = false;
  const dToday = document.getElementById('dashTodayCount');
  const dSeven = document.getElementById('dashSevenDayCount');
  const dTodayAmt = document.getElementById('dashTodayAmount');
  const dSevenAmt = document.getElementById('dashSevenDayAmount');
  const dTodayStand = document.getElementById('dashTodayAmountStandalone');
  const dSevenStand = document.getElementById('dashSevenDayAmountStandalone');
  const dThirtyCard = document.getElementById('dashThirtyCard');
  const dThirtyCount = document.getElementById('dashThirtyDayCount');
  const dThirtyAmount = document.getElementById('dashThirtyDayAmount');
  const dThirtyRow = document.getElementById('dashThirtyRow');
  const dThirtyStand = document.getElementById('dashThirtyDayAmountStandalone');
  const dashChart = document.getElementById('dashBarChart');
  const dashDailyChart = document.getElementById('dashDailySalesBarChart');

  if (dToday) dToday.textContent = '0';
  if (dSeven) dSeven.textContent = '0';
  if (dTodayAmt) { dTodayAmt.textContent = '¥0.00'; dTodayAmt.style.display = ''; }
  if (dSevenAmt) { dSevenAmt.textContent = '¥0.00'; dSevenAmt.style.display = ''; }
  if (dTodayStand) { dTodayStand.style.display = ''; dTodayStand.textContent = '0'; }
  if (dSevenStand) { dSevenStand.style.display = ''; dSevenStand.textContent = '0'; }
  if (dThirtyCard) { dThirtyCard.style.opacity = '0.4'; dThirtyCard.style.cursor = 'pointer'; dThirtyCard.title = '点击刷新同步当月销量'; }
  if (dThirtyCount) dThirtyCount.textContent = '—';
  if (dThirtyAmount) dThirtyAmount.textContent = '点击刷新';
  if (dThirtyRow) { dThirtyRow.style.opacity = '0.4'; dThirtyRow.style.cursor = 'pointer'; dThirtyRow.title = '点击刷新同步当月销售额'; }
  if (dThirtyStand) dThirtyStand.textContent = '🔥 点击刷新';
  if (dashChart) dashChart.innerHTML = '<div class="bar-chart-empty">暂无真实日趋势数据</div>';
  if (dashDailyChart) dashDailyChart.innerHTML = '<div class="bar-chart-empty">点击同步数据后显示每日销量</div>';
  clearDashboardStockReminder('请先登录并连接店铺');
  syncDingHuiTemuHome();
}

// 更新首页销量概览（今日+7日，不含30天）
function updateDashboardSales(todayTotal, sevenDayTotal, todayAmount, sevenDayAmount, products, source = 'sales-query-summary') {
  setDashboardSalesSummary({
    todayTotal,
    sevenDayTotal,
    todayAmount,
    sevenDayAmount,
    source
  });
  const dToday = document.getElementById('dashTodayCount');
  const dSeven = document.getElementById('dashSevenDayCount');
  const dTodayAmt = document.getElementById('dashTodayAmount');
  const dSevenAmt = document.getElementById('dashSevenDayAmount');
  const dTodayStand = document.getElementById('dashTodayAmountStandalone');
  const dSevenStand = document.getElementById('dashSevenDayAmountStandalone');

  if (dToday) dToday.textContent = Number.isFinite(Number(todayTotal)) ? Number(todayTotal).toLocaleString() : '0';
  if (dSeven) dSeven.textContent = Number.isFinite(Number(sevenDayTotal)) ? Number(sevenDayTotal).toLocaleString() : '0';
  if (dTodayAmt) { dTodayAmt.textContent = `¥${formatAmount(todayAmount)}`; dTodayAmt.style.display = ''; }
  if (dSevenAmt) { dSevenAmt.textContent = `¥${formatAmount(sevenDayAmount)}`; dSevenAmt.style.display = ''; }
  if (dTodayStand) {
    dTodayStand.style.display = '';
    dTodayStand.textContent = Number.isFinite(Number(todayTotal)) ? Number(todayTotal).toLocaleString() : '0';
  }
  if (dSevenStand) {
    dSevenStand.style.display = '';
    dSevenStand.textContent = Number.isFinite(Number(sevenDayTotal)) ? Number(sevenDayTotal).toLocaleString() : '0';
  }

  // 更新首页柱状图
  const dashChart = document.getElementById('dashBarChart');
  const dashDailyChart = document.getElementById('dashDailySalesBarChart');
  if (dashChart || dashDailyChart) {
    const bars = buildSevenDayTrendBars(products || []);
    const chartHtml = bars.length > 0
      ? `<div class="bar-chart-row">${renderTrendBarChartMarkup(bars)}</div>`
      : '<div class="bar-chart-empty">暂无真实日趋势数据</div>';
    if (bars.length > 0) {
      if (dashChart) dashChart.innerHTML = chartHtml;
      if (dashDailyChart) dashDailyChart.innerHTML = chartHtml;
    } else {
      if (dashChart) dashChart.innerHTML = chartHtml;
      if (dashDailyChart) dashDailyChart.innerHTML = chartHtml;
    }
  }

  // 更新账号信息
  updateDashboardAccount();
  syncDingHuiTemuHome();
}

let lastDashboardHomeSalesWarnText = '';
let lastDashboardHomeSalesWarnAt = 0;

function warnDashboardHomeSalesFailure(message) {
  const text = String(message || '后台首页销量读取失败');
  const now = Date.now();
  if (text === lastDashboardHomeSalesWarnText && now - lastDashboardHomeSalesWarnAt < 60000) return;
  lastDashboardHomeSalesWarnText = text;
  lastDashboardHomeSalesWarnAt = now;
  console.info('[小青蛙电商助手] 首页销量暂不可用，已保持概览默认值:', text);
}

async function refreshDashboardHomeSales() {
  if (!state.mallId) return null;
  try {
    const result = await safeRuntimeSendMessage({ action: 'getDashboardHomeSales' }, { success: false, error: '后台首页销量读取失败' }, 8000);
    if (!result?.success) {
      warnDashboardHomeSalesFailure(result?.error || result);
      return null;
    }
    updateDashboardSales(
      Number(result.todayTotal || 0),
      Number(result.sevenDayTotal || 0),
      Number(result.todayAmount || 0),
      Number(result.sevenDayAmount || 0),
      [],
      'temu-home'
    );
    return result;
  } catch (error) {
    console.warn('[小青蛙电商助手] 首页销量读取异常:', error);
    return null;
  }
}

// 解锁首页当月销量数据
function unlockDashboardThirty(thirtyTotal, thirtyAmount) {
  dashThirtyUnlocked = true;
  setDashboardSalesSummary({
    thirtyTotal,
    thirtyAmount,
    source: 'sales-query-summary'
  });
  const card = document.getElementById('dashThirtyCard');
  const count = document.getElementById('dashThirtyDayCount');
  const amount = document.getElementById('dashThirtyDayAmount');
  const row = document.getElementById('dashThirtyRow');
  const stand = document.getElementById('dashThirtyDayAmountStandalone');
  if (card) { card.style.opacity = '1'; card.style.cursor = 'pointer'; card.title = '点击刷新同步当月销量'; }
  if (count) count.textContent = thirtyTotal.toLocaleString();
  if (amount) { amount.textContent = '点击刷新同步'; amount.style.display = ''; }
  if (row) { row.style.opacity = '1'; row.style.cursor = 'pointer'; row.title = '点击刷新同步当月销售额'; }
  if (stand) { stand.style.display = ''; stand.textContent = `¥${formatAmount(thirtyAmount)}`; }
  syncDingHuiTemuHome();
}

// 更新首页账号信息
function updateDashboardAccount() {
  // 账号卡片由 renderDashboardAccessPanels 负责整块渲染，这里保留接口避免旧调用报错。
}

function mergeDashboardTrendCardIntoSalesCard() {
  if (document.getElementById('dashTrendMergedMarker')) return;
  const salesCard = document.querySelector('.sales-summary-left');
  const trendCard = document.querySelector('.dashboard-trend-card');
  const chartContainer = trendCard?.querySelector('.chart-container');
  if (!salesCard || !trendCard || !chartContainer) return;

  const marker = document.createElement('span');
  marker.id = 'dashTrendMergedMarker';
  marker.style.display = 'none';
  chartContainer.appendChild(marker);
  salesCard.appendChild(chartContainer);
  trendCard.style.display = 'none';
}

function compareSemverVersion(left, right) {
  const parse = (value) => String(value || '')
    .split('.')
    .map(part => Number.parseInt(part.replace(/\D.*$/, ''), 10) || 0);
  const a = parse(left);
  const b = parse(right);
  const length = Math.max(a.length, b.length, 3);
  for (let i = 0; i < length; i += 1) {
    const diff = (a[i] || 0) - (b[i] || 0);
    if (diff !== 0) return diff > 0 ? 1 : -1;
  }
  return 0;
}

function setDashboardLockedState(locked) {
  document.body.classList.toggle('auth-locked', locked);
  setDashboardAuthPanelVisible(Boolean(locked));
}

function setFeatureAccessState(locked, reason = '') {
  const user = getCurrentServerUser();
  const admin = isAdminUser(user);
  featureAccessState.locked = admin ? false : Boolean(locked);
  featureAccessState.reason = admin ? '' : (reason || (locked ? '当前账号暂无可用授权店铺，请先绑定并完成卡密授权' : ''));
  applyRoleFeatureVisibility(user);
  document.querySelectorAll('.tab').forEach((tab) => {
    const tabId = tab.dataset.tab;
    if (tabId === 'dashboard') {
      tab.classList.remove('locked');
      tab.disabled = false;
      return;
    }
    if (ADMIN_ONLY_TABS.has(tabId) && !admin) {
      tab.classList.remove('locked');
      tab.disabled = true;
      tab.hidden = true;
      return;
    }
    tab.classList.toggle('locked', featureAccessState.locked);
    tab.disabled = featureAccessState.locked;
  });
}

function formatDashboardDate(value, includeTime = true) {
  if (!value) return '未授权';
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return '未授权';
  return includeTime
    ? date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
    : date.toLocaleDateString('zh-CN');
}

function focusDashboardAuthorizationSection(mallId = '', focusTarget = 'code') {
  const renewCard = document.getElementById('dashRenewSection');
  const codeEl = document.getElementById('dashCardInput');
  if (renewCard) {
    renewCard.style.display = 'block';
    renewCard.scrollIntoView({ behavior: 'smooth', block: 'start' });
  }
  requestAnimationFrame(() => {
    codeEl?.focus();
    codeEl?.select?.();
  });
}

function setDashboardShopActionResult(message, tone = 'info') {
  const resultEl = document.getElementById('dashShopActionResult');
  if (!resultEl) return;
  resultEl.style.display = 'block';
  resultEl.textContent = message;
  resultEl.style.color = tone === 'success' ? '#16a34a' : tone === 'error' ? '#dc2626' : '#475569';
}

function setDashboardAuthPanelVisible(visible) {
  const authGate = document.getElementById('authGate');
  if (!authGate) return;
  authGate.style.display = visible ? 'flex' : 'none';
}

function findBoundShopForMall(shops = [], mallId = '') {
  const targetMallId = String(mallId || '').trim();
  if (!targetMallId) return null;
  return (Array.isArray(shops) ? shops : []).find((shop) => String(shop?.mallId || '').trim() === targetMallId) || null;
}

function hasValidShopAccess(shop = null) {
  if (!shop || typeof shop !== 'object') return false;
  if (shop.isMember === true || shop.membership?.isMember === true) return true;
  const expireAt = shop.expireAt || shop.expiresAt || shop.membershipExpireAt || shop.membership?.expireAt;
  if (!expireAt) return false;
  const expireTime = Date.parse(expireAt);
  return Number.isNaN(expireTime) || expireTime > Date.now();
}

function isCurrentShopAllowed(currentShopStatus, shops = [], mallId = '') {
  if (isAdminUser(getCurrentServerUser())) return true;
  if (!mallId) return (Array.isArray(shops) ? shops : []).some(hasValidShopAccess);
  if (currentShopStatus) {
    return Boolean(currentShopStatus.shopBound && currentShopStatus.isMember);
  }
  return hasValidShopAccess(findBoundShopForMall(shops, mallId));
}

async function getAutoBoundShopMap() {
  const localMap = readLocalJson(AUTO_BOUND_SHOPS_KEY, {});
  try {
    const stored = await chrome.storage.local.get([AUTO_BOUND_SHOPS_KEY]);
    return stored?.[AUTO_BOUND_SHOPS_KEY] || localMap || {};
  } catch {
    return localMap || {};
  }
}

async function setAutoBoundShopMap(map) {
  const safeMap = map && typeof map === 'object' ? map : {};
  localStorage.setItem(AUTO_BOUND_SHOPS_KEY, JSON.stringify(safeMap));
  try {
    await chrome.storage.local.set({ [AUTO_BOUND_SHOPS_KEY]: safeMap });
  } catch (error) {
    console.warn('[小青蛙电商助手] 保存自动绑定记录失败:', error?.message || error);
  }
}

async function bindCurrentShopSilently(token, user, shops = []) {
  if (!token || !user || isAdminUser(user)) return false;
  const mallId = String(state.mallId || '').trim();
  if (!mallId || findBoundShopForMall(shops, mallId)) return false;

  const userId = String(user.id || user.phone || 'current');
  const bindKey = `${userId}:${mallId}`;
  const bindMap = await getAutoBoundShopMap();
  if (bindMap[bindKey] && Date.now() - Number(bindMap[bindKey]) < 10 * 60 * 1000) {
    return false;
  }

  bindMap[bindKey] = Date.now();
  await setAutoBoundShopMap(bindMap);

  const resp = await fetch(`${SERVER_URL}/user/bind-shop`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
    body: JSON.stringify({ mallId, mallName: state.mallName || '' })
  });
  const result = await resp.json().catch(() => ({}));
  if (!resp.ok || !result.ok) {
    throw new Error(result.error || result.message || '店铺自动绑定失败');
  }
  return true;
}

async function submitDashboardShopAccess({ mallId, mallName = '', code = '', redeem = true }) {
  const currentMallId = String(mallId || '').trim();
  if (!currentMallId) {
    setDashboardShopActionResult('未找到店铺ID，无法继续', 'error');
    return false;
  }

  try {
    const token = await getServerAuthToken();
    if (!token) {
      setDashboardShopActionResult('请先登录会员账号', 'error');
      return false;
    }

    setDashboardShopActionResult(redeem ? `正在为店铺「${mallName || currentMallId}」续费...` : `正在绑定店铺「${mallName || currentMallId}」...`);

    const bindResp = await fetch(`${SERVER_URL}/user/bind-shop`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
      body: JSON.stringify({ mallId: currentMallId, mallName: mallName || '' })
    });
    const bindResult = await bindResp.json().catch(() => ({}));
    if (!bindResp.ok || !bindResult.ok) {
      throw new Error(bindResult.error || bindResult.message || '店铺绑定失败');
    }

    if (!redeem) {
      setDashboardShopActionResult(bindResult.message || `店铺「${mallName || currentMallId}」绑定成功`, 'success');
      await loadDashboardMembership();
      return true;
    }

    if (!code) {
      throw new Error('请输入店铺卡密');
    }

    const redeemResp = await fetch(`${SERVER_URL}/membership/shop-redeem`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
      body: JSON.stringify({ mallId: currentMallId, code })
    });
    const redeemResult = await redeemResp.json().catch(() => ({}));
    if (!redeemResp.ok || !redeemResult.ok) {
      throw new Error(redeemResult.error || redeemResult.message || '店铺授权失败');
    }

    setDashboardShopActionResult(redeemResult.message || `店铺「${mallName || currentMallId}」续费成功`, 'success');
    await loadDashboardMembership();
    return true;
  } catch (err) {
    console.error('[小青蛙电商助手] 店铺绑定/续费失败:', err);
    setDashboardShopActionResult(err?.message || '店铺绑定/续费失败', 'error');
    return false;
  }
}

async function bindCurrentDashboardShop() {
  const currentMallId = String(state.mallId || '').trim();
  if (!currentMallId) {
    setDashboardShopActionResult('当前未识别到店铺ID，请先打开卖家店铺页面', 'error');
    return;
  }
  const currentShop = Array.isArray(state.boundShops)
    ? state.boundShops.find((shop) => String(shop.mallId) === String(currentMallId))
    : null;
  await submitDashboardShopAccess({
    mallId: currentMallId,
    mallName: currentShop?.mallName || state.mallName || '',
    redeem: false
  });
}

function bindShopRedeemAction() {
  const btn = document.getElementById('dashRedeemBtn');
  if (!btn) return;
  const runAction = async (shouldRedeem = true) => {
    const mallId = document.getElementById('dashBindMallId')?.value?.trim() || state.mallId || '';
    const code = document.getElementById('dashCardInput')?.value?.trim();
    const resultEl = document.getElementById('dashRedeemResult');
    if (!mallId) {
      if (resultEl) {
        resultEl.style.display = 'block';
        resultEl.style.color = '#ff4d4f';
        resultEl.textContent = '请输入店铺ID';
      }
      return;
    }
    if (shouldRedeem && !code) {
      if (resultEl) {
        resultEl.style.display = 'block';
        resultEl.style.color = '#ff4d4f';
        resultEl.textContent = '请输入店铺授权码';
      }
      return;
    }
    try {
      const token = await getServerAuthToken();
      if (!token) {
        if (resultEl) {
          resultEl.style.display = 'block';
          resultEl.style.color = '#ff4d4f';
          resultEl.textContent = '请先登录会员账号';
        }
        return;
      }

      if (resultEl) {
        resultEl.style.display = 'block';
        resultEl.style.color = '#666';
        resultEl.textContent = shouldRedeem ? '正在绑定并授权...' : '正在绑定店铺...';
      }

      const bindResp = await fetch(`${SERVER_URL}/user/bind-shop`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
        body: JSON.stringify({ mallId })
      });
      const bindResult = await bindResp.json();
      if (!bindResp.ok || !bindResult.ok) {
        throw new Error(bindResult.error || bindResult.message || '店铺绑定失败');
      }

      if (!shouldRedeem) {
        if (resultEl) {
          resultEl.style.display = 'block';
          resultEl.style.color = '#52c41a';
          resultEl.textContent = bindResult.message || '店铺绑定成功，请继续输入授权码完成开通';
        }
        await loadDashboardMembership();
        return;
      }

      const redeemResp = await fetch(`${SERVER_URL}/membership/shop-redeem`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
        body: JSON.stringify({ mallId, code })
      });
      const redeemResult = await redeemResp.json();
      if (!redeemResp.ok || !redeemResult.ok) {
        throw new Error(redeemResult.error || redeemResult.message || '店铺授权失败');
      }

      if (resultEl) {
        resultEl.style.display = 'block';
        resultEl.style.color = '#52c41a';
        resultEl.textContent = redeemResult.message || '店铺绑定并授权成功';
      }
      await loadDashboardMembership();
    } catch (err) {
      if (resultEl) {
        resultEl.style.display = 'block';
        resultEl.style.color = '#ff4d4f';
        resultEl.textContent = err?.message || '店铺绑定/授权失败';
      }
    }
  };
  btn.onclick = () => runAction(true);
}

function renderDashboardAccessPanels(user, membership, shops, currentShopStatus = null) {
  const dashboard = document.getElementById('dashboard');
  if (!dashboard) return;
  const accountCard = dashboard.querySelector('#dashAccountPanel');
  const shopCard = dashboard.querySelector('#dashShopPanel');
  const renewCard = document.getElementById('dashRenewSection');
  const admin = isAdminUser(user);
  const shopList = (Array.isArray(shops) ? shops : []).map(hydrateShopNameFromCache);
  rememberShopNames(shopList, {
    mallId: currentShopStatus?.mallId || state.mallId,
    mallName: currentShopStatus?.shopName || currentShopStatus?.mallName || state.mallName
  });
  const boundCount = shopList.length;
  const authorizedCount = admin ? shopList.length : shopList.filter(hasValidShopAccess).length;
  const hasCurrentShop = Boolean(currentShopStatus?.mallId || state.mallId);
  const currentShopId = currentShopStatus?.mallId || state.mallId || '';
  const currentBoundShop = hasCurrentShop ? shopList.find((shop) => String(shop.mallId) === String(currentShopId)) : null;
  const currentShopName = hasCurrentShop
    ? (pickShopName(
        currentShopStatus?.shopName,
        currentShopStatus?.mallName,
        currentBoundShop?.shopName,
        currentBoundShop?.mallName,
        state.mallName,
        getCachedShopName(currentShopId)
      ) || `店铺 ${currentShopId}`)
    : getCurrentShopDisplayName();
  const currentShopBound = admin ? true : (hasCurrentShop ? Boolean(currentShopStatus?.shopBound || currentBoundShop) : boundCount > 0);
  const currentShopAuthorized = admin ? true : (hasCurrentShop
    ? Boolean(currentShopStatus?.isMember || hasValidShopAccess(currentBoundShop))
    : authorizedCount > 0);
  const currentShopTone = currentShopAuthorized ? '#16a34a' : currentShopBound ? '#d97706' : '#dc2626';
  const currentShopBg = currentShopAuthorized ? '#ecfdf5' : currentShopBound ? '#fff7ed' : '#fef2f2';
  const currentShopText = !currentShopBound
    ? '当前账号暂无绑定店铺'
    : currentShopAuthorized
      ? (hasCurrentShop
        ? (admin ? '管理员可直接使用' : `${currentShopStatus?.membership?.planName || currentBoundShop?.planName || '店铺会员'} · 剩余${Math.max(0, (currentShopStatus?.membership?.daysLeft ?? currentBoundShop?.daysLeft) ?? 0)}天`)
        : (admin ? '管理员已登录，执行前连接 TEMU 店铺' : '服务器店铺授权已加载，执行前连接 TEMU 店铺'))
      : '已绑定店铺，待授权';
  const currentShopBadgeText = admin
    ? '管理员已登录'
    : (currentShopAuthorized ? '账号已授权' : currentShopBound ? '待授权' : '未绑定');
  const currentShopStatusBadge = currentShopAuthorized
    ? `<span style="display:inline-flex;align-items:center;padding:3px 8px;border-radius:999px;background:#dcfce7;color:#166534;font-size:12px;font-weight:800;">${escapeHtml(currentShopBadgeText)}</span>`
    : `<span style="display:inline-flex;align-items:center;padding:3px 8px;border-radius:999px;background:#fff7ed;color:#c2410c;font-size:12px;font-weight:800;">${escapeHtml(currentShopBadgeText)}</span>`;
  const shouldShowReconnectBindButton = !admin && (!hasCurrentShop || !currentShopBound);
  const topAuthStatusEl = document.getElementById('topAuthStatus');
  if (topAuthStatusEl) {
    topAuthStatusEl.textContent = currentShopBadgeText;
    topAuthStatusEl.className = `status-badge ${currentShopAuthorized ? 'status-authorized' : 'status-disconnected'}`;
    topAuthStatusEl.style.display = user ? 'inline-flex' : 'none';
  }
  const roleText = user?.role === 'admin' ? '管理员' : user?.role === 'operator' ? '运营人员' : user?.role ? String(user.role) : '—';
  const statusText = user?.status === 'active' ? '正常' : user?.status === 'inactive' ? '停用' : user?.status ? String(user.status) : '—';
  const shopRows = shopList.length > 0
    ? shopList.map((shop, index) => {
        const hasAuth = admin || hasValidShopAccess(shop);
        const isCurrentShop = state.mallId && String(shop.mallId) === String(state.mallId);
        const daysLeft = shop.daysLeft != null ? Math.max(0, shop.daysLeft) : (shop.expireAt ? Math.max(0, Math.ceil((new Date(shop.expireAt) - new Date()) / 86400000)) : null);
        const statusLabel = hasAuth
          ? (daysLeft === 0 && shop.expireAt ? '已过期' : '已授权')
          : '未授权';
        const statusBg = statusLabel === '已授权'
          ? '#dcfce7'
          : statusLabel === '已过期'
            ? '#fee2e2'
            : '#fef3c7';
        const statusColor = statusLabel === '已授权'
          ? '#166534'
          : statusLabel === '已过期'
            ? '#b91c1c'
            : '#b45309';
        const rowShopName = pickShopName(shop.shopName, shop.mallName, shop.storeName, shop.name, isCurrentShop ? state.mallName : null, getCachedShopName(shop.mallId))
          || (shop.mallId ? `店铺 ${shop.mallId}` : '未命名店铺');
        const expireText = admin
          ? '管理员账号可直接使用'
          : hasAuth
            ? `授权至 ${formatDashboardDate(shop.expireAt)}${daysLeft != null ? ` · 剩余 ${daysLeft} 天` : ''}`
            : '该店铺暂未完成授权';
        return `
          <tr class="account-shop-row${isCurrentShop ? ' is-current' : ''}">
            <td class="account-shop-index">${index + 1}</td>
            <td>
              <div class="account-shop-name">
                <span>${escapeHtml(rowShopName)}</span>
                ${isCurrentShop ? '<span style="font-size:12px;font-weight:800;padding:2px 8px;border-radius:999px;background:#dbeafe;color:#2563eb;">当前连接</span>' : ''}
              </div>
              <div class="account-shop-meta">店铺ID：${escapeHtml(shop.mallId || '—')}</div>
            </td>
            <td>
              <span style="display:inline-flex;align-items:center;padding:3px 9px;border-radius:999px;background:${statusBg};color:${statusColor};font-size:12px;font-weight:800;">${escapeHtml(statusLabel)}</span>
              <div class="account-shop-expire">${escapeHtml(expireText)}</div>
            </td>
            <td>
              <div class="account-shop-actions">
                ${admin ? '<span style="font-size:12px;color:#64748b;">无需卡密授权</span>' : `<button class="btn btn-primary btn-sm" type="button" data-shop-action="renew" data-shop-id="${escapeHtml(shop.mallId || '')}" data-shop-name="${escapeHtml(rowShopName)}">续费</button><button class="btn btn-default btn-sm" type="button" data-shop-action="unbind" data-shop-id="${escapeHtml(shop.mallId || '')}" data-shop-name="${escapeHtml(rowShopName)}">解绑店铺</button>`}
              </div>
            </td>
          </tr>
        `;
      }).join('')
    : `
      <tr>
        <td colspan="4" style="padding:24px 14px;text-align:center;color:#64748b;">
          暂无绑定店铺，连接 TEMU 店铺后可在这里完成绑定和授权。
        </td>
      </tr>
    `;

  const shopListCardHtml = `
    <div class="account-shop-list-card" style="margin:0;border:0;padding:0;box-shadow:none;background:transparent;">
      <div class="account-shop-list-head">
        <div class="account-shop-list-title">店铺列表</div>
        <span class="account-shop-list-count">共 ${boundCount} 个</span>
      </div>
      <div class="account-shop-table-wrap" style="max-height:205px;overflow:auto;">
        <table class="account-shop-table">
          <thead>
            <tr>
              <th style="width:48px;text-align:center;">序号</th>
              <th>店铺</th>
              <th>授权状态</th>
              <th>操作</th>
            </tr>
          </thead>
          <tbody>${shopRows}</tbody>
        </table>
      </div>
    </div>
  `;

  if (accountCard) {
    accountCard.style.display = 'block';
    accountCard.innerHTML = `
      <div class="account-current-card">
        <div class="account-hero-row">
          <div class="account-current-main">
            <div class="account-current-label">当前 TEMU 店铺</div>
            <div id="mallInfo" class="account-current-title">${escapeHtml(currentShopName)}${currentShopId ? ` (${escapeHtml(currentShopId)})` : ''}</div>
            <div class="account-current-badges">
              <span id="connectionStatus" class="status-badge status-disconnected">未连接</span>
              <span id="topAuthStatus" class="status-badge ${currentShopAuthorized ? 'status-authorized' : 'status-disconnected'}" style="display:inline-flex;">${escapeHtml(currentShopBadgeText)}</span>
              <span style="display:inline-flex;align-items:center;padding:4px 10px;border-radius:999px;background:${currentShopBg};color:${currentShopTone};font-size:12px;font-weight:800;">${escapeHtml(currentShopText)}</span>
            </div>
          </div>
          <div class="account-current-actions">
            <button class="btn btn-primary btn-sm" id="reconnectBtn" type="button" title="重新检测店铺连接">🔄 重新连接</button>
            <button class="btn btn-default btn-sm" id="visitStoreBtn" type="button" title="在浏览器新标签页打开店铺页面">🏪 一键访问店铺</button>
            <button id="dashAuthLogoutBtn" class="btn btn-default btn-sm" type="button">退出登录</button>
            ${shouldShowReconnectBindButton ? `<button class="btn btn-primary btn-sm" type="button" id="dashBindCurrentShopBtn">${hasCurrentShop ? '绑定当前店铺' : '重新连接并绑定当前店铺'}</button>` : ''}
          </div>
        </div>
        <div class="account-current-side">
          <div class="account-mini-cell">
            <div class="account-mini-label">手机号</div>
            <div id="dashLoginPhone" class="account-mini-value">${escapeHtml(user?.phone || '—')}</div>
          </div>
          <div class="account-mini-cell">
            <div class="account-mini-label">运营人员</div>
            <div id="dashLoginNickname" class="account-mini-value">${escapeHtml(user?.nickname || user?.realName || user?.name || '—')}</div>
          </div>
          <div class="account-mini-cell">
            <div class="account-mini-label">店铺授权</div>
            <div class="account-mini-value">${boundCount} 个 / 已授权 ${authorizedCount} 个</div>
          </div>
        </div>
      </div>

      ${shopListCardHtml}
      <div id="dashShopActionResult" style="display:none;margin-top:10px;font-size:13px;"></div>
      <div id="dashAuthStatus" style="display:none;">已登录</div>
      <div id="dashAuthResult" style="margin-top:8px;font-size:13px;display:none;"></div>
    `;
    const dashShopListCard = document.getElementById('dashShopListCard');
    if (dashShopListCard) dashShopListCard.innerHTML = shopListCardHtml;
    const handleShopPanelClick = async (event) => {
      const logoutBtn = event.target.closest('#dashAuthLogoutBtn');
      if (logoutBtn) {
        await logoutDashboardAuth();
        return;
      }
      const bindCurrentBtn = event.target.closest('#dashBindCurrentShopBtn');
      if (bindCurrentBtn) {
        if (!state.mallId) {
          await reconnectCurrentShop();
        }
        await bindCurrentDashboardShop();
        return;
      }
      const reconnectBtn = event.target.closest('#reconnectBtn');
      if (reconnectBtn) {
        await reconnectCurrentShop();
        return;
      }
      const visitStoreBtn = event.target.closest('#visitStoreBtn');
      if (visitStoreBtn) {
        visitCurrentStore();
        return;
      }
      const button = event.target.closest('button[data-shop-action]');
      if (!button) return;
      const action = button.dataset.shopAction;
      const mallId = button.dataset.shopId || '';
      const mallName = button.dataset.shopName || '';
      if (action === 'renew') {
        const code = window.prompt(`请输入店铺「${mallName || mallId || '当前店铺'}」的卡密进行续费`, '')?.trim();
        if (!code) return;
        await submitDashboardShopAccess({ mallId, mallName, code, redeem: true });
        return;
      }
      if (action === 'card-renew') {
        const code = window.prompt(`请输入店铺「${mallName || mallId || '当前店铺'}」的卡密完成授权`, '')?.trim();
        if (!code) return;
        await submitDashboardShopAccess({ mallId, mallName, code, redeem: true });
        return;
      }
      if (action === 'unbind') {
        if (!mallId) {
          setDashboardShopActionResult('未找到店铺ID，无法解绑', 'error');
          return;
        }
        if (!window.confirm(`确定要解绑店铺「${mallName || mallId}」吗？`)) return;
        try {
          const token = await getServerAuthToken();
          if (!token) {
            setDashboardShopActionResult('请先登录会员账号', 'error');
            return;
          }
          setDashboardShopActionResult(`正在解绑店铺「${mallName || mallId}」...`);
          const resp = await fetch(`${SERVER_URL}/user/unbind-shop/${encodeURIComponent(mallId)}`, {
            method: 'DELETE',
            headers: { Authorization: `Bearer ${token}` }
          });
          const result = await resp.json().catch(() => ({}));
          if (!resp.ok || !result.ok) {
            throw new Error(result.error || result.message || '解绑失败');
          }
          setDashboardShopActionResult(`店铺「${mallName || mallId}」已解绑`, 'success');
          await loadDashboardMembership();
        } catch (err) {
          setDashboardShopActionResult(err?.message || '解绑失败', 'error');
        }
      }
    };
    accountCard.onclick = handleShopPanelClick;
    if (dashShopListCard) dashShopListCard.onclick = handleShopPanelClick;
    updateConnectionStatus(Boolean(state.mallId && state.antiContent), state.mallName);
  }

  if (shopCard) {
    shopCard.style.display = 'none';
    shopCard.innerHTML = '';
    shopCard.onclick = null;
  }

  if (renewCard) {
    renewCard.style.display = 'none';
    renewCard.style.marginTop = '14px';
    renewCard.style.border = '1px solid #e2e8f0';
    renewCard.style.borderRadius = '14px';
    renewCard.style.borderLeft = '4px solid #f59e0b';
    renewCard.style.boxShadow = 'none';
    renewCard.style.background = '#fff';
    bindShopRedeemAction();
  }
}

// 加载会员状态和绑定店铺
let dashAuthCooldownTimer = null;
let authCaptchaValue = '';

function setDashAuthResult(message, tone = 'info') {
  const resultEls = [
    document.getElementById('loginAuthResult'),
    document.getElementById('dashAuthResult')
  ].filter(Boolean);
  if (!resultEls.length) return;
  resultEls.forEach((resultEl) => {
    resultEl.style.display = 'block';
    resultEl.textContent = message;
    resultEl.style.color = tone === 'success' ? '#52c41a' : tone === 'error' ? '#ff4d4f' : '#666';
  });
  const dhResult = document.getElementById('dhTemuDashAuthResult');
  if (dhResult) {
    dhResult.style.display = message ? 'block' : 'none';
    dhResult.textContent = message || '';
    dhResult.style.color = tone === 'success' ? '#52c41a' : tone === 'error' ? '#ff4d4f' : '#667085';
  }
  syncDingHuiTemuHome();
}

function getTopbarAccountLabel(user) {
  if (!user) return '未登录';
  return user.nickname || user.realName || user.name || user.phone || '已登录账号';
}

function getTopbarAccountInitials(user) {
  if (!user) return 'XQ';
  const selected = getSelectedAuthPlatform();
  if (selected === 'ozon') return 'OZ';
  if (selected === 'temu') return 'TM';
  const label = getTopbarAccountLabel(user);
  return String(label).trim().slice(0, 2).toUpperCase() || 'XQ';
}

function renderTopbarAccount(user = null) {
  const wrap = document.querySelector('.topbar-right');
  if (!wrap) return;
  const roleText = user?.role === 'admin' ? '管理员' : user?.role === 'operator' ? '运营人员' : user ? '会员账号' : '未登录';
  const phone = user?.phone ? ` · ${user.phone}` : '';
  wrap.innerHTML = `
    <div class="topbar-account">
      <div class="topbar-avatar">${escapeHtml(getTopbarAccountInitials(user))}</div>
      <div class="topbar-account-main">
        <div class="topbar-account-name">${escapeHtml(getTopbarAccountLabel(user))}</div>
        <div class="topbar-account-meta">${escapeHtml(roleText)}${escapeHtml(phone)}</div>
      </div>
      ${user ? '<button class="topbar-account-action" id="topbarLogoutBtn" type="button">退出</button>' : ''}
    </div>
  `;
  const logoutBtn = document.getElementById('topbarLogoutBtn');
  if (logoutBtn && logoutBtn.dataset.bound !== '1') {
    logoutBtn.dataset.bound = '1';
    logoutBtn.addEventListener('click', () => logoutDashboardAuth());
  }
}


const AUTH_PLATFORM_KEY = 'xqwaSelectedPlatform';
const SCRIPT_PLATFORM_LOCK = 'ozon';

function normalizeAuthPlatform(value) {
  return String(value || '').toLowerCase() === 'ozon' ? 'ozon' : 'temu';
}

function getScriptPlatformLock() {
  return SCRIPT_PLATFORM_LOCK ? normalizeAuthPlatform(SCRIPT_PLATFORM_LOCK) : '';
}

function isWebsiteWorkspaceUrl() {
  return window.location.protocol === 'http:' || window.location.protocol === 'https:';
}

function getPlatformFromPath() {
  if (!isWebsiteWorkspaceUrl()) return '';
  const first = String(window.location.pathname || '').split('/').filter(Boolean)[0] || '';
  return first.toLowerCase() === 'ozon' ? 'ozon' : first.toLowerCase() === 'temu' ? 'temu' : '';
}

function isWorkspaceEntryPath() {
  if (!isWebsiteWorkspaceUrl()) return false;
  const parts = String(window.location.pathname || '').split('/').filter(Boolean);
  return parts.length === 1 && parts[0].toLowerCase() === 'app';
}

function buildPlatformUrl(platform) {
  const selected = getScriptPlatformLock() || normalizeAuthPlatform(platform);
  const next = new URL(window.location.href);
  if (isWebsiteWorkspaceUrl()) {
    next.pathname = `/${selected}/`;
    next.searchParams.delete('platform');
  } else {
    next.searchParams.set('platform', selected);
  }
  return next.toString();
}

function getSelectedAuthPlatform() {
  return normalizeAuthPlatform(getScriptPlatformLock() || window.__XQW_SELECTED_PLATFORM__ || getPlatformFromPath() || localStorage.getItem(AUTH_PLATFORM_KEY) || 'temu');
}

function applyAuthPlatform(platform, options = {}) {
  const selected = getScriptPlatformLock() || normalizeAuthPlatform(platform);
  window.__XQW_SELECTED_PLATFORM__ = selected;
  localStorage.setItem(AUTH_PLATFORM_KEY, selected);
  document.documentElement?.setAttribute?.('data-platform', selected);
  document.body?.setAttribute?.('data-platform', selected);
  updateAuthEntryPlatformText(selected);
  document.querySelectorAll('.auth-platform-btn[data-platform]').forEach((button) => {
    button.classList.toggle('active', normalizeAuthPlatform(button.dataset.platform) === selected);
  });
  if (options.updateUrl && typeof history?.replaceState === 'function') {
    history.replaceState(null, '', buildPlatformUrl(selected));
  }
  if (options.switchWorkspace) {
    switchWorkspaceForPlatform(selected);
  }
  return selected;
}

function updateAuthEntryPlatformText(platform = getSelectedAuthPlatform()) {
  const selected = normalizeAuthPlatform(platform);
  const textEl = document.getElementById('authEntryPlatformText');
  if (textEl) textEl.textContent = `当前入口：${selected === 'ozon' ? 'OZON' : 'TEMU'} 工作台`;
}

function syncAuthPlatformVisibility(mode = 'login') {
  void mode;
  const chooser = document.querySelector('.auth-platform-choice');
  if (!chooser) return;
  chooser.style.display = 'none';
}

function initAuthPlatformChoice() {
  const params = new URLSearchParams(window.location.search || '');
  const pathPlatform = getPlatformFromPath();
  const initial = getScriptPlatformLock() || normalizeAuthPlatform(pathPlatform || params.get('platform') || localStorage.getItem(AUTH_PLATFORM_KEY) || 'temu');
  document.querySelectorAll('.auth-platform-btn[data-platform]').forEach((button) => {
    if (button.dataset.platformBound === '1') return;
    button.dataset.platformBound = '1';
    button.addEventListener('click', (event) => {
      event.preventDefault();
      if (getScriptPlatformLock() && normalizeAuthPlatform(button.dataset.platform) !== getScriptPlatformLock()) return;
      applyAuthPlatform(button.dataset.platform, { updateUrl: true, switchWorkspace: Boolean(localStorage.getItem('temuServerToken')) });
    });
  });
  applyAuthPlatform(initial, { updateUrl: params.has('platform') || Boolean(pathPlatform) || isWorkspaceEntryPath() });
  syncAuthPlatformVisibility('login');
}


const TEMU_TAB_IDS = new Set(['dashboard', 'salesdata', 'compliance', 'livephotos', 'pricing', 'customized', 'activity', 'qualification', 'collection', 'finance']);
const OZON_TAB_IDS = new Set(['ozonHome', 'ozonCollection', 'ozonProducts', 'ozonProductOnline', 'ozonUpload', 'ozonUploadTest', 'ozonOrders', 'ozonOrderList', 'ozonAds', 'ozonTraffic', 'ozonMarketing', 'ozonFreight', 'ozonFinance', 'ozonMessages']);
const WORKSPACE_TAB_LABELS = {
  dashboard: { icon: '🏠', label: '首页' },
  salesdata: { icon: '📊', label: '销量查询' },
  compliance: { icon: '📋', label: '合规信息' },
  livephotos: { icon: '📷', label: '实拍图' },
  pricing: { icon: '💰', label: '核价' },
  customized: { icon: '🎨', label: '定制品' },
  activity: { icon: '🎉', label: '活动报名' },
  qualification: { icon: '📄', label: '资质上传' },
  collection: { icon: '🛒', label: '店铺搬家' },
  finance: { icon: '💳', label: '财务数据' },
  ozonHome: { icon: '🏠', label: '首页' },
  ozonOrders: { icon: '📦', label: '订单分析' },
  ozonOrderList: { icon: '🧾', label: '订单列表' },
  ozonProducts: { icon: '🤖', label: 'AI商品诊断' },
  ozonProductOnline: { icon: '📦', label: '商品列表' },
  ozonUpload: { icon: '⬆', label: '批量上架' },
  ozonUploadTest: { icon: '⬆', label: '批量上架（测试）' },
  ozonAds: { icon: '📣', label: '广告分析' },
  ozonTraffic: { icon: '📈', label: '流量分析' },
  ozonMarketing: { icon: '🎯', label: '营销活动' },
  ozonCollection: { icon: '🔗', label: '选品链接' },
  ozonFreight: { icon: '🚚', label: '运费计算' },
  ozonFinance: { icon: '💳', label: '财务中心' },
  ozonMessages: { icon: '💬', label: '消息中心' }
};

function normalizeWorkspaceTabLabels(root = document) {
  root.querySelectorAll?.('.tab.nav-item[data-tab]')?.forEach((tab) => {
    const meta = WORKSPACE_TAB_LABELS[tab.dataset.tab || ''];
    if (!meta) return;
    tab.dataset.navLabel = meta.label;
    if ((tab.dataset.tab || '').startsWith('ozon')) {
      delete tab.dataset.navIcon;
      tab.replaceChildren(document.createTextNode(meta.label));
      return;
    }
    tab.dataset.navIcon = meta.icon;
    tab.replaceChildren(document.createTextNode(meta.icon));
    const label = document.createElement('span');
    label.textContent = meta.label;
    label.className = 'nav-primary';
    tab.appendChild(label);
  });
}
const OZON_COLLECTION_STATUS_LABELS = {
  collected: '已采集',
  selected: '选品池',
  ready: '准备上架',
  listed: '已上架',
  archived: '已归档'
};
const OZON_RESIZABLE_TABLE_STORAGE_PREFIX = 'xqwa:ozon:columnWidths:v3:';
const OZON_SALES_ANALYSIS_COLUMN_DEFAULTS = [
  100, 64, 86, 58, 68, 58, 58, 60, 66, 72, 66, 70, 320, 350, 170
];
const OZON_SALES_ANALYSIS_COLUMN_MINS = [
  78, 56, 72, 52, 58, 52, 52, 54, 56, 60, 56, 58, 220, 240, 136
];
const OZON_ORDER_LIST_COLUMN_DEFAULTS = [
  32, 250, 68, 58, 104, 88, 74, 80, 72, 76, 80, 72, 112, 104, 152, 148
];
const OZON_ORDER_LIST_COLUMN_MINS = [
  30, 230, 60, 52, 90, 80, 64, 68, 62, 66, 68, 62, 96, 86, 128, 132
];
const OZON_TRAFFIC_COLUMN_DEFAULTS = [
  92, 76, 120, 72, 72, 72, 72, 96, 76, 230
];
const OZON_TRAFFIC_COLUMN_MINS = [
  72, 64, 80, 58, 58, 58, 58, 78, 64, 140
];
const OZON_PRODUCT_DIAGNOSIS_COLUMN_DEFAULTS = [
  220, 88, 116, 88, 250, 300, 450, 140
];
const OZON_PRODUCT_DIAGNOSIS_COLUMN_MINS = [
  180, 78, 96, 78, 170, 200, 220, 108
];
const OZON_PRODUCT_LIST_COLUMN_DEFAULTS = [
  44, 347, 120, 150, 220, 120, 160, 170
];
const OZON_PRODUCT_LIST_COLUMN_MINS = [
  40, 220, 92, 110, 156, 76, 118, 108
];
const OZON_PRODUCT_LIST_TABLE_KEY = 'productOnlineListInfoNarrow20260715';
const OZON_PRODUCT_STATUS_TABS = [
  { key: 'ALL', label: '所有', values: [] },
  { key: 'IN_SALE', label: '销售中', values: ['IN_SALE', 'VISIBLE'] },
  { key: 'TO_SUPPLY', label: '准备出售', values: ['TO_SUPPLY'] },
  { key: 'ERROR', label: '错误', values: ['STATE_FAILED', 'VALIDATION_STATE_FAIL', 'ERROR'] },
  {
    key: 'WAIT_EDIT',
    label: '待修改',
    values: ['WAIT_EDIT', 'NEED_EDIT', 'PARTIAL_APPROVED']
  },
  { key: 'OFF_SALE', label: '已下架', values: ['OFF_SALE', 'HIDDEN', 'REMOVED_FROM_SALE'] },
  { key: 'ARCHIVED', label: '已归档', values: ['ARCHIVED'] }
];
const OZON_MESSAGE_CATEGORIES = {
  buyer: '买家',
  service: '客服',
  main: '主要',
  notification: '通知',
  promotion: '推广',
  training: '培训',
  finance: '金融服务'
};
const OZON_MESSAGE_CATEGORY_BADGE_IDS = {
  buyer: 'ozonMessageBuyerRailCount',
  service: 'ozonMessageServiceRailCount',
  main: 'ozonMessageMainRailCount',
  notification: 'ozonNotificationRailCount',
  promotion: 'ozonMessagePromotionRailCount',
  training: 'ozonMessageTrainingRailCount',
  finance: 'ozonMessageFinanceRailCount'
};
const ozonState = {
  accounts: [],
  products: [],
  orders: [],
  overview: null,
  overviewError: '',
  accountsError: '',
  homeTasks: null,
  collectionLinks: [],
  selectedAccountId: '',
  productKeyword: '',
  productVisibility: 'ALL',
  collectionStatus: '',
  collectionKeyword: '',
  productAccountId: '',
  productAccountTouched: false,
  productName: '',
  productSku: '',
  productListVisibility: 'ALL',
  productSelectedIds: new Set(),
  productSelectedRows: new Map(),
  productStatusCounts: {},
  productWarehouseCache: {},
  productStockSyncingKeys: new Set(),
  productListDiagnosis: null,
  productListDiagnosisLoading: false,
  productTotal: 0,
  productPage: 1,
  productLimit: 50,
  orderAnalysis: null,
  orderAnalysisLoading: false,
  orderAnalysisPage: 1,
  orderAnalysisLimit: 50,
  orderAnalysisTotal: 0,
  orderAnalysisView: 'all',
  orderAnalysisAiAdvice: {},
  orderAnalysisAiAdviceRunning: false,
  orderAnalysisAiAdviceRunningKeys: new Set(),
  orderAnalysisAiAdviceSeq: 0,
  productDiagnosis: null,
  productDiagnosisLoading: false,
  productDiagnosisAccountId: '',
  productDiagnosisSku: '',
  productDiagnosisRisk: '',
  productDiagnosisKeyword: '',
  productDiagnosisPage: 1,
  productDiagnosisLimit: 50,
  productDiagnosisAiRunning: false,
  productDiagnosisAiRunningKeys: new Set(),
  productDiagnosisAiPromise: null,
  productDiagnosisAiFingerprint: '',
  productDiagnosisAiCompletedFingerprints: new Set(),
  orderAccountId: '',
  orderAnalysisAccountId: '',
  orderAccountTouched: false,
  orderAnalysisSku: '',
  orderKeyword: '',
  orderStatus: 'all',
  orderBucketCounts: {},
  orderDateFrom: '',
  orderDateTo: '',
  orderStatusCounts: {},
  orderTotal: 0,
  orderPage: 1,
  orderLimit: 50,
  accountKeyword: '',
  selectedCollectionIds: new Set(),
  aiProviders: [],
  aiConfigs: [],
  selectedAiConfigId: '',
  aiDefaultConfig: null,
  performanceConfig: null,
  performanceAccountId: '',
  ads: null,
  adsLoading: false,
  traffic: null,
  trafficLoading: false,
  trafficPage: 1,
  trafficLimit: 50,
  adsState: '',
  adsType: '',
  adsKeyword: '',
  adsAccountId: '',
  trafficAccountId: '',
  trafficKeyword: '',
  marketing: null,
  marketingLoading: false,
  marketingAccountId: '',
  marketingView: 'actions',
  marketingProducts: null,
  marketingProductSource: 'joined',
  marketingSelectedAction: null,
  marketingDetailTab: 'joined',
  marketingDetailAutoAddDate: '',
  marketingProductKeyword: '',
  marketingProductPage: 1,
  marketingProductLimit: 50,
  marketingProductHydrateKey: '',
  marketingProductHydrating: false,
  marketingDetailSelectedIds: new Set(),
  marketingCandidateProducts: null,
  marketingCandidateKeyword: '',
  marketingCandidateSelectedIds: new Set(),
  marketingCandidateSelectedProducts: {},
  marketingCandidatePriceEdits: {},
  marketingCandidatePage: 1,
  marketingCandidateLimit: 100,
  marketingCandidateRequestSeq: 0,
  discountTasks: null,
  discountTaskStatus: 'NEW',
  finance: null,
  financeLoading: false,
  financeAccountId: '',
  messages: null,
  messagesLoading: false,
  notifications: null,
  messageHistoryById: {},
  messageAccountId: '',
  messageType: 'buyer',
  messageQuickFilter: '',
  messageSearch: '',
  messageAccountBadges: [],
  notificationDrawerOpen: false,
  selectedMessageId: '',
  messageSendingById: {},
  messageReplyDraftById: {},
  messagePollingTimer: null,
  messageEventSource: null,
  messagePollInFlight: false,
  messageLastAutoSyncAt: 0,
  messageListRenderSignature: '',
  messageAccountCardsRenderSignature: '',
  messageReadOverrideByKey: {},
  overviewRequestSeq: 0,
  orderAnalysisRequestSeq: 0,
  productDiagnosisRequestSeq: 0,
  productListDiagnosisRequestSeq: 0,
  uploadAccountId: '',
  uploadTemplates: [],
  uploadPrecheckRows: [],
  uploadPreviewUrls: [],
  uploadExcelFile: null,
  uploadImageFiles: [],
  uploadImageZipFile: null
};
let ozonInitialCacheLoadScheduled = false;
let ozonInitialCacheLoadedAuthAt = 0;
let ozonPostAuthLoadTimer = null;

function isOzonWorkspace() {
  return getSelectedAuthPlatform() === 'ozon';
}

function renderOzonTabShell(tabId = 'ozonHome') {
  if (!isOzonWorkspace()) return;
  ensureOzonTabSyncStamp(tabId);
  syncOzonAccountSelects();
  renderOzonAccounts();
  renderOzonOverview();
  if (tabId === 'ozonHome') {
    renderOzonHomeTasks();
  } else if (tabId === 'ozonCollection') {
    renderOzonCollectionLinks();
    renderOzonLinkDiagnosis();
  } else if (tabId === 'ozonProducts') {
    renderOzonProductDiagnosis();
  } else if (tabId === 'ozonProductOnline') {
    renderOzonProducts();
  } else if (tabId === 'ozonUpload') {
    renderOzonUploadTemplates();
  } else if (tabId === 'ozonUploadTest') {
    window.renderOzonUploadTest?.();
  } else if (tabId === 'ozonOrders') {
    renderOzonOrderAnalysis();
  } else if (tabId === 'ozonOrderList') {
    renderOzonOrders();
  } else if (tabId === 'ozonAds') {
    renderOzonAds();
  } else if (tabId === 'ozonTraffic') {
    renderOzonTraffic();
  } else if (tabId === 'ozonMarketing') {
    renderOzonMarketing();
  } else if (tabId === 'ozonFinance') {
    renderOzonFinance();
  } else if (tabId === 'ozonMessages') {
    renderOzonMessages();
  }
  updateOzonTabSyncStamps(tabId);
}

function scheduleOzonInitialCacheLoad(options = {}) {
  if (!isOzonWorkspace()) return;
  if (options.force) {
    ozonInitialCacheLoadScheduled = false;
    ozonInitialCacheLoadedAuthAt = 0;
  }
  if (ozonInitialCacheLoadScheduled) return;
  ozonInitialCacheLoadScheduled = true;
  window.setTimeout(async () => {
    if (!isOzonWorkspace()) {
      ozonInitialCacheLoadScheduled = false;
      return;
    }
    const auth = await getServerAuthState();
    const token = auth?.temuServerToken || await getServerAuthTokenForRead({ timeoutMs: options.authTimeoutMs ?? 3200 });
    const authAt = Number(auth?.temuServerAuthAt || 0);
    if (!token) {
      ozonInitialCacheLoadScheduled = false;
      return;
    }
    if (!options.force && authAt && ozonInitialCacheLoadedAuthAt === authAt) {
      ozonInitialCacheLoadScheduled = false;
      return;
    }
    const loadOptions = { allowStale: true, silent: true, keepStaleOnError: true, authTimeoutMs: 0 };
    Promise.allSettled([
      loadOzonAccounts(loadOptions),
      loadOzonOverview(loadOptions),
      loadOzonTraffic({ ...loadOptions, forceRefresh: false })
    ]).then((results) => {
      if (results.some(item => item.status === 'fulfilled')) {
        ozonInitialCacheLoadedAuthAt = authAt || Number(localStorage.getItem('temuServerAuthAt') || Date.now()) || Date.now();
      }
    }).catch(err => {
      console.warn('[xiaoqingwa][OZON] initial light cache load failed:', err);
    }).finally(() => {
      ozonInitialCacheLoadScheduled = false;
    });
  }, Math.max(0, Number(options.delayMs ?? 800) || 0));
}

function scheduleOzonHomeBackgroundLoad() {
  if (!isOzonWorkspace()) return;
  window.setTimeout(() => {
    if (!isOzonWorkspace()) return;
    const activeTabId = document.querySelector('.section.active')?.id || '';
    if (activeTabId !== 'ozonHome') return;
    Promise.allSettled([
      loadOzonHomeTasks({ allowStale: true, silent: true, keepStaleOnError: true })
    ]).catch(err => console.warn('[xiaoqingwa][OZON] home background load failed:', err));
  }, 1200);
}

function scheduleOzonPostAuthLoad(options = {}) {
  if (!isOzonWorkspace()) return;
  if (ozonPostAuthLoadTimer) window.clearTimeout(ozonPostAuthLoadTimer);
  ozonPostAuthLoadTimer = window.setTimeout(async () => {
    ozonPostAuthLoadTimer = null;
    if (!isOzonWorkspace()) return;
    const token = await getServerAuthTokenForRead({ timeoutMs: options.authTimeoutMs ?? 4200 });
    if (!token) return;
    if (options.forceInitialCache) {
      scheduleOzonInitialCacheLoad({ force: true, delayMs: 0, authTimeoutMs: 0 });
    }
    await loadOzonDashboard({ authTimeoutMs: 0 });
    scheduleOzonHomeBackgroundLoad();
  }, Math.max(0, Number(options.delayMs ?? 180) || 0));
}

function switchWorkspaceForPlatform(platform = getSelectedAuthPlatform()) {
  const selected = normalizeAuthPlatform(platform);
  normalizeWorkspaceTabLabels();
  document.body?.setAttribute('data-platform', selected);
  document.documentElement?.setAttribute('data-platform', selected);
  updateAuthEntryPlatformText(selected);
  getServerAuthState().then(data => {
    renderTopbarAccount(data?.temuServerToken ? data?.temuServerUser : null);
  }).catch(() => renderTopbarAccount(null));
  const logoText = document.querySelector('.sidebar-logo-text');
  if (logoText) logoText.textContent = selected === 'ozon' ? 'OZON 工作台' : 'TEMU 工作台';
  const topTitle = document.querySelector('.topbar h1');
  if (topTitle) topTitle.innerHTML = selected === 'ozon'
    ? '<span class="logo-icon">OZ</span> OZON 工作台'
    : '<span class="logo-icon">TM</span> TEMU 工作台';
  document.querySelectorAll('.tab.nav-item').forEach((tab) => {
    const tabId = tab.dataset.tab || '';
    const isOzonTab = OZON_TAB_IDS.has(tabId);
    if (selected === 'ozon') {
      tab.style.display = isOzonTab ? 'flex' : 'none';
      tab.disabled = !isOzonTab;
    } else if (isOzonTab) {
      tab.style.display = 'none';
      tab.disabled = true;
    } else {
      tab.style.display = '';
    }
  });
  const banner = document.getElementById('bridgeBanner');
  if (banner && selected === 'ozon') banner.style.display = 'none';
  if (selected === 'ozon') {
    const activeOzonSection = Array.from(document.querySelectorAll('.section.active')).find(section => OZON_TAB_IDS.has(section.id));
    const targetTabId = activeOzonSection?.id || 'ozonHome';
    document.querySelectorAll('.section').forEach(section => section.classList.remove('active'));
    document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
    document.getElementById(targetTabId)?.classList.add('active');
    document.querySelector(`.tab[data-tab="${targetTabId}"]`)?.classList.add('active');
    renderOzonTabShell(targetTabId);
    scheduleOzonInitialCacheLoad();
  } else if (Array.from(document.querySelectorAll('.section.active')).some(section => OZON_TAB_IDS.has(section.id))) {
    document.getElementById('dashboard')?.classList.add('active');
    document.querySelector('.tab[data-tab="dashboard"]')?.classList.add('active');
  }
}

function setOzonResult(message, tone = 'info') {
  const el = document.getElementById('ozonBindResult') || document.getElementById('ozonAiAnalysisResult');
  if (!el) return;
  el.style.display = 'block';
  el.textContent = message;
  el.style.color = tone === 'success' ? '#16a34a' : tone === 'error' ? '#dc2626' : '#475569';
}

function setOzonAuthModalResult(message = '', tone = 'info') {
  const el = document.getElementById('ozonAccountAuthModalResult');
  if (!el) return;
  el.textContent = message || '';
  el.style.color = tone === 'success' ? '#16a34a' : tone === 'error' ? '#dc2626' : '#475569';
}

function setOzonOrderSyncStatus(message, tone = 'info') {
  const el = document.getElementById('ozonOrderSyncStatus');
  if (!el) return;
  el.textContent = message;
  el.style.background = tone === 'error' ? '#fef2f2' : tone === 'success' ? '#f0fdf4' : '#eff6ff';
  el.style.color = tone === 'error' ? '#991b1b' : tone === 'success' ? '#166534' : '#1e3a8a';
}

const OZON_SYNC_PROGRESS_SUCCESS_HIDE_MS = 2200;
const ozonSyncProgressHideTimers = { product: null, order: null };

function clearOzonSyncProgressHideTimer(kind = 'product') {
  const timerKey = kind === 'order' ? 'order' : 'product';
  if (ozonSyncProgressHideTimers[timerKey]) {
    window.clearTimeout(ozonSyncProgressHideTimers[timerKey]);
    ozonSyncProgressHideTimers[timerKey] = null;
  }
}

function setOzonSyncProgress(kind = 'product', percent = 0, message = '', tone = 'info') {
  const timerKey = kind === 'order' ? 'order' : 'product';
  clearOzonSyncProgressHideTimer(timerKey);
  const idGroups = kind === 'order'
    ? [['ozonOrderSyncProgress', 'ozonOrderSyncProgressText', 'ozonOrderSyncProgressPercent', 'ozonOrderSyncProgressFill']]
    : [
      ['ozonProductSyncProgress', 'ozonProductSyncProgressText', 'ozonProductSyncProgressPercent', 'ozonProductSyncProgressFill'],
      ['ozonProductOnlineSyncProgress', 'ozonProductOnlineSyncProgressText', 'ozonProductOnlineSyncProgressPercent', 'ozonProductOnlineSyncProgressFill']
    ];
  const byId = (id) => Array.from(document.querySelectorAll(`[id="${id}"]`));
  const cards = idGroups.flatMap(([cardId]) => byId(cardId));
  const texts = idGroups.flatMap(([, textId]) => byId(textId));
  const percentEls = idGroups.flatMap(([, , percentId]) => byId(percentId));
  const fills = idGroups.flatMap(([, , , fillId]) => byId(fillId));
  const value = Math.max(0, Math.min(100, Math.round(Number(percent) || 0)));
  cards.forEach((card) => {
    card.classList.add('is-visible');
    card.classList.toggle('is-success', tone === 'success');
    card.classList.toggle('is-error', tone === 'error');
  });
  texts.forEach(text => { text.textContent = message || '正在同步...'; });
  percentEls.forEach(percentEl => { percentEl.textContent = `${value}%`; });
  fills.forEach(fill => { fill.style.width = `${value}%`; });
  if (kind === 'order') setOzonOrderSyncStatus(message, tone);
  else setOzonResult(message, tone);
  if (tone === 'success' && value >= 100) {
    hideOzonSyncProgress(timerKey, OZON_SYNC_PROGRESS_SUCCESS_HIDE_MS);
  }
}

function hideOzonSyncProgress(kind = 'product', delayMs = 0) {
  const timerKey = kind === 'order' ? 'order' : 'product';
  clearOzonSyncProgressHideTimer(timerKey);
  const cardIds = kind === 'order'
    ? ['ozonOrderSyncProgress']
    : ['ozonProductSyncProgress', 'ozonProductOnlineSyncProgress'];
  const hide = () => {
    cardIds.forEach(cardId => {
      document.querySelectorAll(`[id="${cardId}"]`).forEach(card => {
        card.classList.remove('is-visible', 'is-success', 'is-error');
      });
    });
  };
  const delay = Math.max(0, Number(delayMs || 0));
  if (delay) {
    ozonSyncProgressHideTimers[timerKey] = window.setTimeout(() => {
      ozonSyncProgressHideTimers[timerKey] = null;
      hide();
    }, delay);
  }
  else hide();
}

function setOzonSyncButtonsDisabled(disabled = false, kind = 'all') {
  const ids = kind === 'order'
    ? ['ozonSyncOrdersBtnList', 'ozonSyncHistoryOrdersBtnList']
    : kind === 'product'
      ? ['ozonSyncProductsBtn', 'ozonSyncProductsBtn2', 'ozonSyncSelectedProductsBtn', 'ozonBulkArchiveProductsBtn']
      : ['ozonSyncOrdersBtnList', 'ozonSyncHistoryOrdersBtnList', 'ozonSyncProductsBtn', 'ozonSyncProductsBtn2', 'ozonSyncSelectedProductsBtn'];
  ids.forEach(id => {
    const el = document.getElementById(id);
    if (el) el.disabled = disabled;
  });
}

function isOzonSyncTimeoutError(err) {
  return /同步请求超时|服务器读取超时|504|timeout/i.test(String(err?.message || err || ''));
}

function sleepOzon(ms = 0) {
  return new Promise(resolve => window.setTimeout(resolve, Math.max(0, Number(ms) || 0)));
}

async function waitForOzonProductSyncTask(accountId = '', startedAtMs = Date.now(), options = {}) {
  const normalizedAccountId = normalizeOzonText(accountId);
  const deadlineMs = Date.now() + Math.max(30000, Number(options.timeoutMs || 240000));
  const startedFloor = Math.max(0, Number(startedAtMs || 0) - 10000);
  let latestTask = null;
  while (Date.now() < deadlineMs) {
    const query = new URLSearchParams({ type: 'products', limit: '5' });
    if (normalizedAccountId) query.set('ozonAccountId', normalizedAccountId);
    const result = await ozonRequest(`/sync-tasks?${query.toString()}`, {
      cache: false,
      requestTimeoutMs: 12000
    });
    const tasks = Array.isArray(result.tasks) ? result.tasks : [];
    latestTask = tasks.find(task => {
      const created = new Date(task.createdAt || task.startedAt || 0).getTime();
      return (!startedFloor || !Number.isFinite(created) || created >= startedFloor) &&
        (!normalizedAccountId || normalizeOzonText(task.ozonAccountId || task.accountId) === normalizedAccountId);
    }) || tasks[0] || null;
    if (latestTask) {
      const progress = Number(latestTask.progress || 0);
      setOzonSyncProgress(
        'product',
        Math.max(8, Math.min(95, Number.isFinite(progress) ? progress : 50)),
        latestTask.message || '商品同步仍在服务器执行，正在等待结果...'
      );
      if (latestTask.status === 'success') return latestTask;
      if (latestTask.status === 'failed') {
        throw new Error(latestTask.errorText || latestTask.message || '商品同步失败');
      }
    } else {
      setOzonSyncProgress('product', 88, '商品同步仍在服务器执行，正在等待任务状态...');
    }
    await sleepOzon(5000);
  }
  throw new Error(latestTask?.message || '商品同步仍未返回最终结果，请稍后刷新商品列表查看服务器缓存');
}

function toggleOzonAccountAuthPanel(force) {
  const panel = document.getElementById('ozonAccountAuthModal') || document.getElementById('ozonAccountAuthPanel');
  const buttons = ['ozonToggleAuthPanelBtn']
    .map(id => document.getElementById(id))
    .filter(Boolean);
  if (!panel) return;
  const willOpen = typeof force === 'boolean' ? force : !panel.classList.contains('is-open');
  panel.classList.toggle('is-open', willOpen);
  panel.setAttribute('aria-hidden', willOpen ? 'false' : 'true');
  buttons.forEach((button) => {
    button.textContent = '添加店铺授权';
    button.setAttribute('aria-expanded', willOpen ? 'true' : 'false');
  });
  if (willOpen) {
    setOzonAuthModalResult('');
    window.setTimeout(() => document.getElementById('ozonShopTitleInput')?.focus(), 120);
  }
}

function getOzonCurrentUserCacheScope() {
  const user = readLocalJson('temuServerUser', {}) || {};
  return String(user.id || user.phone || 'guest').replace(/[^\w.-]/g, '_');
}

function getOzonCachePathBase(path = '') {
  const clean = String(path || '').split('?')[0].replace(/\/+$/, '') || '/';
  if (/^\/messages\/[^/]+\/history$/.test(clean)) return '/messages/history';
  const matched = Object.keys(OZON_READ_CACHE_TTLS)
    .sort((a, b) => b.length - a.length)
    .find(prefix => clean === prefix || clean.startsWith(`${prefix}/`));
  return matched || clean;
}

function getOzonReadCacheTtl(path = '') {
  return OZON_READ_CACHE_TTLS[getOzonCachePathBase(path)] || OZON_READ_CACHE_DEFAULT_TTL_MS;
}

function getOzonReadCacheKey(path = '') {
  return `${OZON_READ_CACHE_PREFIX}${OZON_READ_CACHE_VERSION}:${getOzonCurrentUserCacheScope()}:${String(path || '')}`;
}

function shouldPersistOzonReadCache(path = '', serialized = '') {
  const base = getOzonCachePathBase(path);
  if (OZON_SERVER_CACHE_ONLY_PATHS.has(base)) return false;
  if (OZON_READ_CACHE_MEMORY_ONLY_PATHS.has(base)) return false;
  return String(serialized || '').length <= OZON_READ_CACHE_LOCAL_MAX_CHARS;
}

function shouldReadOzonMemoryCache(path = '') {
  const base = getOzonCachePathBase(path);
  return !OZON_SERVER_CACHE_ONLY_PATHS.has(base);
}

function shouldUseOzonStaleLocalCache(path = '', options = {}) {
  const base = getOzonCachePathBase(path);
  return Boolean(options.allowLocalFallback || options.allowStale) && !OZON_SERVER_CACHE_ONLY_PATHS.has(base);
}

function shouldWriteOzonMemoryCache(path = '') {
  return shouldReadOzonMemoryCache(path);
}

function shouldWriteOzonPersistentCache(path = '', serialized = '') {
  return shouldPersistOzonReadCache(path, serialized);
}

function normalizeOzonReadCacheEntry(path = '', cached = null, options = {}) {
  if (!cached || cached.version !== OZON_READ_CACHE_VERSION || !cached.data) return null;
  const ttl = Number(options.ttlMs || cached.ttlMs || getOzonReadCacheTtl(path));
  const age = Date.now() - Number(cached.savedAt || 0);
  if (!options.allowStale && age > ttl) return null;
  return {
    data: cached.data,
    savedAt: cached.savedAt || 0,
    stale: age > ttl,
    age
  };
}

function readOzonReadCache(path = '', options = {}) {
  if (options.skipCache || options.force || options.method) return null;
  if (getOzonReadCacheTtl(path) <= 0) return null;
  const key = getOzonReadCacheKey(path);
  if (shouldReadOzonMemoryCache(path)) {
    const memoryEntry = normalizeOzonReadCacheEntry(path, ozonReadMemoryCache.get(key), options);
    if (memoryEntry) return memoryEntry;
  }
  if (!shouldUseOzonStaleLocalCache(path, options)) return null;
  return normalizeOzonReadCacheEntry(path, readLocalJson(key, null), options);
}

function writeOzonReadCache(path = '', data, options = {}) {
  if (!path || !data || options.skipCache) return;
  if (getOzonReadCacheTtl(path) <= 0) return;
  const key = getOzonReadCacheKey(path);
  const entry = {
    version: OZON_READ_CACHE_VERSION,
    savedAt: Date.now(),
    ttlMs: Number(options.ttlMs || getOzonReadCacheTtl(path)),
    data
  };
  if (shouldWriteOzonMemoryCache(path)) {
    ozonReadMemoryCache.set(key, entry);
    while (ozonReadMemoryCache.size > OZON_READ_CACHE_MEMORY_MAX) {
      const oldestKey = ozonReadMemoryCache.keys().next().value;
      if (!oldestKey) break;
      ozonReadMemoryCache.delete(oldestKey);
    }
  }
  let serialized = '';
  try {
    serialized = JSON.stringify(entry);
  } catch (_) {
    return;
  }
  if (!shouldWriteOzonPersistentCache(path, serialized)) return;
  writeLocalJson(key, entry);
}

function clearOzonReadCache() {
  try {
    const prefix = `${OZON_READ_CACHE_PREFIX}${OZON_READ_CACHE_VERSION}:${getOzonCurrentUserCacheScope()}:`;
    Array.from(ozonReadMemoryCache.keys()).forEach(key => {
      if (key.startsWith(prefix)) ozonReadMemoryCache.delete(key);
    });
    const keys = [];
    for (let index = 0; index < localStorage.length; index += 1) {
      const key = localStorage.key(index);
      if (key && key.startsWith(prefix)) keys.push(key);
    }
    keys.forEach(key => localStorage.removeItem(key));
  } catch (error) {
    console.warn('[小青蛙电商助手][OZON] 清理读取缓存失败:', error?.message || error);
  }
}

function clearOzonReadCacheForPath(path = '') {
  const target = String(path || '');
  if (!target) return;
  try {
    const prefix = `${OZON_READ_CACHE_PREFIX}${OZON_READ_CACHE_VERSION}:${getOzonCurrentUserCacheScope()}:${target}`;
    Array.from(ozonReadMemoryCache.keys()).forEach(key => {
      if (key.startsWith(prefix)) ozonReadMemoryCache.delete(key);
    });
    const keys = [];
    for (let index = 0; index < localStorage.length; index += 1) {
      const key = localStorage.key(index);
      if (key && key.startsWith(prefix)) keys.push(key);
    }
    keys.forEach(key => localStorage.removeItem(key));
  } catch (error) {
    console.warn('[小青蛙电商助手][OZON] 清理指定读取缓存失败:', error?.message || error);
  }
}

function clearOzonReadCacheByBase(pathBase = '') {
  const base = getOzonCachePathBase(pathBase);
  if (!base) return;
  try {
    const prefix = `${OZON_READ_CACHE_PREFIX}${OZON_READ_CACHE_VERSION}:${getOzonCurrentUserCacheScope()}:`;
    Array.from(ozonReadMemoryCache.keys()).forEach(key => {
      const path = key.slice(prefix.length);
      if (key.startsWith(prefix) && getOzonCachePathBase(path) === base) ozonReadMemoryCache.delete(key);
    });
    const keys = [];
    for (let index = 0; index < localStorage.length; index += 1) {
      const key = localStorage.key(index);
      if (!key || !key.startsWith(prefix)) continue;
      const path = key.slice(prefix.length);
      if (getOzonCachePathBase(path) === base) keys.push(key);
    }
    keys.forEach(key => localStorage.removeItem(key));
  } catch (error) {
    console.warn('[小青蛙电商助手][OZON] 清理同类读取缓存失败:', error?.message || error);
  }
}

function purgeOzonPersistentReadCache() {
  if (ozonPersistentReadCachePurged) return;
  ozonPersistentReadCachePurged = true;
  try {
    const keys = [];
    for (let index = 0; index < localStorage.length; index += 1) {
      const key = localStorage.key(index);
      if (key && key.startsWith(OZON_READ_CACHE_PREFIX)) keys.push(key);
    }
    keys.forEach(key => localStorage.removeItem(key));
  } catch (error) {
    console.warn('[小青蛙电商助手][OZON] 清理旧读取缓存失败:', error?.message || error);
  }
}

async function ozonRequest(path, options = {}) {
  const token = await getServerAuthTokenForRead({ timeoutMs: options.authTimeoutMs ?? 2200 });
  if (!token) throw new Error('\u8bf7\u5148\u767b\u5f55\u8d26\u53f7');
  const method = String(options.method || 'GET').toUpperCase();
  if (method === 'GET' && options.cache !== false && !options.forceRefresh) {
    const cached = readOzonReadCache(path, { allowStale: Boolean(options.allowStale), ttlMs: options.cacheTtlMs });
    if (cached && (!cached.stale || options.allowStale)) {
      return { ...cached.data, __fromOzonCache: true, __cacheSavedAt: cached.savedAt, __cacheStale: cached.stale };
    }
  }
  const headers = {
    'Authorization': `Bearer ${token}`,
    ...(options.body ? { 'Content-Type': 'application/json' } : {}),
    ...(options.headers || {})
  };
  const fetchOptions = { ...options, headers };
  delete fetchOptions.forceRefresh;
  delete fetchOptions.allowStale;
  delete fetchOptions.cacheTtlMs;
  delete fetchOptions.cache;
  const requestTimeoutMs = Math.max(0, Number(options.requestTimeoutMs || options.timeoutMs || 0) || 0);
  delete fetchOptions.requestTimeoutMs;
  delete fetchOptions.timeoutMs;
  delete fetchOptions.authTimeoutMs;
  const controller = requestTimeoutMs > 0 && typeof AbortController !== 'undefined' && !fetchOptions.signal
    ? new AbortController()
    : null;
  const timeoutId = controller
    ? window.setTimeout(() => controller.abort(), requestTimeoutMs)
    : null;
  if (controller) fetchOptions.signal = controller.signal;
  let resp;
  let result;
  try {
    resp = await fetch(`${SERVER_URL}/ozon${path}`, fetchOptions);
    result = await resp.json().catch(() => ({}));
  } catch (err) {
    if (err?.name === 'AbortError') {
      throw new Error(`服务器读取超时：${Math.round(requestTimeoutMs / 1000)}秒内未返回，请稍后重试`);
    }
    throw err;
  } finally {
    if (timeoutId) window.clearTimeout(timeoutId);
  }
  if (!resp.ok || result.ok === false) {
    if (resp.status === 504) {
      throw new Error('同步请求超时：已改为分段同步，请稍后重试或缩小日期范围');
    }
    if (result.code === 'OZON_SHOP_NOT_AUTHORIZED') {
      const error = new Error(result.error || '该 OZON 店铺未完成卡密授权，请先兑换店铺卡密');
      error.code = result.code;
      error.accountId = result.accountId || '';
      error.mallId = result.mallId || '';
      throw error;
    }
    const detail = [result.error || result.message || `HTTP ${resp.status}`, result.code, result.status ? `OZON ${result.status}` : '']
      .filter(Boolean)
      .join(' / ');
    throw new Error(detail);
  }
  if (method === 'GET' && options.cache !== false) {
    writeOzonReadCache(path, result, { ttlMs: options.cacheTtlMs });
  } else if (method !== 'GET') {
    clearOzonReadCache();
  }
  return result;
}

function applyOzonCachedResponse(path = '', applyResult, options = {}) {
  if (options.forceRefresh || options.cache === false || typeof applyResult !== 'function') return false;
  const cached = readOzonReadCache(path, { allowStale: true, ttlMs: options.cacheTtlMs });
  if (!cached?.data) return false;
  applyResult({
    ...cached.data,
    __fromOzonCache: true,
    __cacheSavedAt: cached.savedAt,
    __cacheStale: cached.stale
  });
  return true;
}

async function loadOzonCached(path = '', applyResult, options = {}) {
  const hadCached = applyOzonCachedResponse(path, applyResult, options);
  try {
    const result = await ozonRequest(path, {
      ...(options.requestOptions || {}),
      cache: options.cache,
      cacheTtlMs: options.cacheTtlMs,
      forceRefresh: Boolean(options.forceRefresh),
      allowStale: Boolean(options.allowStale),
      requestTimeoutMs: options.requestTimeoutMs || options.timeoutMs,
      authTimeoutMs: options.authTimeoutMs
    });
    if (typeof applyResult === 'function') applyResult(result);
    return result;
  } catch (err) {
    if (hadCached && options.keepStaleOnError !== false) {
      setOzonResult(`已显示本地缓存，最新数据刷新失败：${err.message || '网络异常'}`, 'warn');
      return null;
    }
    throw err;
  }
}

async function aiRequest(path, options = {}) {
  const token = await getServerAuthToken();
  if (!token) throw new Error('请先登录账号');
  const headers = {
    'Authorization': `Bearer ${token}`,
    ...(options.body ? { 'Content-Type': 'application/json' } : {}),
    ...(options.headers || {})
  };
  const resp = await fetch(`${SERVER_URL}/ai${path}`, { ...options, headers });
  const result = await resp.json().catch(() => ({}));
  if (!resp.ok || result.ok === false) {
    throw new Error(result.error || result.message || `HTTP ${resp.status}`);
  }
  return result;
}

function syncOzonAccountsToExtension() {
  try {
    localStorage.setItem('ozonAccounts', JSON.stringify(ozonState.accounts || []));
  } catch (_) {}
  try {
    globalThis.chrome?.storage?.local?.set?.({
      ozonAccounts: ozonState.accounts || []
    });
  } catch (_) {}
}

function restoreOzonAccountsFromLocalCache() {
  if (Array.isArray(ozonState.accounts) && ozonState.accounts.length) return ozonState.accounts;
  const cachedAccounts = readLocalJson('ozonAccounts', []);
  const accounts = Array.isArray(cachedAccounts)
    ? cachedAccounts.filter(account => account && account.id && account.status !== 'deleted' && account.status !== 'disabled')
    : [];
  if (!accounts.length) return [];
  ozonState.accounts = accounts;
  ensureOzonScopedAccountDefaults();
  syncOzonAccountSelects();
  return accounts;
}

const OZON_AI_DIAGNOSIS_BATCH_SIZE = 1;
const OZON_AI_DIAGNOSIS_MAX_ITEMS = 50;
const OZON_PRODUCT_DIAGNOSIS_RESPONSE_LIMIT = 50;
const OZON_AI_TITLE_BATCH_SIZE = OZON_AI_DIAGNOSIS_BATCH_SIZE;
const OZON_AI_TITLE_MAX_ITEMS = OZON_AI_DIAGNOSIS_MAX_ITEMS;

function setOzonAiStatus(message, tone = 'info') {
  const status = document.getElementById('ozonAiConfigStatus');
  if (status) {
    status.textContent = message;
    status.style.color = tone === 'success' ? '#16a34a' : tone === 'error' ? '#dc2626' : '#475569';
  }
}

function setOzonAiResult(message, tone = 'info', type = 'orders') {
  const targetId = type === 'links' ? 'ozonAiProductAnalysisResult' : 'ozonAiOrderAnalysisResult';
  const el = document.getElementById(targetId) || document.getElementById('ozonAiAnalysisResult');
  if (!el) return;
  el.textContent = message;
  el.classList.toggle('is-success', tone === 'success');
  el.classList.toggle('is-error', tone === 'error');
  if (!el.classList.contains('ozon-home-analysis-result')) {
    el.style.borderColor = tone === 'error' ? '#fecaca' : tone === 'success' ? '#bbf7d0' : '#c7d2fe';
    el.style.background = tone === 'error' ? '#fef2f2' : tone === 'success' ? '#f0fdf4' : '#eef2ff';
    el.style.color = tone === 'error' ? '#991b1b' : tone === 'success' ? '#166534' : '#312e81';
  }
}

function renderOzonAiConfig() {
  const config = ozonState.aiDefaultConfig || ozonState.aiConfigs[0] || null;
  if (config) {
    ozonState.selectedAiConfigId = String(config.id || '');
    setOzonAiStatus('AI分析已启用', 'success');
  } else {
    ozonState.selectedAiConfigId = '';
    setOzonAiStatus('AI分析暂不可用，请稍后再试。', 'error');
  }
}

async function loadOzonAiConfig() {
  const configsResult = await aiRequest('/configs?platform=ozon');
  ozonState.aiProviders = [];
  ozonState.aiConfigs = Array.isArray(configsResult.configs) ? configsResult.configs : [];
  ozonState.aiDefaultConfig = ozonState.aiConfigs.find(config => config.isDefault) || ozonState.aiConfigs[0] || null;
  renderOzonAiConfig();
}

function setOzonPerformanceModalResult(message, tone = 'info') {
  const el = document.getElementById('ozonPerformanceAuthModalResult');
  if (!el) return;
  el.textContent = message || '';
  el.style.display = message ? 'block' : 'none';
  el.style.color = tone === 'success' ? '#166534' : tone === 'error' ? '#991b1b' : '#334155';
  el.style.background = tone === 'success' ? '#f0fdf4' : tone === 'error' ? '#fef2f2' : '#f8fafc';
  el.style.borderColor = tone === 'success' ? '#bbf7d0' : tone === 'error' ? '#fecaca' : '#e2e8f0';
}

function getOzonPerformanceAccountId() {
  return getOzonActiveAccountId(
    ozonState.performanceAccountId
    || document.getElementById('ozonAdsAccountFilter')?.value
    || ozonState.adsAccountId
    || ''
  );
}

function renderOzonPerformanceConfig() {
  const config = ozonState.performanceConfig || null;
  const status = document.getElementById('ozonPerformanceStatusText');
  const accountId = getOzonPerformanceAccountId();
  const accountLabel = getOzonAccountLabel(accountId) || '默认店铺';
  if (status) {
    if (!config) {
      status.textContent = `${accountLabel} · 未绑定 Performance API`;
      status.className = 'ozon-muted';
    } else {
      const online = config.onlineStatus === 'online';
      status.textContent = `${accountLabel} · ${online ? '已连接' : '已保存'} · Secret ${config.clientSecretMasked || '***'}${config.lastTestAt ? ` · 最近检测 ${formatOzonDateTime(config.lastTestAt)}` : ''}`;
      status.className = online ? 'status-badge status-connected' : 'status-badge status-warning';
    }
  }
  const clientIdInput = document.getElementById('ozonPerformanceClientIdInput');
  const secretInput = document.getElementById('ozonPerformanceClientSecretInput');
  if (clientIdInput && config?.clientId) clientIdInput.value = config.clientId;
  if (secretInput) secretInput.value = '';
}

async function loadOzonPerformanceConfig(options = {}) {
  try {
    const accountId = getOzonPerformanceAccountId();
    const qs = new URLSearchParams();
    if (accountId) qs.set('accountId', accountId);
    await loadOzonCached(`/performance/config${qs.toString() ? `?${qs.toString()}` : ''}`, (result) => {
      ozonState.performanceConfig = result.config || null;
      renderOzonPerformanceConfig();
      renderOzonHomeAuthorizedAccounts(ozonState.accounts);
    }, { cache: false, ...options });
  } catch (err) {
    ozonState.performanceConfig = null;
    renderOzonPerformanceConfig();
    renderOzonHomeAuthorizedAccounts(ozonState.accounts);
  }
}

function toggleOzonPerformanceAuthPanel(force, accountId = '') {
  const modal = document.getElementById('ozonPerformanceAuthModal');
  if (!modal) return;
  if (modal.parentElement !== document.body) document.body.appendChild(modal);
  if (accountId) {
    ozonState.performanceAccountId = String(accountId);
    const select = document.getElementById('ozonAdsAccountFilter');
    if (select) select.value = String(accountId);
  }
  const shouldOpen = force === undefined ? modal.getAttribute('aria-hidden') !== 'false' : Boolean(force);
  modal.setAttribute('aria-hidden', shouldOpen ? 'false' : 'true');
  modal.classList.toggle('is-open', shouldOpen);
  if (shouldOpen) {
    setOzonText('ozonPerformanceModalAccountName', getOzonAccountLabel(getOzonPerformanceAccountId()) || '当前店铺');
    renderOzonPerformanceConfig();
    setOzonPerformanceModalResult('');
    window.setTimeout(() => document.getElementById('ozonPerformanceClientIdInput')?.focus(), 40);
  }
}

async function saveOzonPerformanceFromForm(checkOnly = false) {
  const accountId = getOzonPerformanceAccountId();
  const clientId = document.getElementById('ozonPerformanceClientIdInput')?.value?.trim();
  const clientSecret = document.getElementById('ozonPerformanceClientSecretInput')?.value?.trim();
  if (!accountId) {
    setOzonPerformanceModalResult('请先选择要绑定的 OZON 店铺', 'error');
    return;
  }
  if (!clientId || !clientSecret) {
    setOzonPerformanceModalResult('Performance Client ID 和 Client Secret 不能为空', 'error');
    return;
  }
  setOzonPerformanceModalResult(checkOnly ? '正在检测 Performance API...' : '正在保存并检测 Performance API...');
  try {
    const result = await ozonRequest(checkOnly ? '/performance/config/check' : '/performance/config', {
      method: 'POST',
      body: JSON.stringify({ accountId, clientId, clientSecret })
    });
    if (checkOnly) {
      setOzonPerformanceModalResult(result.text || 'Performance API 在线', 'success');
    } else {
      document.getElementById('ozonPerformanceClientSecretInput').value = '';
      ozonState.performanceConfig = result.config || null;
      ozonState.performanceAccountId = accountId;
      renderOzonPerformanceConfig();
      await loadOzonAccounts({ forceRefresh: true }).catch(() => null);
      renderOzonHomeAuthorizedAccounts(ozonState.accounts);
      setOzonPerformanceModalResult('Performance API 已保存并校验通过', 'success');
      setOzonResult('OZON Performance API 已连接', 'success');
      if (document.getElementById('ozonAds')?.classList.contains('active')) {
        await loadOzonAds().catch(() => null);
      }
      window.setTimeout(() => toggleOzonPerformanceAuthPanel(false), 650);
    }
  } catch (err) {
    setOzonPerformanceModalResult(err.message || 'Performance API 操作失败', 'error');
  }
}

async function ensureOzonAiSourceData(type = 'overall') {
  const tasks = [];
  if (!ozonState.orderAnalysis) tasks.push(loadOzonOrderAnalysis());
  if (!ozonState.productDiagnosis) tasks.push(loadOzonProductDiagnosis({ enrichAi: false, resetAiQueue: false }));
  if (type === 'links' && !ozonState.collectionLinks.length) tasks.push(loadOzonCollectionLinks());
  if (tasks.length) await Promise.allSettled(tasks);
}

function compactOzonAiOrderAnalysis(source = {}) {
  const items = Array.isArray(source.items) ? source.items : [];
  return {
    summary: source.summary || {},
    status: (Array.isArray(source.status) ? source.status : []).slice(0, 12),
    daily: (Array.isArray(source.daily) ? source.daily : []).slice(-28).map(row => ({
      date: row.date,
      orders: row.orders,
      qty: row.qty,
      amount: row.amount
    })),
    items: items.slice(0, 200).map(item => ({
      accountId: item.accountId,
      accountTitle: item.accountTitle,
      currencyCode: item.currencyCode || 'RUB',
      todayQty: item.todayQty || 0,
      monthAvg: item.monthAvg || 0,
      qty7: item.qty7 || 0,
      prevQty7: item.prevQty7 || 0,
      trend7Percent: item.trend7Percent || 0,
      qty28: item.qty28 || 0,
      prevQty28: item.prevQty28 || 0,
      trend28Percent: item.trend28Percent || 0,
      share28Percent: item.share28Percent || 0,
      tag: item.tag || '',
      alert: Boolean(item.alert)
    }))
  };
}

function compactOzonAiProductDiagnosis(source = {}) {
  const items = Array.isArray(source.items) ? source.items : [];
  const priorityItems = items
    .filter(item => item.diagnosis?.riskCode === 'high' || item.sales?.alert || Number(item.sales?.trend7Percent || 0) <= -20)
    .concat(items.filter(item => item.diagnosis?.riskCode === 'medium'))
    .concat(items.filter(item => Number(item.diagnosis?.score || 0) >= 85));
  const seen = new Set();
  const rows = priorityItems.filter(item => {
    const ui = normalizeOzonProductForUi(item.product || {});
    const key = ui.productId || ui.offerId || ui.sku || ui.title;
    if (!key || seen.has(key)) return false;
    seen.add(key);
    return true;
  }).slice(0, 100);
  return {
    summary: source.summary || {},
    items: rows.map(item => {
      const ui = normalizeOzonProductForUi(item.product || {});
      const diag = item.diagnosis || {};
      const sales = item.sales || {};
      return {
        product: {
          productId: ui.productId,
          offerId: ui.offerId,
          sku: ui.sku,
          name: ui.title,
          accountTitle: ui.accountTitle,
          statusLabel: ui.statusLabel,
          priceIndex: ui.priceIndex?.label,
          stock: ui.stock?.available
        },
        sales: {
          qty7: sales.qty7 || 0,
          qty28: sales.qty28 || 0,
          todayQty: sales.todayQty || 0,
          trend7Percent: sales.trend7Percent || 0,
          trend28Percent: sales.trend28Percent || 0,
          alert: Boolean(sales.alert)
        },
        diagnosis: {
          score: diag.score,
          riskCode: diag.riskCode,
          riskLevel: diag.riskLevel,
          imageCount: diag.imageCount,
          stock: diag.stock,
          priceIndex: diag.priceIndex,
          problems: (diag.problems || []).slice(0, 4),
          suggestions: (diag.suggestions || []).slice(0, 4),
          optimizedTitle: diag.optimizedTitle,
          optimizedKeywords: diag.optimizedKeywords
        }
      };
    })
  };
}

function compactOzonAiLinks(links = []) {
  return (Array.isArray(links) ? links : []).slice(0, 50).map(link => ({
    title: link.title,
    url: link.url,
    priceText: link.priceText,
    status: link.status,
    productId: link.productId,
    accountTitle: link.accountTitle
  }));
}

async function runOzonAiAnalysis(type = 'overall') {
  setOzonAiResult(type === 'links' ? '正在分析产品链接...' : '正在分析订单与商品数据...', 'info', type);
  try {
    await ensureOzonAiSourceData(type);
    const result = await aiRequest('/ozon/analyze', {
      method: 'POST',
      body: JSON.stringify({
        type,
        prompt: '',
        ordersAnalysis: compactOzonAiOrderAnalysis(ozonState.orderAnalysis || {}),
        productsAnalysis: compactOzonAiProductDiagnosis(ozonState.productDiagnosis || {}),
        links: compactOzonAiLinks(ozonState.collectionLinks || [])
      })
    });
    setOzonAiResult(result.text || '模型已返回分析结果。', 'success', type);
  } catch (err) {
    const message = String(err.message || 'AI不可用：请稍后再试');
    setOzonAiResult(message.startsWith('AI不可用') ? message : `AI不可用：${message}`, 'error', type);
  }
}

function getOzonStatusLabel(status = '') {
  return OZON_COLLECTION_STATUS_LABELS[status] || status || '已采集';
}

function setOzonCollectionCounts() {
  const all = ozonState.collectionLinks || [];
  const active = all.filter(link => link.status !== 'archived');
  const selectedCount = active.filter(link => link.status === 'selected').length;
  const readyCount = active.filter(link => link.status === 'ready').length;
  const listedCount = active.filter(link => link.status === 'listed').length;
  const collectionEl = document.getElementById('ozonCollectionCount');
  const selectedEl = document.getElementById('ozonSelectedCount');
  const readyEl = document.getElementById('ozonReadyCount');
  const listedEl = document.getElementById('ozonListedCount');
  if (collectionEl) collectionEl.textContent = String(active.length || 0);
  if (selectedEl) selectedEl.textContent = String(selectedCount || 0);
  if (readyEl) readyEl.textContent = String(readyCount || 0);
  if (listedEl) listedEl.textContent = String(listedCount || 0);
}

function normalizeOzonCurrencyCode(currency = 'RUB') {
  const code = normalizeOzonText(currency || 'RUB').toUpperCase();
  if (code === 'RMB') return 'CNY';
  return code || 'RUB';
}

function formatOzonAmount(value, currency = 'RUB') {
  const n = parseOzonNumber(value);
  if (!Number.isFinite(n)) return '--';
  const code = normalizeOzonCurrencyCode(currency);
  const formatted = n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  if (code === 'CNY') return `¥${formatted}`;
  if (code === 'RUB') return `${formatted} ₽`;
  if (code === 'USD') return `$${formatted}`;
  if (code === 'EUR') return `€${formatted}`;
  return `${formatted} ${code}`;
}

function formatOzonCnyAmount(value) {
  const n = parseOzonNumber(value);
  if (!Number.isFinite(n)) return '¥0.00';
  return `¥${n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
}

function formatOzonOrderAmount(value, currency = 'RUB') {
  return formatOzonAmount(value, currency || 'RUB');
}

function getOzonBusinessDateKey(value = new Date(), offsetMinutes = 8 * 60) {
  const date = value instanceof Date ? value : new Date(value);
  if (Number.isNaN(date.getTime())) return '';
  return new Date(date.getTime() + offsetMinutes * 60 * 1000).toISOString().slice(0, 10);
}

function getOzonTodayDateRange() {
  const today = getOzonBusinessDateKey();
  return { dateFrom: today, dateTo: today };
}

function getOzonRecentOrderSyncDateRange(days = 2) {
  const now = new Date();
  const today = getOzonBusinessDateKey(now);
  const startDate = new Date(now.getTime() - Math.max(1, Number(days || 2) - 1) * 24 * 60 * 60 * 1000);
  return { dateFrom: getOzonBusinessDateKey(startDate), dateTo: today };
}

function normalizeOzonDateKey(value) {
  const text = normalizeOzonText(value);
  return /^\d{4}-\d{2}-\d{2}$/.test(text) ? text : '';
}

function normalizeOzonDateInputValue(value) {
  const text = normalizeOzonText(value);
  if (!text) return '';
  const compact = text.replace(/[./\s年]/g, '-').replace(/[月]/g, '-').replace(/[日]/g, '').replace(/-+/g, '-').replace(/^-|-$/g, '');
  if (/^\d{8}$/.test(compact)) {
    return `${compact.slice(0, 4)}-${compact.slice(4, 6)}-${compact.slice(6, 8)}`;
  }
  const match = compact.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
  if (match) {
    const year = match[1];
    const month = match[2].padStart(2, '0');
    const day = match[3].padStart(2, '0');
    return `${year}-${month}-${day}`;
  }
  return text;
}

function isOzonValidDateInput(value) {
  if (!value) return true;
  if (!normalizeOzonDateKey(value)) return false;
  const date = ozonDateKeyToUtcDate(value);
  return Boolean(date && formatOzonUtcDateKey(date) === value);
}

function ozonDateKeyToUtcDate(value) {
  const key = normalizeOzonDateKey(value);
  if (!key) return null;
  const [year, month, day] = key.split('-').map(Number);
  return new Date(Date.UTC(year, month - 1, day));
}

function formatOzonUtcDateKey(date) {
  if (!(date instanceof Date) || Number.isNaN(date.getTime())) return '';
  return date.toISOString().slice(0, 10);
}

function addOzonDateKeyDays(value, days) {
  const date = ozonDateKeyToUtcDate(value);
  if (!date) return '';
  date.setUTCDate(date.getUTCDate() + Number(days || 0));
  return formatOzonUtcDateKey(date);
}

function formatOzonDateKeyShort(value) {
  const key = normalizeOzonDateKey(value);
  if (!key) return '--';
  return `${key.slice(5, 7)}/${key.slice(8, 10)}`;
}

function getOzonRollingSalesWindows(todayKey = getOzonBusinessDateKey()) {
  const today = normalizeOzonDateKey(todayKey) || getOzonBusinessDateKey();
  return {
    recent7: { from: addOzonDateKeyDays(today, -6), to: today },
    previous7: { from: addOzonDateKeyDays(today, -13), to: addOzonDateKeyDays(today, -7) },
    recent28: { from: addOzonDateKeyDays(today, -27), to: today },
    previous28: { from: addOzonDateKeyDays(today, -55), to: addOzonDateKeyDays(today, -28) }
  };
}

function renderOzonRollingSalesWindowHint() {
  const target = document.getElementById('ozonOrderAnalysisWindowHint');
  if (!target) return;
  const windows = getOzonRollingSalesWindows();
  const rangeText = range => `${formatOzonDateKeyShort(range.from)}-${formatOzonDateKeyShort(range.to)}`;
  target.innerHTML = [
    `<strong>近7日</strong> ${rangeText(windows.recent7)}`,
    `<strong>上个7日</strong> ${rangeText(windows.previous7)}`,
    `<strong>近28日</strong> ${rangeText(windows.recent28)}`,
    `<strong>上个28日</strong> ${rangeText(windows.previous28)}`
  ].join(' ｜ ');
}

function compareOzonDateKeys(left, right) {
  const a = ozonDateKeyToUtcDate(left);
  const b = ozonDateKeyToUtcDate(right);
  if (!a || !b) return 0;
  return a.getTime() - b.getTime();
}

function buildOzonOrderSyncDateChunks(dateFrom = '', dateTo = '', chunkDays = 30) {
  const today = getOzonBusinessDateKey();
  let end = normalizeOzonDateKey(dateTo) || today;
  let start = normalizeOzonDateKey(dateFrom) || addOzonDateKeyDays(end, -1094);
  if (compareOzonDateKeys(start, end) > 0) {
    const tmp = start;
    start = end;
    end = tmp;
  }
  const size = Math.max(1, Math.min(30, Number(chunkDays || 30)));
  const chunks = [];
  let cursorEnd = end;
  while (cursorEnd && compareOzonDateKeys(start, cursorEnd) <= 0 && chunks.length < 120) {
    const candidateStart = addOzonDateKeyDays(cursorEnd, -(size - 1));
    const chunkStart = compareOzonDateKeys(candidateStart, start) < 0 ? start : candidateStart;
    chunks.push({ dateFrom: chunkStart, dateTo: cursorEnd });
    cursorEnd = addOzonDateKeyDays(chunkStart, -1);
  }
  return chunks.length ? chunks : [{ dateFrom: start, dateTo: end }];
}

function getOzonFullHistoryOrderDateRange() {
  const dateTo = getOzonBusinessDateKey();
  return {
    dateFrom: addOzonDateKeyDays(dateTo, -1094),
    dateTo
  };
}

function normalizeOzonText(value) {
  if (value === null || value === undefined) return '';
  if (typeof value === 'object') {
    const candidates = [
      value.status_name,
      value.statusName,
      value.status_description,
      value.statusDescription,
      value.name,
      value.label,
      value.title,
      value.value,
      value.text
    ];
    const text = candidates.map(item => normalizeOzonText(item)).find(Boolean);
    if (text) return text;
    try {
      return JSON.stringify(value);
    } catch {
      return '';
    }
  }
  return String(value).trim();
}

function formatOzonStructuredText(value, depth = 0) {
  if (value === null || value === undefined || value === '') return '';
  if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
    return normalizeOzonText(value);
  }
  if (Array.isArray(value)) {
    return value.map(item => formatOzonStructuredText(item, depth + 1)).filter(Boolean).join('；');
  }
  if (typeof value !== 'object') return normalizeOzonText(value);
  const directFields = [
    'description',
    'message',
    'error',
    'error_message',
    'errorMessage',
    'reason',
    'comment',
    'attribute_name',
    'attributeName',
    'name',
    'code',
    'status_description',
    'statusDescription'
  ];
  const direct = directFields
    .map(field => formatOzonStructuredText(value[field], depth + 1))
    .filter(Boolean);
  if (direct.length) return Array.from(new Set(direct)).join(' ');
  if (depth >= 2) {
    try {
      return JSON.stringify(value);
    } catch {
      return '';
    }
  }
  return Object.entries(value)
    .map(([key, item]) => {
      const text = formatOzonStructuredText(item, depth + 1);
      return text ? `${key}: ${text}` : '';
    })
    .filter(Boolean)
    .slice(0, 6)
    .join('；');
}

function getOzonAccountTitle(accountId) {
  const account = (ozonState.accounts || []).find(item => String(item.id) === String(accountId));
  return account?.title || '';
}

function getOzonShopAuth(account = {}) {
  return account?.shopAuth || account?.shop_auth || {};
}

function isOzonAccountShopAuthorized(account = {}) {
  if (!account || account.status === 'deleted' || account.status === 'disabled') return false;
  const shopAuth = getOzonShopAuth(account);
  return Boolean(account.id && shopAuth?.isMember);
}

function getOzonAccountShopMallId(account = {}) {
  return account.shopMallId || account.shop_mall_id || (account.id ? `ozon:${account.id}` : '');
}

function getOzonAccountHomeListSortText(account = {}) {
  return normalizeOzonText(account.title || account.clientId || account.shopTitle || account.name || '');
}

function compareOzonAccountsByHomeListOrder(a = {}, b = {}) {
  const titleCompare = getOzonAccountHomeListSortText(a)
    .toLowerCase()
    .localeCompare(getOzonAccountHomeListSortText(b).toLowerCase(), 'zh-Hans-CN', { numeric: true, sensitivity: 'base' });
  if (titleCompare !== 0) return titleCompare;
  return String(a.id || '').localeCompare(String(b.id || ''), 'zh-Hans-CN', { numeric: true, sensitivity: 'base' });
}

function sortOzonAccountsByHomeListOrder(accounts = []) {
  return (Array.isArray(accounts) ? [...accounts] : []).sort(compareOzonAccountsByHomeListOrder);
}

function getAuthorizedOzonAccounts() {
  return sortOzonAccountsByHomeListOrder((ozonState.accounts || []).filter(isOzonAccountShopAuthorized));
}

function getOzonScopedAccountId(preferred = '', { allowAll = false } = {}) {
  const preferredId = normalizeOzonText(preferred);
  if (!preferredId && allowAll) return '';
  if (preferredId && getAuthorizedOzonAccounts().some(account => String(account.id) === preferredId)) return preferredId;
  return allowAll ? '' : getDefaultOzonAccountId();
}

function getSelectedOzonScopeId(type = 'product') {
  const scoped = type === 'order'
    ? (ozonState.orderAccountId || ozonState.orderAnalysisAccountId)
    : (ozonState.productAccountId || ozonState.productDiagnosisAccountId);
  return type === 'order'
    ? getOzonScopedAccountId(scoped, { allowAll: true })
    : (scoped || getDefaultOzonAccountId());
}

function getDefaultOzonAccountId() {
  const account = getAuthorizedOzonAccounts()[0] || null;
  return account?.id ? String(account.id) : '';
}

function getOzonScopedAccountKeys() {
  return [
    'productAccountId',
    'productDiagnosisAccountId',
    'orderAccountId',
    'orderAnalysisAccountId',
    'trafficAccountId',
    'marketingAccountId',
    'financeAccountId',
    'messageAccountId'
  ];
}

function normalizeOzonOrderBucket(value = '') {
  const text = normalizeOzonText(value).toLowerCase();
  return ['all', 'cancelled', 'unpurchased', 'delivered', 'returns', 'fba'].includes(text) ? text : 'all';
}

function ensureOzonScopedAccountDefaults() {
  const defaultAccountId = getDefaultOzonAccountId();
  if (!defaultAccountId) return '';
  getOzonScopedAccountKeys().forEach((key) => {
    if (key === 'orderAccountId' || key === 'orderAnalysisAccountId') {
      ozonState[key] = getOzonScopedAccountId(ozonState[key], { allowAll: true });
      return;
    }
    ozonState[key] = getOzonActiveAccountId(ozonState[key] || defaultAccountId);
  });
  return defaultAccountId;
}

function getOzonSelectedMessageAccountId() {
  return getOzonActiveAccountId(ozonState.messageAccountId || getDefaultOzonAccountId() || '');
}

function setOzonSelectedMessageAccountId(accountId = '') {
  const nextAccountId = getOzonActiveAccountId(accountId || ozonState.messageAccountId || getDefaultOzonAccountId() || '');
  ozonState.messageAccountId = nextAccountId;
  return nextAccountId;
}

function getOzonFullSyncAccountIds(type = 'product') {
  const activeTabId = document.querySelector('.section.active')?.id || '';
  let scoped = '';
  if (type === 'order') {
    const selectValue = document.getElementById(activeTabId === 'ozonOrders' ? 'ozonOrderAnalysisAccountFilter' : 'ozonOrderAccountFilter')?.value;
    scoped = selectValue !== undefined
      ? selectValue
      : (ozonState.orderAnalysisAccountId || ozonState.orderAccountId || '');
  } else {
    const productOnlineValue = document.getElementById('ozonProductAccountFilter')?.value;
    const diagnosisValue = document.getElementById('ozonProductDiagnosisAccountFilter')?.value;
    scoped = activeTabId === 'ozonProducts'
      ? (diagnosisValue || ozonState.productDiagnosisAccountId || productOnlineValue || ozonState.productAccountId || '')
      : (productOnlineValue || ozonState.productAccountId || diagnosisValue || ozonState.productDiagnosisAccountId || '');
  }
  if (type === 'order' && !normalizeOzonText(scoped)) {
    return getAuthorizedOzonAccounts().map(account => String(account.id)).filter(Boolean);
  }
  const accountId = type === 'order'
    ? getOzonScopedAccountId(scoped, { allowAll: true })
    : getOzonActiveAccountId(scoped);
  return accountId ? [String(accountId)] : [];
}

function getOzonNoAuthorizedAccountMessage() {
  return (ozonState.accounts || []).length
    ? 'OZON 官方 API 已绑定，但未完成小青蛙密钥授权'
    : '请先在首页绑定 OZON 官方 API，再兑换小青蛙密钥授权';
}

function syncOzonAccountSelects() {
  const accounts = getAuthorizedOzonAccounts();
  const defaultAccountId = getDefaultOzonAccountId();
  const shopOptionRows = accounts.length
    ? accounts
    : sortOzonAccountsByHomeListOrder((ozonState.accounts || []).filter(account => account && account.status !== 'deleted' && account.status !== 'disabled'));
  const shopOptionHtml = shopOptionRows.map(account => {
      return `<option value="${escapeHtml(account.id)}">${escapeHtml(account.title || account.clientId || 'OZON 店铺')}</option>`;
    })
    .join('') || '<option value="">暂无已授权店铺</option>';
  const allShopOptionHtml = accounts.length
    ? `<option value="">全部店铺</option>${shopOptionHtml}`
    : shopOptionHtml;
  ensureOzonScopedAccountDefaults();
  const pairs = [
    ['ozonProductAccountFilter', 'productAccountId', false],
    ['ozonProductDiagnosisAccountFilter', 'productDiagnosisAccountId', false],
    ['ozonOrderAccountFilter', 'orderAccountId', true],
    ['ozonOrderAnalysisAccountFilter', 'orderAnalysisAccountId', true],
    ['ozonAdsAccountFilter', 'adsAccountId', false],
    ['ozonTrafficAccountFilter', 'trafficAccountId', false],
    ['ozonMarketingAccountFilter', 'marketingAccountId', false],
    ['ozonFinanceAccountFilter', 'financeAccountId', false]
  ];
  pairs.forEach(([id, key, allowAll]) => {
    const select = document.getElementById(id);
    if (!select) return;
    const optionsHtml = allowAll ? allShopOptionHtml : shopOptionHtml;
    const rawState = Object.prototype.hasOwnProperty.call(ozonState, key) ? ozonState[key] : '';
    const stateValue = normalizeOzonText(allowAll ? rawState : (rawState || defaultAccountId));
    const current = getOzonScopedAccountId(stateValue || select.value || (allowAll ? '' : defaultAccountId), { allowAll });
    if (select.dataset.ozonOptions !== optionsHtml) {
      select.innerHTML = optionsHtml;
      select.dataset.ozonOptions = optionsHtml;
    }
    select.value = current;
    ozonState[key] = current;
  });
  updateOzonTabSyncStamps();
}

function getOzonProductStatusLabel(status = '') {
  const text = normalizeOzonText(status).toUpperCase();
  const labels = {
    ALL: '所有',
    IN_SALE: '在售中',
    VISIBLE: '在售中',
    TO_SUPPLY: '准备出售',
    READY_TO_SUPPLY: '准备出售',
    READY: '准备出售',
    INVISIBLE: '已下架',
    HIDDEN: '已下架',
    OFF_SALE: '已下架',
    REMOVED_FROM_SALE: '已下架',
    EMPTY_STOCK: '无库存',
    ARCHIVED: '已归档',
    ERROR: '错误',
    STATE_FAILED: '错误',
    VALIDATION_STATE_FAIL: '错误',
    PARTIAL_APPROVED: '待修改',
    WAIT_EDIT: '待修改',
    NEED_EDIT: '待修改',
    VALIDATION_STATE_PENDING: '待修改',
    NOT_MODERATED: '待修改',
    DISABLED: '待修改',
    EMPTY_BARCODE: '待修改',
    BARCODE_EXISTS: '待修改',
    OVERPRICED: '待修改',
    CRITICALLY_OVERPRICED: '待修改',
    OVERPRICED_WITH_STOCK: '待修改',
    QUARANTINE: '待修改',
    PARTIAL_APPROVED: '待修改'
  };
  return labels[text] || status || '未知';
}

function getOzonOfficialProductStatusUi(raw = {}, fallbackVisibility = '') {
  const statuses = raw.statuses && typeof raw.statuses === 'object' ? raw.statuses : {};
  const rawStatus = normalizeOzonText(statuses.status_name || statuses.statusName || statuses.status || raw.status || '');
  const statusText = rawStatus.toLowerCase();
  if (/прода[её]тся|in sale|selling/.test(statusText) && !/не прода[её]тся|not selling/.test(statusText)) {
    return { visibility: 'IN_SALE', label: '在售', rawStatus };
  }
  if (/готов к продаже|ready to supply|ready for sale/.test(statusText)) {
    return { visibility: 'TO_SUPPLY', label: '准备出售', rawStatus };
  }
  if (/не прода[её]тся|not selling|removed from sale|убран из продажи|снят с продажи/.test(statusText)) {
    return { visibility: 'OFF_SALE', label: '已下架', rawStatus };
  }
  const visibility = normalizeOzonVisibilityForUi(fallbackVisibility || raw.visibility || raw.status || '') || '';
  return {
    visibility,
    label: getOzonProductStatusLabel(visibility || rawStatus),
    rawStatus
  };
}

function normalizeOzonVisibilityForUi(status = '') {
  const text = normalizeOzonText(status).toUpperCase();
  if (text === 'VISIBLE') return 'IN_SALE';
  if (text === 'READY') return 'READY_TO_SUPPLY';
  if (text === 'STATE_FAILED' || text === 'VALIDATION_STATE_FAIL') return 'ERROR';
  if (text === 'PARTIAL_APPROVED' || text === 'NEED_EDIT') return 'WAIT_EDIT';
  if (text === 'HIDDEN') return 'INVISIBLE';
  if (text === 'REMOVED_FROM_SALE') return 'OFF_SALE';
  return text;
}

function getOzonProductStatusTabByKey(key = '') {
  const normalized = normalizeOzonVisibilityForUi(key || 'ALL') || 'ALL';
  return OZON_PRODUCT_STATUS_TABS.find(tab => tab.key === normalized) ||
    OZON_PRODUCT_STATUS_TABS.find(tab => tab.values.includes(normalized)) ||
    OZON_PRODUCT_STATUS_TABS[0];
}

function getOzonProductStatusTabCount(tab = {}, counts = {}) {
  const normalizedCounts = Object.entries(counts || {}).reduce((acc, [status, count]) => {
    const key = normalizeOzonVisibilityForUi(status || '') || normalizeOzonText(status).toUpperCase();
    acc[key] = (acc[key] || 0) + Number(count || 0);
    return acc;
  }, {});
  if (!tab?.values?.length) {
    if (Number.isFinite(Number(normalizedCounts.ALL))) return Number(normalizedCounts.ALL);
    return Object.entries(normalizedCounts)
      .filter(([key]) => key !== 'ARCHIVED')
      .reduce((sum, [, count]) => sum + Number(count || 0), 0);
  }
  if (Number.isFinite(Number(normalizedCounts[tab.key]))) return Number(normalizedCounts[tab.key]);
  const keys = [...new Set(tab.values.map(status => normalizeOzonVisibilityForUi(status || '') || normalizeOzonText(status).toUpperCase()).filter(Boolean))];
  return keys.reduce((sum, key) => sum + Number(normalizedCounts[key] || 0), 0);
}

function setOzonProductAccountFilterValue(accountId = '', { userTouched = false } = {}) {
  const normalized = normalizeOzonText(accountId);
  ozonState.productAccountId = normalized;
  if (userTouched) ozonState.productAccountTouched = true;
  const select = document.getElementById('ozonProductAccountFilter');
  if (select) select.value = normalized;
}

function setOzonOrderAccountFilterValue(accountId = '', { userTouched = false } = {}) {
  const normalized = normalizeOzonText(accountId);
  ozonState.orderAccountId = normalized;
  if (userTouched) ozonState.orderAccountTouched = true;
  const select = document.getElementById('ozonOrderAccountFilter');
  if (select) select.value = normalized;
}

function getOzonOrderOfficialStatusLabel(orderOrRaw = {}) {
  const raw = orderOrRaw?.rawData || orderOrRaw?.raw || orderOrRaw || {};
  return [
    orderOrRaw?.statusLabel,
    orderOrRaw?.status_name,
    orderOrRaw?.statusName,
    orderOrRaw?.status_description,
    orderOrRaw?.statusDescription,
    raw.statusLabel,
    raw.status_name,
    raw.statusName,
    raw.status_description,
    raw.statusDescription,
    raw.statuses?.status_name,
    raw.statuses?.statusName,
    raw.statuses?.status_description,
    raw.statuses?.statusDescription,
    raw.posting?.status_name,
    raw.posting?.statusName,
    raw.posting?.statuses?.status_name,
    raw.posting?.statuses?.statusName
  ].map(normalizeOzonText).find(Boolean) || '';
}

function getOzonOrderStatusLabel(status = '', orderOrRaw = null) {
  const officialLabel = orderOrRaw ? getOzonOrderOfficialStatusLabel(orderOrRaw) : '';
  if (officialLabel) return officialLabel;
  const text = normalizeOzonText(status);
  const labels = {
    awaiting_packaging: '等待备货',
    awaiting_deliver: '已准备发运',
    awaiting_registration: '已准备发运',
    delivering: '运输中',
    arbitration: '有争议的',
    delivered: '已签收',
    cancelled: '取消',
    posting_on_way_to_city: '运往城市',
    posting_received: '已接收',
    posting_awaiting_registration: '已准备发运',
    posting_in_pickup_point: '到达取货点',
    posting_transferred_to_courier_service: '已交快递',
    posting_delivered: '已签收',
    posting_canceled: '取消',
    cancelled_from_split_pending: '取消'
  };
  return labels[text] || text || '未知状态';
}

function getOzonOrderStatusTone(status = '') {
  const text = normalizeOzonText(status).toLowerCase();
  if (/cancel|canceled|cancelled/.test(text)) return 'cancelled';
  if (/delivered|received/.test(text)) return 'delivered';
  if (text === 'awaiting_deliver' || text === 'awaiting_registration' || text === 'posting_awaiting_deliver' || text === 'posting_awaiting_registration') return 'shipping';
  if (/awaiting|registration|packaging/.test(text)) return 'pending';
  if (/deliver|way|transferred|pickup|courier|posting/.test(text)) return 'shipping';
  if (/arbitration/.test(text)) return 'warning';
  return 'other';
}

function isOzonDeliveredOrder(orderOrStatus = '') {
  const text = normalizeOzonText(
    typeof orderOrStatus === 'string'
      ? orderOrStatus
      : (orderOrStatus?.status || orderOrStatus?.rawData?.status || orderOrStatus?.raw?.status || '')
  ).toLowerCase();
  return text === 'delivered' || text === 'delivered_to_customer' || text === 'posting_delivered' || /^delivered/.test(text);
}

function getOzonOrderBucketLabel(bucket = '') {
  const key = normalizeOzonText(bucket || 'all').toLowerCase();
  const labels = {
    all: '全部',
    cancelled: '取消',
    unpurchased: '未采购',
    delivered: '已交付',
    returns: '退货',
    fba: 'FBA'
  };
  return labels[key] || labels.all;
}

function formatOzonDateRange(start, end) {
  const a = formatOzonDateTime(start);
  const b = formatOzonDateTime(end);
  if (a && b) return `${a} 至 ${b}`;
  return a || b || '暂无订单';
}

function getOzonProductMetric(product = {}, names = []) {
  const raw = product.rawData || product.raw || {};
  const sources = [
    product,
    raw,
    raw.list_data,
    raw.price_indexes,
    raw.price_indexes?.external_index_data,
    raw.marketing_price,
    raw.price,
    raw.stocks,
    raw.stocks?.stocks?.[0],
    Array.isArray(raw.stocks) ? raw.stocks[0] : null,
    Array.isArray(raw.sources) ? raw.sources[0] : null,
    raw.sources?.[0],
    raw.model_info,
    raw.dimensions,
    raw.package,
    raw.category,
    raw.commissions?.find?.(item => item.sale_schema === 'FBS') || raw.commissions?.[0]
  ].filter(Boolean);
  for (const source of sources) {
    for (const name of names) {
      const value = source?.[name];
      if (value !== undefined && value !== null && value !== '') return value;
    }
  }
  return '';
}

function parseOzonNumber(value) {
  if (value === undefined || value === null || value === '') return null;
  if (typeof value === 'number') return Number.isFinite(value) ? value : null;
  if (typeof value === 'object') {
    const nested = value.value ?? value.price ?? value.marketing_price ?? value.amount ?? value.total ?? value.sum;
    return parseOzonNumber(nested);
  }
  const normalized = String(value).replace(/[^\d.,-]/g, '').replace(',', '.');
  const n = Number(normalized);
  return Number.isFinite(n) ? n : null;
}

function formatOzonMoney(value, currency = '¥') {
  if (value === undefined || value === null || value === '') return '--';
  const n = parseOzonNumber(value);
  if (!Number.isFinite(n)) return String(value);
  return `${currency}${n.toFixed(n % 1 === 0 ? 0 : 2)}`;
}

function getOzonStatusClass(status = '') {
  const text = normalizeOzonText(status).toUpperCase();
  if (text === 'VISIBLE' || text === 'IN_SALE') return '';
  if (['ERROR', 'STATE_FAILED', 'VALIDATION_STATE_FAIL'].includes(text)) return 'is-error';
  if (['TO_SUPPLY', 'READY_TO_SUPPLY', 'EMPTY_STOCK', 'VALIDATION_STATE_PENDING', 'NOT_MODERATED', 'OVERPRICED', 'CRITICALLY_OVERPRICED', 'OVERPRICED_WITH_STOCK', 'EMPTY_BARCODE', 'BARCODE_EXISTS', 'QUARANTINE', 'PARTIAL_APPROVED', 'DISABLED'].includes(text)) return 'is-warn';
  return 'is-muted';
}

function formatOzonNumber(value, suffix = '') {
  const n = Number(value);
  if (!Number.isFinite(n)) return value === 0 ? `0${suffix}` : '';
  return `${n}${suffix}`;
}

function formatOzonRubAmount(value) {
  const n = Number(value || 0);
  if (!Number.isFinite(n)) return '0.00';
  return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

function pickFirstOzonValue(sources = [], names = []) {
  for (const source of sources.filter(Boolean)) {
    for (const name of names) {
      const value = source?.[name];
      if (value !== undefined && value !== null && value !== '') return value;
    }
  }
  return '';
}

function joinOzonParts(parts = [], separator = ' / ') {
  return parts.map(value => normalizeOzonText(value)).filter(Boolean).join(separator);
}

function formatOzonDateTime(value) {
  if (!value) return '--';
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return String(value);
  return date.toLocaleString('zh-CN', { hour12: false });
}

function formatOzonMessageListTime(value) {
  if (!value) return '--';
  const date = new Date(value);
  if (!Number.isNaN(date.getTime())) {
    const hour = String(date.getHours()).padStart(2, '0');
    const minute = String(date.getMinutes()).padStart(2, '0');
    return `${hour}:${minute}`;
  }
  const match = normalizeOzonText(value).match(/(?:^|[T\s])(\d{1,2}):(\d{2})(?::\d{2})?/);
  if (!match) return '--';
  return `${String(match[1]).padStart(2, '0')}:${match[2]}`;
}

function getOzonCategoryText(product = {}) {
  const raw = product.rawData || product.raw || {};
  const sources = [product, raw, raw.category, raw.description_category, raw.type, raw.list_data].filter(Boolean);
  const officialCategory = raw._xiaoqingwaCategory && typeof raw._xiaoqingwaCategory === 'object' ? raw._xiaoqingwaCategory : {};
  const officialFullName = normalizeOzonText(officialCategory.categoryFullName || officialCategory.category_full_name || '');
  if (officialFullName) return officialFullName;
  if (Array.isArray(officialCategory.categoryPath) && officialCategory.categoryPath.length) {
    const text = officialCategory.categoryPath.map(item => item?.name || item).filter(Boolean).join(' > ');
    if (text) return text;
  }
  const fullPathValue = pickFirstOzonValue(sources, ['category_full_name', 'categoryFullName']);
  if (fullPathValue) return fullPathValue;
  if (Array.isArray(raw.category_path) && raw.category_path.length) {
    return raw.category_path.map(item => item?.name || item).filter(Boolean).join(' > ');
  }
  if (Array.isArray(raw.description_category?.path) && raw.description_category.path.length) {
    return raw.description_category.path.map(item => item?.name || item).filter(Boolean).join(' > ');
  }
  const pathValue = pickFirstOzonValue(sources, ['category_name', 'categoryName', 'description_category_name', 'type_name', 'typeName']);
  if (pathValue) return pathValue;
  const id = getOzonProductMetric(product, ['description_category_id', 'category_id', 'categoryId', 'type_id', 'typeId']);
  return id ? `类目ID ${id}` : '--';
}

function getOzonCommissionInfo(product = {}) {
  const raw = product.rawData || product.raw || {};
  const commissions = Array.isArray(raw.commissions) ? raw.commissions : [];
  const target = commissions.find(item => normalizeOzonText(item?.sale_schema || item?.saleSchema || '').toUpperCase() === 'RFBS') || null;
  if (!target) return { percent: '', amount: '', source: '', scheme: '' };
  const percent = pickFirstOzonValue([target], ['percent']);
  const amount = pickFirstOzonValue([target], ['value']);
  return {
    percent,
    amount,
    source: 'ozon_product_info_commissions',
    scheme: 'RFBS'
  };
}

function getOzonPriceInfo(product = {}) {
  const raw = product.rawData || product.raw || {};
  const priceObj = typeof raw.price === 'object' ? raw.price : {};
  const marketingObj = typeof raw.marketing_price === 'object' ? raw.marketing_price : {};
  return {
    price: pickFirstOzonValue([product, raw, priceObj, marketingObj], ['price', 'marketing_price', 'marketingPrice']),
    oldPrice: pickFirstOzonValue([product, raw, priceObj, marketingObj], ['old_price', 'oldPrice']),
    minPrice: pickFirstOzonValue([product, raw, priceObj, marketingObj], ['min_price', 'minPrice']),
    promoPrice: pickFirstOzonValue([raw, priceObj, marketingObj], ['marketing_seller_price', 'marketingSellerPrice', 'auto_action_enabled', 'action_price', 'actionPrice']),
    currency: pickFirstOzonValue([raw, priceObj, marketingObj], ['currency_code', 'currencyCode', 'currency']) || 'RUB'
  };
}

function getOzonStockInfo(product = {}) {
  const raw = product.rawData || product.raw || {};
  const stockList = Array.isArray(raw.stocks?.stocks) ? raw.stocks.stocks : (Array.isArray(raw.stocks) ? raw.stocks : []);
  const stockSource = stockList[0] || raw.stocks || raw;
  const available = pickFirstOzonValue([stockSource, raw.stocks, raw], ['present', 'stock', 'available_stock', 'availableStock', 'quantity', 'sellable_stock']);
  const reserved = pickFirstOzonValue([stockSource, raw.stocks, raw], ['reserved', 'reserved_stock', 'reservedStock']);
  const sources = stockList.map(item => normalizeOzonText(item?.source)).filter(Boolean);
  const total = stockList.length
    ? stockList.reduce((sum, item) => sum + (parseOzonNumber(item.present ?? item.stock ?? item.available_stock ?? item.quantity) || 0), 0)
    : parseOzonNumber(available);
  const reservedTotal = stockList.length
    ? stockList.reduce((sum, item) => sum + (parseOzonNumber(item.reserved ?? item.reserved_stock) || 0), 0)
    : parseOzonNumber(reserved);
  const hasStock = raw.stocks?.has_stock ?? raw.stocks?.hasStock ?? raw.has_stock ?? raw.hasStock;
  return {
    available: Number.isFinite(total) && total !== null ? total : (hasStock === false ? 0 : available),
    reserved: Number.isFinite(reservedTotal) && reservedTotal !== null ? reservedTotal : reserved,
    warehouses: stockList.length,
    sources: Array.from(new Set(sources))
  };
}

function getOzonWarehouseStockMap(product = {}) {
  const raw = product.rawData || product.raw || {};
  const stockList = Array.isArray(raw.stocks?.stocks) ? raw.stocks.stocks : (Array.isArray(raw.stocks) ? raw.stocks : []);
  return stockList.reduce((acc, item = {}) => {
    const warehouseId = normalizeOzonText(item.warehouse_id || item.warehouseId || item.source_id || item.sourceId || '');
    const stock = item.present ?? item.stock ?? item.available_stock ?? item.availableStock ?? item.quantity;
    if (warehouseId && stock !== undefined && stock !== null && stock !== '') acc[warehouseId] = stock;
    return acc;
  }, {});
}

function getOzonProductCreatedAt(product = {}) {
  const raw = product.rawData || product.raw || {};
  return pickFirstOzonValue([raw, product, raw.list_data], [
    'created_at',
    'createdAt',
    'created',
    'created_date',
    'createdDate',
    'created_time',
    'createdTime'
  ]) || product.updatedAt || raw.updated_at || raw.updatedAt || '';
}

function getOzonWeightText(product = {}) {
  const raw = product.rawData || product.raw || {};
  const weight = getOzonProductMetric(product, ['weight', 'weight_g', 'weightG', 'package_weight', 'packageWeight', 'volume_weight']);
  const dimensions = raw.dimensions || raw.package || {};
  const size = joinOzonParts([
    dimensions.width || raw.width,
    dimensions.height || raw.height,
    dimensions.depth || dimensions.length || raw.depth || raw.length
  ], '×');
  const weightText = weight ? formatOzonNumber(weight, 'g') : '--';
  return size ? `${weightText}<br><span class="ozon-product-muted">${escapeHtml(size)}</span>` : escapeHtml(weightText);
}

function getOzonProductErrorText(product = {}) {
  const raw = product.rawData || product.raw || {};
  const errors = Array.isArray(raw.errors) ? raw.errors : (raw.errors ? [raw.errors] : []);
  const parts = [
    ...errors.map(formatOzonStructuredText),
    formatOzonStructuredText(raw.statuses?.status_description || raw.statuses?.statusDescription),
    formatOzonStructuredText(raw.status_description || raw.statusDescription)
  ].map(normalizeOzonText).filter(Boolean);
  return Array.from(new Set(parts)).slice(0, 6).join('；');
}

function getOzonProductIssueDetails(product = {}) {
  const raw = product.rawData || product.raw || {};
  const errors = Array.isArray(raw.errors) ? raw.errors : (raw.errors ? [raw.errors] : []);
  return errors.map((error, index) => {
    const item = error && typeof error === 'object' ? error : { message: error };
    const texts = item.texts && typeof item.texts === 'object' ? item.texts : {};
    const attributeName = formatOzonStructuredText(texts.attribute_name || texts.attributeName || item.attribute_name || item.attributeName);
    const officialHumanText = [
      texts.short_description,
      texts.shortDescription,
      texts.description,
      texts.message,
      item.short_description,
      item.shortDescription,
      item.description,
      item.message,
      item.error_for_user,
      item.errorForUser,
      item.error_message,
      item.errorMessage
    ].map(formatOzonStructuredText).find(Boolean);
    const code = normalizeOzonText(item.code || item.error_code || item.errorCode);
    const displayText = normalizeOzonText(officialHumanText);
    const level = normalizeOzonText(item.level || item.error_level || item.errorLevel);
    const title = attributeName || displayText || code || `缺陷 ${index + 1}`;
    return {
      title,
      description: displayText || formatOzonStructuredText(item),
      code,
      level
    };
  }).filter(item => item.title || item.description || item.code || item.level);
}

function getOzonPriceIndexInfoForUi(product = {}) {
  const raw = product.rawData || product.raw || {};
  const source = raw.price_indexes || raw.priceIndexes || raw.price_index || raw.priceIndex || {};
  const external = source.external_index_data || source.externalIndexData || {};
  const rawColor = normalizeOzonText(
    source.color_index ||
    source.colorIndex ||
    source.price_index ||
    source.priceIndex ||
    external.color_index ||
    external.colorIndex ||
    raw.price_index ||
    raw.priceIndex
  ).toUpperCase();
  if (/RED|UNPROFIT|BAD|HIGH/.test(rawColor)) return { label: '不利', tone: 'is-error', raw: rawColor };
  if (/YELLOW|MODERATE|MIDDLE|MEDIUM/.test(rawColor)) return { label: '中等', tone: 'is-warn', raw: rawColor };
  if (/GREEN|PROFIT|GOOD|LOW/.test(rawColor)) return { label: '有利', tone: '', raw: rawColor };
  if (/WITHOUT|NONE|NO_INDEX|EMPTY/.test(rawColor) || !rawColor) return { label: '无指数', tone: 'is-muted', raw: rawColor };
  return { label: rawColor, tone: 'is-muted', raw: rawColor };
}

function getOzonSourceInfo(product = {}) {
  const raw = product.rawData || product.raw || {};
  const source = Array.isArray(raw.sources) ? (raw.sources[0] || {}) : (raw.source || {});
  return {
    purchasePrice: raw.purchase_price || raw.purchasePrice || source.purchase_price || source.price || '',
    url: raw.source_url || raw.sourceUrl || raw.purchase_url || raw.purchaseUrl || source.url || '',
    remark: raw.source_remark || raw.sourceRemark || raw.remark || raw.note || ''
  };
}

function normalizeOzonProductForUi(product = {}) {
  const raw = product.rawData || product.raw || {};
  const price = getOzonPriceInfo(product);
  const stock = getOzonStockInfo(product);
  const commission = getOzonCommissionInfo(product);
  const productId = product.productId || getOzonProductMetric(product, ['product_id', 'productId', 'id']) || '';
  const offerId = product.offerId || getOzonProductMetric(product, ['offer_id', 'offerId']) || '';
  const sku = product.sku || getOzonProductMetric(product, ['sku']) || '';
  const title = product.name || getOzonProductMetric(product, ['name', 'product_name', 'productName']) || 'OZON 商品';
  const visibility = normalizeOzonVisibilityForUi(product.visibility || raw.visibility || raw.status || '');
  const priceIndex = getOzonPriceIndexInfoForUi(product);
  return {
    productId,
    offerId,
    sku,
    title,
    image: pickOzonImage(product),
    category: getOzonCategoryText(product),
    commission,
    price,
    stock,
    source: getOzonSourceInfo(product),
    visibility,
    statusLabel: getOzonProductStatusLabel(visibility),
    statusClass: getOzonStatusClass(visibility),
    priceIndex,
    errorText: getOzonProductErrorText(product),
    accountTitle: product.accountTitle || ozonState.accounts.find(account => String(account.id) === String(product.ozonAccountId))?.title || '',
    createdAt: getOzonProductCreatedAt(product),
    updatedAt: product.updatedAt || raw.updated_at || raw.updatedAt || ''
  };
}

function getOzonOrderProducts(order = {}) {
  const raw = order.rawData || order.raw || {};
  const products = Array.isArray(raw.products) ? raw.products : [];
  const financialProducts = Array.isArray(raw.financial_data?.products)
    ? raw.financial_data.products
    : (Array.isArray(raw.financialData?.products) ? raw.financialData.products : []);
  if (!products.length && financialProducts.length) {
    return financialProducts.map(item => {
      const normalized = item && typeof item === 'object' ? { ...item } : {};
      return {
        ...normalized,
        financial: normalized
      };
    });
  }
  return products.map((product, index) => ({ ...product, financial: financialProducts[index] || {} }));
}

function getOzonOrderProductQuantity(product = {}) {
  const financial = product && typeof product.financial === 'object' ? product.financial : product;
  const candidates = [
    product?.quantity,
    product?.qty,
    product?.count,
    product?.product_count,
    product?.productCount,
    financial?.quantity,
    financial?.qty,
    financial?.count,
    financial?.product_count,
    financial?.productCount
  ];
  for (const value of candidates) {
    if (value === undefined || value === null || value === '') continue;
    const qty = parseOzonNumber(value);
    if (Number.isFinite(qty) && qty > 0) return qty;
  }
  return 0;
}

function getOzonOrderProductUnitPrice(product = {}) {
  const financial = product && typeof product.financial === 'object' ? product.financial : product;
  const candidates = [
    product.price,
    product.sale_price,
    product.salePrice,
    product.item_price,
    product.itemPrice,
    product.customer_price,
    product.customerPrice,
    product.client_price,
    product.clientPrice,
    financial?.price,
    financial?.sale_price,
    financial?.salePrice,
    financial?.item_price,
    financial?.itemPrice,
    financial?.customer_price,
    financial?.customerPrice,
    financial?.client_price,
    financial?.clientPrice
  ];
  for (const value of candidates) {
    const number = parseOzonNumber(value);
    if (Number.isFinite(number) && number > 0) return number;
  }
  const total = parseOzonNumber(product.total_amount ?? product.totalAmount ?? product.amount ?? financial?.total_amount ?? financial?.totalAmount ?? financial?.amount);
  const quantity = getOzonOrderProductQuantity(product) || 1;
  return Number.isFinite(total) && total > 0 ? total / quantity : 0;
}

function getOzonOrderProductLineAmount(product = {}) {
  const financial = product && typeof product.financial === 'object' ? product.financial : product;
  const lineAmount = parseOzonNumber(
    product.line_amount ??
    product.lineAmount ??
    product.total_amount ??
    product.totalAmount ??
    product.amount_total ??
    product.amountTotal ??
    financial?.line_amount ??
    financial?.lineAmount ??
    financial?.total_amount ??
    financial?.totalAmount ??
    financial?.amount_total ??
    financial?.amountTotal
  );
  if (Number.isFinite(lineAmount) && lineAmount > 0) return lineAmount;
  const quantity = getOzonOrderProductQuantity(product) || 1;
  return getOzonOrderProductUnitPrice(product) * quantity;
}

function getOzonCommissionEstimate(order = {}) {
  const raw = order.rawData || order.raw || {};
  const estimate = order.commissionEstimate || raw.xqwCommissionEstimate || {};
  return estimate && typeof estimate === 'object' ? estimate : {};
}

function getOzonOrderProductEstimate(order = {}, product = {}, index = 0) {
  const estimate = getOzonCommissionEstimate(order);
  const items = Array.isArray(estimate.items) ? estimate.items : [];
  if (!items.length) return {};
  const financial = product && typeof product.financial === 'object' ? product.financial : product;
  const productId = normalizeOzonText(product.product_id || product.productId || product.id || financial?.product_id || financial?.productId);
  const offerId = normalizeOzonText(product.offer_id || product.offerId || financial?.offer_id || financial?.offerId);
  const sku = normalizeOzonText(product.sku || product.sku_id || product.skuId || financial?.sku || financial?.sku_id || financial?.skuId);
  const matched = items.find(item => (
    (productId && normalizeOzonText(item.productId || item.product_id) === productId) ||
    (offerId && normalizeOzonText(item.offerId || item.offer_id) === offerId) ||
    (sku && normalizeOzonText(item.sku || item.sku_id || item.skuId) === sku)
  ));
  return matched || items[index] || {};
}

function getOzonOrderProductRowKey(order = {}, product = {}, index = 0) {
  return [
    getOzonOrderKey(order),
    normalizeOzonText(product.product_id || product.productId || product.id || ''),
    normalizeOzonText(product.offer_id || product.offerId || ''),
    normalizeOzonText(product.sku || ''),
    index
  ].join('::');
}

function collectOzonNumericValues(source, names = []) {
  const values = [];
  const seen = new Set();
  const visit = (value) => {
    if (!value || typeof value !== 'object') return;
    if (seen.has(value)) return;
    seen.add(value);
    if (Array.isArray(value)) {
      value.forEach(visit);
      return;
    }
    names.forEach((name) => {
      if (value[name] === undefined || value[name] === null || value[name] === '') return;
      const number = parseOzonNumber(value[name]);
      if (Number.isFinite(number)) values.push(number);
    });
    Object.values(value).forEach(visit);
  };
  visit(source);
  return values;
}

function sumOzonValues(values = [], { abs = false } = {}) {
  return values.reduce((sum, value) => {
    const number = parseOzonNumber(value);
    if (!Number.isFinite(number)) return sum;
    return sum + (abs ? Math.abs(number) : number);
  }, 0);
}

function sumOzonProductFinancialValues(products = [], names = [], { abs = true } = {}) {
  return products.reduce((sum, product) => {
    const financial = product && typeof product.financial === 'object' ? product.financial : product;
    return sum + names.reduce((inner, name) => {
      const number = parseOzonNumber(financial?.[name] ?? product?.[name]);
      return Number.isFinite(number) ? inner + (abs ? Math.abs(number) : number) : inner;
    }, 0);
  }, 0);
}

function hasOwnOzonFinancialValue(source = {}, name = '') {
  return source && typeof source === 'object' &&
    Object.prototype.hasOwnProperty.call(source, name) &&
    source[name] !== undefined &&
    source[name] !== null &&
    source[name] !== '';
}

function hasOzonProductFinancialField(products = [], names = []) {
  return products.some(product => {
    const financial = product && typeof product.financial === 'object' ? product.financial : product;
    return names.some(name => hasOwnOzonFinancialValue(financial, name) || hasOwnOzonFinancialValue(product, name));
  });
}

function hasOzonFinancialServiceValues(services = {}) {
  return services && typeof services === 'object' &&
    Object.values(services).some(value => value !== undefined && value !== null && value !== '');
}

function isOzonOrderFinancialTaxServiceName(name = '') {
  const text = normalizeOzonText(name).toLowerCase();
  return /(^|[^a-z])tax([^a-z]|$)|vat|nds|налог|ндс/i.test(text);
}

function sumOzonOrderFinancialServiceObject(services = {}) {
  if (!services || typeof services !== 'object') return 0;
  return Object.entries(services).reduce((sum, [name, value]) => {
    if (isOzonOrderFinancialTaxServiceName(name)) return sum;
    const number = parseOzonNumber(value);
    return Number.isFinite(number) ? sum + Math.abs(number) : sum;
  }, 0);
}

function sumOzonOrderTaxServiceObject(services = {}) {
  if (!services || typeof services !== 'object') return 0;
  return Object.entries(services).reduce((sum, [name, value]) => {
    if (!isOzonOrderFinancialTaxServiceName(name)) return sum;
    const number = parseOzonNumber(value);
    return Number.isFinite(number) ? sum + Math.abs(number) : sum;
  }, 0);
}

function getOzonOrderFinancialServiceTotals(fin = {}, products = []) {
  const postingServices = fin.posting_services || fin.postingServices || {};
  let deliveryFee = sumOzonOrderFinancialServiceObject(postingServices);
  let taxFee = sumOzonOrderTaxServiceObject(postingServices);
  let hasServiceData = hasOzonFinancialServiceValues(postingServices);
  products.forEach(product => {
    const financial = product && typeof product.financial === 'object' ? product.financial : product;
    const itemServices = financial?.item_services || financial?.itemServices || product?.item_services || product?.itemServices || {};
    if (hasOzonFinancialServiceValues(itemServices)) hasServiceData = true;
    deliveryFee += sumOzonOrderFinancialServiceObject(itemServices);
    taxFee += sumOzonOrderTaxServiceObject(itemServices);
  });
  return { deliveryFee, taxFee, hasServiceData };
}

function getOzonOrderMoney(order = {}) {
  const raw = order.rawData || order.raw || {};
  const products = getOzonOrderProducts(order);
  const calculatedCustomerPrice = products.reduce((sum, product) => {
    const financial = product && typeof product.financial === 'object' ? product.financial : product;
    const unitPrice = parseOzonNumber(
      product.price ??
      product.sale_price ??
      product.salePrice ??
      product.amount ??
      product.total_amount ??
      product.totalAmount ??
      product.item_price ??
      product.itemPrice ??
      financial?.price ??
      financial?.sale_price ??
      financial?.salePrice ??
      product.customer_price ??
      product.customerPrice ??
      product.client_price ??
      product.clientPrice ??
      financial?.client_price ??
      financial?.clientPrice ??
      financial?.customer_price ??
      financial?.customerPrice ??
      financial?.old_price
    ) || 0;
    const quantity = getOzonOrderProductQuantity(product);
    return sum + unitPrice * quantity;
  }, 0);
  const storedCustomerPrice = parseOzonNumber(order.customerPrice ?? raw.customer_price ?? raw.customerPrice);
  const customerPrice = Number.isFinite(calculatedCustomerPrice) && calculatedCustomerPrice > 0
    ? calculatedCustomerPrice
    : storedCustomerPrice;
  const fin = raw.financial_data || raw.financialData || {};
  const hasCommissionField = hasOzonProductFinancialField(products, ['commission_amount', 'commissionAmount']);
  const productCommissionFee = sumOzonProductFinancialValues(products, ['commission_amount', 'commissionAmount'], { abs: true });
  const hasPositiveProductCommissionFee = Math.abs(productCommissionFee || 0) > 0;
  const productCommissionCurrency = products.map(product => {
    const financial = product && typeof product.financial === 'object' ? product.financial : product;
    return financial?.commissions_currency_code || financial?.commissionsCurrencyCode || financial?.currency_code || financial?.currencyCode || '';
  }).find(Boolean);
  const orderFinancialServiceTotals = getOzonOrderFinancialServiceTotals(fin, products);
  const financeSummary = order.financeSummary || raw.financeSummary || raw.xqwFinanceSummary || {};
  const financeStatus = normalizeOzonText(financeSummary.status || '');
  const hasOfficialFinance = financeStatus === 'matched' || Number(financeSummary.operationCount || 0) > 0;
  const legacyFeeTax = parseOzonNumber(financeSummary.feeTax);
  const summaryCommissionFee = hasOfficialFinance ? Math.abs(parseOzonNumber(financeSummary.commissionFee ?? legacyFeeTax) || 0) : 0;
  const hasSummaryCommissionFee = summaryCommissionFee > 0;
  const estimate = getOzonCommissionEstimate(order);
  const estimateAmount = parseOzonNumber(estimate.amount);
  const isDelivered = isOzonDeliveredOrder(order);
  const hasEstimatedCommission = !hasPositiveProductCommissionFee &&
    !hasOfficialFinance &&
    !hasSummaryCommissionFee &&
    !isDelivered &&
    ['estimated', 'partial'].includes(normalizeOzonText(estimate.status || '')) &&
    Number.isFinite(estimateAmount) &&
    estimateAmount > 0;
  const commissionFee = (hasSummaryCommissionFee ? summaryCommissionFee : 0) ||
    (hasPositiveProductCommissionFee ? productCommissionFee : 0) ||
    (hasEstimatedCommission ? estimateAmount : 0) ||
    0;
  const taxFee = (hasOfficialFinance ? Math.abs(parseOzonNumber(financeSummary.taxFee) || 0) : 0) || orderFinancialServiceTotals.taxFee || 0;
  const feeTax = commissionFee + taxFee;
  const summaryDeliveryFee = hasOfficialFinance ? Math.abs(parseOzonNumber(financeSummary.deliveryFee) || 0) : 0;
  const deliveryFee = summaryDeliveryFee || orderFinancialServiceTotals.deliveryFee || 0;
  const hasPartialOfficialOrderFinance = hasOfficialFinance || hasCommissionField || orderFinancialServiceTotals.hasServiceData;
  const hasCompleteOfficialOrderFinance = hasOfficialFinance || (hasPositiveProductCommissionFee && orderFinancialServiceTotals.hasServiceData);
  const payout = parseOzonNumber(fin.payout || fin.payout_amount || fin.payoutAmount || fin.sale_commission || fin.accruals_for_sale);
  const purchaseCost = parseOzonNumber(
    order.purchaseCost ??
    order.purchase_cost ??
    raw.purchase_cost ??
    raw.purchaseCost
  );
  const cost = Number.isFinite(purchaseCost) ? purchaseCost : parseOzonNumber(
    fin.cost_price ||
    fin.costPrice ||
    raw.cost_price ||
    raw.costPrice ||
    order.cost ||
    order.purchaseCost
  );
  const productCurrency = products.map(product => {
    const financial = product && typeof product.financial === 'object' ? product.financial : product;
    return product.currency_code || product.currencyCode || financial?.currency_code || financial?.currencyCode || '';
  }).find(Boolean);
  const orderCurrency = normalizeOzonCurrencyCode(productCurrency || raw.currency_code || raw.currencyCode || fin.currency_code || fin.currencyCode || order.currencyCode || 'RUB');
  const baseFeeCurrency = normalizeOzonCurrencyCode(productCommissionCurrency || financeSummary.currency || fin.commissions_currency_code || fin.commissionsCurrencyCode || orderCurrency);
  const feeCurrency = hasEstimatedCommission ? normalizeOzonCurrencyCode(estimate.currency || 'CNY') : baseFeeCurrency;
  const deliveryCurrency = normalizeOzonCurrencyCode(financeSummary.currency || fin.currency_code || fin.currencyCode || orderCurrency);
  const costCurrency = 'CNY';
  const normalizedOrderCurrency = normalizeOzonCurrencyCode(orderCurrency);
  const canUseCnyFormula = normalizedOrderCurrency === 'CNY' && feeCurrency === 'CNY' && deliveryCurrency === 'CNY';
  const formulaCost = Number.isFinite(cost) ? cost : 0;
  const hasCommissionForFormula = hasCompleteOfficialOrderFinance || hasEstimatedCommission;
  const derivedProfitCny = canUseCnyFormula && hasCommissionForFormula
    ? Number(customerPrice || 0) - Math.abs(commissionFee || 0) - Math.abs(deliveryFee || 0) - formulaCost - Math.abs(taxFee || 0)
    : null;
  const profit = Number.isFinite(derivedProfitCny)
    ? derivedProfitCny
    : null;
  const profitCurrency = Number.isFinite(derivedProfitCny) ? 'CNY' : orderCurrency;
  const profitRateBase = Number(customerPrice || 0);
  const profitRate = Number.isFinite(profit) && profitRateBase > 0 ? profit / profitRateBase * 100 : null;
  const profitBlockReason = Number.isFinite(cost) && !Number.isFinite(profit)
    ? (!hasCommissionForFormula ? '待出账' : (canUseCnyFormula ? '待数据' : '待同币种费用'))
    : '';
  return {
    amount: customerPrice || 0,
    fee: Math.abs(commissionFee || 0),
    commissionFee: Math.abs(commissionFee || 0),
    taxFee: Math.abs(taxFee || 0),
    feeTax: Math.abs(feeTax || 0),
    feeCurrency,
    deliveryFee: Math.abs(deliveryFee || 0),
    deliveryCurrency,
    cost: Number.isFinite(cost) ? cost : null,
    costCurrency,
    payout: Number.isFinite(payout) ? payout : null,
    profit: Number.isFinite(profit) ? profit : null,
    profitRate,
    profitCurrency,
    profitBlockReason,
    financeStatus: hasPartialOfficialOrderFinance ? 'order_financial_data' : (hasEstimatedCommission ? 'estimated_commission' : (financeStatus || (hasOfficialFinance ? 'matched' : ''))),
    hasOfficialOrderFinance: hasCompleteOfficialOrderFinance,
    hasPartialOfficialOrderFinance,
    hasFinanceSummary: hasOfficialFinance,
    hasCommissionField,
    hasEstimatedCommission,
    commissionEstimate: estimate,
    hasServiceData: orderFinancialServiceTotals.hasServiceData || hasOfficialFinance,
    hasCommissionFee: hasPositiveProductCommissionFee || hasSummaryCommissionFee,
    isDelivered,
    currency: orderCurrency
  };
}

function getOzonOrderProductMoney(order = {}, product = {}, orderMoney = null, productIndex = 0) {
  const money = orderMoney || getOzonOrderMoney(order);
  const lineAmount = getOzonOrderProductLineAmount(product);
  const financial = product && typeof product.financial === 'object' ? product.financial : product;
  const productCommissionFee = sumOzonProductFinancialValues([product], ['commission_amount', 'commissionAmount'], { abs: true });
  const hasPositiveProductCommissionFee = Math.abs(productCommissionFee || 0) > 0;
  const productServiceTotals = getOzonOrderFinancialServiceTotals({}, [product]);
  const estimate = getOzonOrderProductEstimate(order, product, productIndex);
  const estimateAmount = parseOzonNumber(estimate.amount);
  const isDelivered = isOzonDeliveredOrder(order);
  const hasEstimatedCommission = !hasPositiveProductCommissionFee &&
    !money.hasFinanceSummary &&
    !isDelivered &&
    normalizeOzonText(estimate.status || '') === 'estimated' &&
    Number.isFinite(estimateAmount) &&
    estimateAmount > 0;
  const commissionFee = (hasPositiveProductCommissionFee ? productCommissionFee : 0) ||
    (hasEstimatedCommission ? estimateAmount : 0) ||
    0;
  const taxFee = productServiceTotals.taxFee || 0;
  const deliveryFee = productServiceTotals.deliveryFee || 0;
  const productCost = parseOzonNumber(
    product.purchaseCost ??
    product.purchase_cost ??
    product.costPrice ??
    product.cost_price ??
    financial?.purchaseCost ??
    financial?.purchase_cost ??
    financial?.costPrice ??
    financial?.cost_price
  );
  const cost = Number.isFinite(productCost)
    ? productCost
    : (productIndex === 0 && Number.isFinite(money.cost) ? money.cost : null);
  const hasCommissionField = hasOzonProductFinancialField([product], ['commission_amount', 'commissionAmount']);
  const hasServiceData = productServiceTotals.hasServiceData;
  const orderCurrency = normalizeOzonCurrencyCode(
    product.currency_code ||
    product.currencyCode ||
    financial?.currency_code ||
    financial?.currencyCode ||
    money.currency ||
    order.currencyCode ||
    'RUB'
  );
  const baseFeeCurrency = normalizeOzonCurrencyCode(
    financial?.commissions_currency_code ||
    financial?.commissionsCurrencyCode ||
    money.feeCurrency ||
    orderCurrency
  );
  const feeCurrency = hasEstimatedCommission ? normalizeOzonCurrencyCode(estimate.currency || 'CNY') : baseFeeCurrency;
  const deliveryCurrency = normalizeOzonCurrencyCode(money.deliveryCurrency || orderCurrency);
  const canUseCnyFormula = orderCurrency === 'CNY' && feeCurrency === 'CNY' && deliveryCurrency === 'CNY';
  const hasCompleteOfficialOrderFinance = Boolean(hasPositiveProductCommissionFee && hasServiceData);
  const hasCommissionForFormula = hasCompleteOfficialOrderFinance || hasEstimatedCommission;
  const formulaCost = Number.isFinite(cost) ? cost : 0;
  const profit = canUseCnyFormula && hasCommissionForFormula
    ? lineAmount - Math.abs(commissionFee || 0) - Math.abs(deliveryFee || 0) - formulaCost - Math.abs(taxFee || 0)
    : null;
  return {
    ...money,
    amount: lineAmount || 0,
    currency: orderCurrency,
    commissionFee: Math.abs(commissionFee || 0),
    fee: Math.abs(commissionFee || 0),
    taxFee: Math.abs(taxFee || 0),
    deliveryFee: Math.abs(deliveryFee || 0),
    feeCurrency,
    deliveryCurrency,
    cost,
    profit: Number.isFinite(profit) ? profit : null,
    profitCurrency: Number.isFinite(profit) ? 'CNY' : orderCurrency,
    profitRate: Number.isFinite(profit) && lineAmount > 0 ? profit / lineAmount * 100 : null,
    profitBlockReason: Number.isFinite(cost) && !Number.isFinite(profit)
      ? (!hasCommissionForFormula ? '待出账' : (canUseCnyFormula ? '待数据' : '待同币种费用'))
      : '',
    hasCommissionField,
    hasEstimatedCommission,
    commissionEstimate: estimate,
    hasServiceData,
    hasCommissionFee: hasPositiveProductCommissionFee,
    isDelivered
  };
}

function getOzonOfficialCommissionText(money = {}) {
  if (money.hasCommissionFee || Number(money.commissionFee || 0) > 0) {
    const text = formatOzonAmount(money.commissionFee || 0, money.feeCurrency || money.currency || 'RUB');
    return text;
  }
  if (money.hasEstimatedCommission && Number(money.commissionFee || 0) > 0) {
    return formatOzonAmount(money.commissionFee || 0, money.feeCurrency || money.currency || 'CNY');
  }
  const estimate = money.commissionEstimate || {};
  const estimateStatus = normalizeOzonText(estimate.status || '');
  if (['unmatched', 'ambiguous_category', 'ambiguous_type', 'missing_rate'].includes(estimateStatus) || estimate.reason) return '待类目匹配';
  if (estimateStatus === 'missing_amount') return '待订单金额';
  const status = normalizeOzonText(money.financeStatus || '');
  if ((status === 'matched' || status === 'order_financial_data') && (money.hasCommissionField || money.hasFinanceSummary)) {
    return formatOzonAmount(0, money.feeCurrency || money.currency || 'RUB');
  }
  if (status === 'not_found' || status === 'empty' || !status) return '待出账';
  if (status === 'error') return '读取失败';
  return '待出账';
}

function getOzonOfficialDeliveryFeeText(money = {}) {
  if (!money.hasServiceData) return '';
  return formatOzonAmount(money.deliveryFee || 0, money.deliveryCurrency || money.currency || 'RUB');
}

function getOzonOrderDelivery(order = {}) {
  const raw = order.rawData || order.raw || {};
  const delivery = raw.delivery_method || raw.deliveryMethod || {};
  const analytics = raw.analytics_data || raw.analyticsData || {};
  return {
    warehouse: analytics.warehouse_name || delivery.warehouse || delivery.warehouse_name || raw.warehouse_name || raw.warehouse_id || delivery.warehouse_id || '--',
    method: delivery.tpl_provider || delivery.name || raw.tpl_provider || raw.tpl_integration_type || '--',
    tracking: raw.tracking_number || raw.trackingNumber || raw.barcode || raw.tracking?.number || raw.posting?.tracking_number || ''
  };
}

function formatOzonWaybillMethod(value = '') {
  return normalizeOzonText(value)
    .replace(/\baggregator\s*3pl\b/ig, '3PL')
    .replace(/\baggregator\b/ig, '')
    .replace(/(?:^|\s)[-:：/|]+(?:\s|$)/g, ' ')
    .replace(/\s{2,}/g, ' ')
    .trim();
}

function formatOzonWaybillText(value = '') {
  return formatOzonWaybillMethod(value);
}

function getOzonOrderCancelText(order = {}) {
  const raw = order.rawData || order.raw || {};
  const cancel = raw.cancellation || raw.cancelation || {};
  return joinOzonParts([
    cancel.cancelled_by || cancel.canceled_by || raw.cancelled_by,
    cancel.cancel_reason || cancel.cancelReason || raw.cancel_reason || raw.cancelReason
  ], ' / ') || '--';
}

function getOzonProductKey(product = {}) {
  return normalizeOzonText(product.id || product.productId || product.offerId || product.sku);
}

function getOzonOrderAccountId(order = {}) {
  return normalizeOzonText(order.ozonAccountId || order.ozon_account_id || order.accountId || order.account_id || order.rawData?.ozon_account_id || order.raw?.ozon_account_id);
}

function getOzonOrderKey(order = {}) {
  const accountId = getOzonOrderAccountId(order);
  const postingNumber = normalizeOzonText(order.postingNumber || order.posting_number || order.orderId || order.order_id);
  if (accountId && postingNumber) return `${accountId}::${postingNumber}`;
  return postingNumber || normalizeOzonText(order.id || order.orderId || order.order_id);
}

function findOzonOrderByKey(key = '') {
  const text = normalizeOzonText(key);
  if (!text) return null;
  return (ozonState.orders || []).find(order => {
    if (getOzonOrderKey(order) === text) return true;
    return [
      order.id,
      order.postingNumber,
      order.posting_number,
      order.orderId,
      order.order_id
    ].map(normalizeOzonText).filter(Boolean).includes(text);
  }) || null;
}

function getOzonOfficialOrderDateValue(order = {}) {
  const raw = order.rawData || order.raw || {};
  const source = normalizeOzonText(
    raw.xqwSource ||
    raw.delivery_schema ||
    raw.deliverySchema ||
    raw.posting?.delivery_schema ||
    raw.posting?.deliverySchema ||
    raw.analytics_data?.delivery_schema ||
    raw.analyticsData?.deliverySchema ||
    ''
  ).toUpperCase();
  const inProcessCandidates = [
    raw.in_process_at,
    raw.inProcessAt,
    raw.posting?.in_process_at,
    raw.posting?.inProcessAt
  ];
  const createdCandidates = [
    raw.created_at,
    raw.createdAt,
    raw.posting?.created_at,
    raw.posting?.createdAt
  ];
  const candidates = source.includes('FBO')
    ? [...createdCandidates, ...inProcessCandidates]
    : inProcessCandidates;
  for (const value of candidates) {
    if (!value) continue;
    const date = new Date(value);
    if (!Number.isNaN(date.getTime())) return value;
  }
  return '';
}

function getOzonOrderSortTime(order = {}) {
  const value = getOzonOfficialOrderDateValue(order);
  if (value) {
    const date = new Date(value);
    if (!Number.isNaN(date.getTime())) return date.getTime();
  }
  return null;
}

function getOzonOfficialOrderDateText(order = {}) {
  const value = getOzonOfficialOrderDateValue(order);
  return value ? formatOzonDateTime(value) : '--';
}

function sortOzonOrdersNewestFirst(orders = []) {
  return [...(Array.isArray(orders) ? orders : [])].sort((a, b) => {
    const aTime = getOzonOrderSortTime(a);
    const bTime = getOzonOrderSortTime(b);
    if (aTime === null && bTime !== null) return 1;
    if (bTime === null && aTime !== null) return -1;
    const timeDiff = (bTime || 0) - (aTime || 0);
    if (timeDiff) return timeDiff;
    const updatedDiff = new Date(b?.updatedAt || 0).getTime() - new Date(a?.updatedAt || 0).getTime();
    if (Number.isFinite(updatedDiff) && updatedDiff) return updatedDiff;
    return Number(b?.id || 0) - Number(a?.id || 0);
  });
}

function getOzonCachedProductMap() {
  const map = new Map();
  const scoreProduct = (product) => {
    const ui = normalizeOzonProductForUi(product);
    const raw = product.rawData || product.raw || {};
    let score = 0;
    if (ui.image) score += 12;
    if (ui.title && ui.title !== 'OZON 商品') score += 6;
    if (ui.sku || raw.sku || raw.sku_id || raw.skuId) score += 4;
    if (ui.productId) score += 2;
    if (ui.offerId) score += 2;
    if (Object.keys(raw || {}).length > 8) score += 1;
    return score;
  };
  (ozonState.products || []).forEach(product => {
    const ui = normalizeOzonProductForUi(product);
    const raw = product.rawData || product.raw || {};
    [
      ui.productId,
      ui.offerId,
      ui.sku,
      raw.product_id,
      raw.productId,
      raw.id,
      raw.offer_id,
      raw.offerId,
      raw.sku,
      raw.sku_id,
      raw.skuId,
      raw.barcode
    ].map(normalizeOzonText).filter(Boolean).forEach(key => {
      const existing = map.get(key);
      if (!existing || scoreProduct(product) >= scoreProduct(existing.product)) {
        map.set(key, { product, ui });
      }
    });
  });
  return map;
}

function getOzonOrderProductKeys(product = {}) {
  const fin = product.financial || {};
  return [
    product.product_id,
    product.productId,
    product.id,
    product.offer_id,
    product.offerId,
    product.sku,
    product.sku_id,
    product.skuId,
    product.barcode,
    fin.product_id,
    fin.productId,
    fin.offer_id,
    fin.offerId,
    fin.sku,
    fin.sku_id,
    fin.skuId
  ].map(normalizeOzonText).filter(Boolean);
}

function getOzonOrderCachedProduct(product = {}, productMap = new Map(), order = {}) {
  const keys = getOzonOrderProductKeys(product);
  const cacheItems = Array.isArray(order.productCache) ? order.productCache : [];
  const cachedFromOrder = cacheItems.find(item => {
    const cacheKeys = [
      item.productId,
      item.product_id,
      item.offerId,
      item.offer_id,
      item.sku,
      item.sku_id,
      item.skuId
    ].map(normalizeOzonText).filter(Boolean);
    return cacheKeys.some(key => keys.includes(key));
  });
  if (cachedFromOrder) {
    const raw = cachedFromOrder.rawData || cachedFromOrder.raw || {};
    return {
      product: cachedFromOrder,
      ui: {
        productId: cachedFromOrder.productId || raw.product_id || raw.productId || '',
        offerId: cachedFromOrder.offerId || raw.offer_id || raw.offerId || '',
        sku: cachedFromOrder.sku || raw.sku || '',
        title: cachedFromOrder.name || raw.name || raw.product_name || raw.productName || '',
        image: pickOzonImage(cachedFromOrder)
      }
    };
  }
  return keys.map(key => productMap.get(key)).find(Boolean) || null;
}

function renderOzonClickableImageThumb(url, className = 'ozon-product-thumb', title = '') {
  const safeUrl = normalizeOzonImageUrl(url);
  if (!safeUrl) return renderOzonImageThumb('', className);
  return `<button class="ozon-image-preview-trigger" type="button" data-ozon-preview-image="${escapeHtml(safeUrl)}" data-ozon-preview-title="${escapeHtml(title || 'OZON 商品图片')}" title="点击放大图片">${renderOzonImageThumb(safeUrl, className)}</button>`;
}

let ozonImagePreviewState = {
  items: [],
  index: 0,
  canDelete: false,
  deleteEvent: '',
  deleteDetail: {}
};

function ensureOzonImagePreviewModal() {
  let modal = document.getElementById('ozonImagePreviewModal');
  if (modal) return modal;
  modal = document.createElement('div');
  modal.id = 'ozonImagePreviewModal';
  modal.className = 'ozon-modal ozon-image-preview-modal';
  modal.setAttribute('aria-hidden', 'true');
  modal.innerHTML = `<div class="ozon-modal-panel ozon-image-preview-panel">
    <div class="ozon-modal-head">
      <h3 id="ozonImagePreviewTitle">OZON 商品图片</h3>
      <div class="ozon-image-preview-actions">
        <button class="ozon-image-preview-delete" type="button" data-ozon-image-preview-delete>删除当前图片</button>
        <button class="btn btn-default btn-sm" type="button" data-ozon-image-preview-close>关闭</button>
      </div>
    </div>
    <div class="ozon-image-preview-body">
      <button class="ozon-image-preview-arrow is-prev" type="button" data-ozon-image-preview-prev aria-label="上一张图片">‹</button>
      <img id="ozonImagePreviewImg" src="" alt="">
      <button class="ozon-image-preview-arrow is-next" type="button" data-ozon-image-preview-next aria-label="下一张图片">›</button>
      <div class="ozon-image-preview-error" style="display:none;">图片加载失败，请稍后重试</div>
    </div>
  </div>`;
  modal.querySelector('[data-ozon-image-preview-close]')?.addEventListener('click', () => closeOzonImagePreview());
  modal.querySelector('[data-ozon-image-preview-prev]')?.addEventListener('click', () => shiftOzonImagePreview(-1));
  modal.querySelector('[data-ozon-image-preview-next]')?.addEventListener('click', () => shiftOzonImagePreview(1));
  modal.querySelector('[data-ozon-image-preview-delete]')?.addEventListener('click', () => deleteCurrentOzonImagePreviewItem());
  modal.addEventListener('click', (event) => {
    if (event.target === modal) closeOzonImagePreview();
  });
  document.body.appendChild(modal);
  return modal;
}

function normalizeOzonImagePreviewItems(url = '', title = '', options = {}) {
  const sourceItems = Array.isArray(options.items) && options.items.length
    ? options.items
    : [{ url, title }];
  return sourceItems
    .map((item, index) => ({
      url: normalizeOzonImageUrl(item?.url || item?.image || ''),
      title: item?.title || item?.name || title || `OZON 商品图片 ${index + 1}`,
      index: Number.isInteger(Number(item?.index)) ? Number(item.index) : index
    }))
    .filter(item => item.url);
}

function renderOzonImagePreviewState() {
  const modal = ensureOzonImagePreviewModal();
  const item = ozonImagePreviewState.items[ozonImagePreviewState.index] || null;
  if (!item) {
    closeOzonImagePreview();
    return;
  }
  const safeUrl = normalizeOzonImageUrl(item.url);
  if (!safeUrl) return;
  const body = modal.querySelector('.ozon-image-preview-body');
  let img = modal.querySelector('#ozonImagePreviewImg');
  if (!img && body) {
    img = document.createElement('img');
    img.id = 'ozonImagePreviewImg';
    img.alt = '';
    body.prepend(img);
  }
  const errorEl = modal.querySelector('.ozon-image-preview-error');
  const titleEl = modal.querySelector('#ozonImagePreviewTitle');
  const prevBtn = modal.querySelector('[data-ozon-image-preview-prev]');
  const nextBtn = modal.querySelector('[data-ozon-image-preview-next]');
  const deleteBtn = modal.querySelector('[data-ozon-image-preview-delete]');
  const total = ozonImagePreviewState.items.length;
  if (errorEl) errorEl.style.display = 'none';
  if (img) {
    img.onload = () => {
      img.classList.remove('is-error');
      if (errorEl) errorEl.style.display = 'none';
    };
    img.onerror = () => {
      img.classList.add('is-error');
      if (errorEl) errorEl.style.display = 'block';
    };
    img.classList.remove('is-error');
    img.alt = item.title || 'OZON 商品图片';
    img.decoding = 'async';
    img.loading = 'eager';
    img.removeAttribute('src');
    img.src = safeUrl;
  }
  if (titleEl) titleEl.textContent = `${item.title || 'OZON 商品图片'}${total > 1 ? `（${ozonImagePreviewState.index + 1}/${total}）` : ''}`;
  if (prevBtn) {
    prevBtn.hidden = total <= 1;
    prevBtn.disabled = total <= 1;
  }
  if (nextBtn) {
    nextBtn.hidden = total <= 1;
    nextBtn.disabled = total <= 1;
  }
  if (deleteBtn) {
    deleteBtn.hidden = !ozonImagePreviewState.canDelete;
    deleteBtn.disabled = !ozonImagePreviewState.canDelete;
  }
  modal.classList.add('is-open');
  modal.setAttribute('aria-hidden', 'false');
}

function openOzonImagePreview(url = '', title = '', options = {}) {
  const items = normalizeOzonImagePreviewItems(url, title, options);
  if (!items.length) return;
  const requestedIndex = Number(options.index);
  ozonImagePreviewState = {
    items,
    index: Number.isInteger(requestedIndex) ? Math.max(0, Math.min(items.length - 1, requestedIndex)) : Math.max(0, items.findIndex(item => item.url === normalizeOzonImageUrl(url))),
    canDelete: Boolean(options.canDelete),
    deleteEvent: String(options.deleteEvent || ''),
    deleteDetail: options.deleteDetail && typeof options.deleteDetail === 'object' ? options.deleteDetail : {}
  };
  if (ozonImagePreviewState.index < 0) ozonImagePreviewState.index = 0;
  renderOzonImagePreviewState();
}

function shiftOzonImagePreview(delta = 0) {
  const total = ozonImagePreviewState.items.length;
  if (total <= 1) return;
  ozonImagePreviewState.index = (ozonImagePreviewState.index + Number(delta || 0) + total) % total;
  renderOzonImagePreviewState();
}

function deleteCurrentOzonImagePreviewItem() {
  if (!ozonImagePreviewState.canDelete) return;
  const item = ozonImagePreviewState.items[ozonImagePreviewState.index];
  if (!item) return;
  const eventName = ozonImagePreviewState.deleteEvent || 'ozon:image-preview-delete';
  window.dispatchEvent(new CustomEvent(eventName, {
    detail: {
      ...ozonImagePreviewState.deleteDetail,
      imageIndex: item.index,
      previewIndex: ozonImagePreviewState.index,
      url: item.url,
      title: item.title
    }
  }));
  ozonImagePreviewState.items.splice(ozonImagePreviewState.index, 1);
  if (!ozonImagePreviewState.items.length) {
    closeOzonImagePreview();
    return;
  }
  ozonImagePreviewState.index = Math.min(ozonImagePreviewState.index, ozonImagePreviewState.items.length - 1);
  renderOzonImagePreviewState();
}

function closeOzonImagePreview() {
  const modal = document.getElementById('ozonImagePreviewModal');
  if (!modal) return;
  modal.classList.remove('is-open');
  modal.setAttribute('aria-hidden', 'true');
  const img = modal.querySelector('#ozonImagePreviewImg');
  if (img) {
    img.classList.remove('is-error');
    img.onload = null;
    img.onerror = null;
    img.removeAttribute('src');
  }
  const errorEl = modal.querySelector('.ozon-image-preview-error');
  if (errorEl) errorEl.style.display = 'none';
}

function ensureOzonSalesProductDetailModal() {
  let modal = document.getElementById('ozonSalesProductDetailModal');
  if (modal) return modal;
  modal = document.createElement('div');
  modal.id = 'ozonSalesProductDetailModal';
  modal.className = 'ozon-modal';
  modal.setAttribute('aria-hidden', 'true');
  modal.innerHTML = `<div class="ozon-modal-panel ozon-sales-product-detail-panel">
    <div class="ozon-modal-head">
      <h3>商品详情</h3>
      <button class="btn btn-default btn-sm" type="button" data-ozon-sales-detail-close>关闭</button>
    </div>
    <div id="ozonSalesProductDetailBody"></div>
  </div>`;
  modal.querySelector('[data-ozon-sales-detail-close]')?.addEventListener('click', () => setOzonOrderDetailModalVisible(modal, false));
  modal.addEventListener('click', event => {
    if (event.target === modal) setOzonOrderDetailModalVisible(modal, false);
  });
  document.body.appendChild(modal);
  return modal;
}

function openOzonSalesProductDetail(item = {}) {
  const modal = ensureOzonSalesProductDetailModal();
  const body = modal.querySelector('#ozonSalesProductDetailBody');
  const productUrl = getOzonProductUrl({
    sku: item.sku || '',
    productId: item.productId || '',
    rawData: {
      sku: item.sku || '',
      product_id: item.productId || ''
    }
  });
  const canOpenProduct = Boolean(item.sku || item.productId);
  const offerId = normalizeOzonText(item.offerId || '');
  const image = renderOzonImageThumb(item.image || '', 'ozon-sales-detail-main-image');
  if (body) {
    body.innerHTML = `<div class="ozon-sales-product-detail-layout">
      <div class="ozon-sales-product-detail-image">${image}</div>
      <div class="ozon-detail-grid ozon-sales-product-detail-grid">
      <div><span>标题</span><strong>${escapeHtml(item.name || item.title || 'OZON 商品')}</strong></div>
      <div><span>SKU ID</span><strong class="ozon-mono">${escapeHtml(item.sku || '--')}</strong><button class="btn btn-primary btn-sm" type="button" data-ozon-sales-product-open="${escapeHtml(productUrl)}" ${canOpenProduct ? '' : 'disabled'}>查看前台商品详情</button></div>
      <div><span>Offer:</span><strong class="ozon-mono">${escapeHtml(offerId || '--')}</strong><button class="btn btn-default btn-sm" type="button" data-ozon-sales-offer-copy="${escapeHtml(offerId)}" ${offerId ? '' : 'disabled'}>复制</button></div>
      </div>
    </div>`;
    body.querySelector('[data-ozon-sales-product-open]')?.addEventListener('click', event => {
      const url = event.currentTarget?.dataset?.ozonSalesProductOpen || '';
      if (url) window.open(url, '_blank', 'noopener');
    });
    body.querySelector('[data-ozon-sales-offer-copy]')?.addEventListener('click', event => {
      copyOzonText(event.currentTarget?.dataset?.ozonSalesOfferCopy || '', '已复制 Offer ID');
    });
  }
  setOzonOrderDetailModalVisible(modal, true);
}

function ensureOzonSalesAdviceModal() {
  let modal = document.getElementById('ozonSalesAdviceModal');
  if (modal) return modal;
  modal = document.createElement('div');
  modal.id = 'ozonSalesAdviceModal';
  modal.className = 'ozon-modal';
  modal.setAttribute('aria-hidden', 'true');
  modal.innerHTML = `<div class="ozon-modal-panel ozon-sales-advice-panel">
    <div class="ozon-modal-head">
      <h3>AI 完整改方案</h3>
      <button class="btn btn-default btn-sm" type="button" data-ozon-sales-advice-close>关闭</button>
    </div>
    <div id="ozonSalesAdviceModalBody"></div>
  </div>`;
  modal.querySelector('[data-ozon-sales-advice-close]')?.addEventListener('click', () => setOzonOrderDetailModalVisible(modal, false));
  modal.addEventListener('click', event => {
    if (event.target === modal) setOzonOrderDetailModalVisible(modal, false);
  });
  document.body.appendChild(modal);
  return modal;
}

function openOzonSalesAdviceModal(key = '') {
  const safeKey = normalizeOzonText(key);
  if (!safeKey) return;
  const state = ozonState.orderAnalysisAiAdvice?.[safeKey];
  if (!state || state.status !== 'success') {
    setOzonResult('当前链接还没有可查看的 AI 完整改方案', 'error');
    return;
  }
  const modal = ensureOzonSalesAdviceModal();
  const body = modal.querySelector('#ozonSalesAdviceModalBody');
  const priorityLabel = state.priority === 'high' ? '高优先级' : state.priority === 'low' ? '低优先级' : '中优先级';
  const basis = (Array.isArray(state.basis) ? state.basis : []).filter(Boolean);
  if (body) {
    body.innerHTML = `<div class="ozon-sales-advice-modal-body">
      <div class="ozon-sales-advice-modal-meta">
        <span>${escapeHtml(priorityLabel)}</span>
        ${state.model ? `<span>${escapeHtml(state.model)}</span>` : ''}
        <span>诊断时间：${escapeHtml(formatOzonDateTime(getOzonSalesAdviceTime(state)))}</span>
      </div>
      <div class="ozon-sales-advice-modal-copy">${escapeHtml(state.advice || 'AI未返回整改方案')}</div>
      ${basis.length ? `<div class="ozon-sales-advice-modal-basis">
        <strong>依据</strong>
        ${basis.map(text => `<p>${escapeHtml(text)}</p>`).join('')}
      </div>` : ''}
    </div>`;
  }
  setOzonOrderDetailModalVisible(modal, true);
}

function openOzonTrafficProductDetail(item = {}) {
  const modal = ensureOzonSalesProductDetailModal();
  const body = modal.querySelector('#ozonSalesProductDetailBody');
  const productUrl = getOzonProductUrl({
    sku: item.sku || '',
    productId: item.productId || '',
    rawData: {
      sku: item.sku || '',
      product_id: item.productId || ''
    }
  });
  const canOpenProduct = Boolean(item.sku || item.productId);
  const offerId = normalizeOzonText(item.offerId || '');
  const image = renderOzonImageThumb(item.image || '', 'ozon-sales-detail-main-image');
  if (body) {
    body.innerHTML = `<div class="ozon-sales-product-detail-layout">
      <div class="ozon-sales-product-detail-image">${image}</div>
      <div class="ozon-detail-grid ozon-sales-product-detail-grid">
      <div><span>标题</span><strong>${escapeHtml(item.name || item.offerId || item.sku || 'OZON 商品')}</strong></div>
      <div><span>店铺</span><strong>${escapeHtml(item.accountTitle || getOzonAccountLabel(item.accountId) || 'OZON 店铺')}</strong></div>
      <div><span>Product ID</span><strong class="ozon-mono">${escapeHtml(item.productId || '--')}</strong></div>
      <div><span>SKU ID</span><strong class="ozon-mono">${escapeHtml(item.sku || '--')}</strong><button class="btn btn-primary btn-sm" type="button" data-ozon-traffic-product-open="${escapeHtml(productUrl)}" ${canOpenProduct ? '' : 'disabled'}>查看前台商品详情</button></div>
      <div><span>Offer:</span><strong class="ozon-mono">${escapeHtml(offerId || '--')}</strong><button class="btn btn-default btn-sm" type="button" data-ozon-traffic-offer-copy="${escapeHtml(offerId)}" ${offerId ? '' : 'disabled'}>复制</button></div>
      <div><span>统计行数</span><strong>${escapeHtml(item.sourceRows || item.days || 0)}${item.days ? ` / ${escapeHtml(item.days)} 天` : ''}</strong></div>
      </div>
    </div>`;
    body.querySelector('[data-ozon-traffic-product-open]')?.addEventListener('click', event => {
      const url = event.currentTarget?.dataset?.ozonTrafficProductOpen || '';
      if (url) window.open(url, '_blank', 'noopener');
    });
    body.querySelector('[data-ozon-traffic-offer-copy]')?.addEventListener('click', event => {
      copyOzonText(event.currentTarget?.dataset?.ozonTrafficOfferCopy || '', '已复制 Offer ID');
    });
  }
  setOzonOrderDetailModalVisible(modal, true);
}

function getSelectedOzonProductRows() {
  const checkedKeys = Array.from(document.querySelectorAll('[data-ozon-product-check]:checked'))
    .map(input => String(input.dataset.ozonProductCheck || ''))
    .filter(Boolean);
  checkedKeys.forEach((key) => {
    ozonState.productSelectedIds.add(key);
    const product = findOzonProductByKey(key);
    if (product) ozonState.productSelectedRows.set(key, product);
  });
  const rowMap = new Map(ozonState.productSelectedRows || []);
  (ozonState.products || []).forEach((product) => {
    const key = getOzonProductKey(product);
    if (key && ozonState.productSelectedIds.has(key)) rowMap.set(key, product);
  });
  ozonState.productSelectedRows = rowMap;
  return Array.from(ozonState.productSelectedIds)
    .map(key => rowMap.get(key))
    .filter(Boolean);
}

function setOzonProductSelected(productOrKey, selected = true) {
  const product = typeof productOrKey === 'string' ? findOzonProductByKey(productOrKey) : productOrKey;
  const key = typeof productOrKey === 'string' ? normalizeOzonText(productOrKey) : getOzonProductKey(product || {});
  if (!key) return;
  if (selected) {
    ozonState.productSelectedIds.add(key);
    if (product) ozonState.productSelectedRows.set(key, product);
  } else {
    ozonState.productSelectedIds.delete(key);
    ozonState.productSelectedRows.delete(key);
  }
}

function clearOzonProductSelection() {
  ozonState.productSelectedIds.clear();
  ozonState.productSelectedRows.clear();
}

function getOzonProductRowsForTargetedSync() {
  const selected = getSelectedOzonProductRows();
  if (selected.length) return selected;
  const diagnosisRows = getFilteredOzonDiagnosisItems()
    .map(item => item.product || null)
    .filter(Boolean);
  if (diagnosisRows.length) return diagnosisRows;
  return ozonState.products || [];
}

function handleOzonOrderTableClick(event) {
  const wrap = document.getElementById('ozonOrderListTable');
  if (!wrap || !event?.target || !wrap.contains(event.target)) return;
  const imageButton = event.target.closest?.('[data-ozon-preview-image]');
  if (imageButton) {
    event.preventDefault();
    event.stopPropagation();
    openOzonImagePreview(imageButton.dataset.ozonPreviewImage || '', imageButton.dataset.ozonPreviewTitle || '');
    return;
  }
  const detailButton = event.target.closest?.('[data-ozon-order-detail]');
  if (detailButton) {
    event.preventDefault();
    loadOzonOrderDetail(detailButton.dataset.ozonOrderDetail || '', detailButton.dataset.ozonOrderAccountId || '');
    return;
  }
  const copyButton = event.target.closest?.('[data-ozon-order-copy]');
  if (copyButton) {
    event.preventDefault();
    copyOzonText(copyButton.dataset.ozonOrderCopy || '', copyButton.dataset.ozonCopyKind === 'offer' ? '已复制 Offer 编号' : '已复制订单号');
    return;
  }
  const costButton = event.target.closest?.('[data-ozon-order-cost]');
  if (costButton) {
    event.preventDefault();
    openOzonPurchaseInfoModal(costButton.dataset.ozonOrderCost || '');
  }
}

function bindOzonOrderTableDelegates() {
  const wrap = document.getElementById('ozonOrderListTable');
  if (!wrap || wrap.dataset.ozonDelegateBound === '1') return;
  wrap.dataset.ozonDelegateBound = '1';
  wrap.addEventListener('click', handleOzonOrderTableClick);
}

function updateOzonPagination(type) {
  const isOrder = type === 'order';
  const total = Number(isOrder ? ozonState.orderTotal : ozonState.productTotal) || 0;
  const page = Math.max(1, Number(isOrder ? ozonState.orderPage : ozonState.productPage) || 1);
  const limit = Math.max(1, Number(isOrder ? ozonState.orderLimit : ozonState.productLimit) || 50);
  const maxPage = Math.max(1, Math.ceil(total / limit));
  const prev = document.getElementById(isOrder ? 'ozonOrderPrevPageBtn' : 'ozonProductPrevPageBtn');
  const next = document.getElementById(isOrder ? 'ozonOrderNextPageBtn' : 'ozonProductNextPageBtn');
  const info = document.getElementById(isOrder ? 'ozonOrderPageInfo' : 'ozonProductPageInfo');
  if (prev) prev.disabled = page <= 1;
  if (next) next.disabled = page >= maxPage || total <= page * limit;
  if (info) info.textContent = `第 ${page} 页 / 共 ${total} 条`;
  if (isOrder) renderOzonOrderProfitSummary();
}

function formatOzonCurrencySummary(map = {}, fallback = '--') {
  const entries = Object.entries(map || {})
    .filter(([, value]) => Number.isFinite(Number(value)) && Math.abs(Number(value)) > 0.000001);
  if (!entries.length) return fallback;
  return entries.map(([currency, value]) => formatOzonAmount(value, currency)).join(' / ');
}

function renderOzonOrderProfitSummary() {
  const el = document.getElementById('ozonOrderProfitSummary');
  if (!el) return;
  const summary = ozonState.orderSummary || {};
  if (!summary || !summary.kind) {
    el.textContent = '';
    return;
  }
  if (summary.kind === 'returns') {
    el.innerHTML = `<strong>退货汇总：</strong><span>退货金额 ${escapeHtml(formatOzonCurrencySummary(summary.amountByCurrency, '¥0.00'))}</span>`;
    return;
  }
  const amountText = formatOzonCurrencySummary(summary.amountByCurrency, '¥0.00');
  const commissionText = formatOzonCurrencySummary(summary.commissionByCurrency, '¥0.00');
  const deliveryText = formatOzonCurrencySummary(summary.deliveryByCurrency, '¥0.00');
  const purchaseText = formatOzonCurrencySummary(summary.purchaseByCurrency, '¥0.00');
  const profitText = summary.canUseFormula === false || summary.profit === null || summary.profit === undefined
    ? '--'
    : formatOzonAmount(summary.profit, summary.currency || 'CNY');
  const profitRateText = summary.canUseFormula === false || summary.profitRate === null || summary.profitRate === undefined
    ? '--'
    : `${Number(summary.profitRate || 0).toFixed(2)}%`;
  const notes = [];
  if (Number(summary.missingFinanceCount || 0) > 0) notes.push(`待出账 ${Number(summary.missingFinanceCount || 0)} 单`);
  if (Number(summary.missingPurchaseCount || 0) > 0) notes.push(`未采购 ${Number(summary.missingPurchaseCount || 0)} 单`);
  if (summary.canUseFormula === false) notes.push('存在非人民币费用，暂不合并利润');
  el.innerHTML = `
    <strong>计算公式：</strong>
    <span>销售额 ${escapeHtml(amountText)}</span>
    <span>- 佣金 ${escapeHtml(commissionText)}</span>
    <span>- 运费 ${escapeHtml(deliveryText)}</span>
    <span>- 采购 ${escapeHtml(purchaseText)}</span>
    <span>= 利润 ${escapeHtml(profitText)}</span>
    <span>/ 利润率 ${escapeHtml(profitRateText)}</span>
    ${notes.length ? `<em>${escapeHtml(notes.join(' · '))}</em>` : ''}
  `;
}

function updateOzonProductStatusTabs() {
  const counts = ozonState.productStatusCounts || {};
  const activeTab = getOzonProductStatusTabByKey(ozonState.productVisibility || 'ALL');
  ozonState.productVisibility = activeTab?.key || 'ALL';
  document.querySelectorAll('[data-ozon-product-tab]').forEach(button => {
    const tab = getOzonProductStatusTabByKey(button.dataset.ozonProductTab || 'ALL');
    const count = getOzonProductStatusTabCount(tab, counts);
    button.innerHTML = `${escapeHtml(tab.label || tab.key)} <span class="ozon-tab-count">${escapeHtml(count)}</span>`;
    button.classList.toggle('active', String(ozonState.productVisibility || 'ALL') === tab.key);
  });
  const hidden = document.getElementById('ozonProductVisibilityFilter');
  if (hidden && hidden.value !== ozonState.productVisibility) hidden.value = ozonState.productVisibility;
}

function updateOzonProductListStatusTabs() {
  const counts = ozonState.productStatusCounts || {};
  const activeTab = getOzonProductStatusTabByKey(ozonState.productListVisibility || 'ALL');
  ozonState.productListVisibility = activeTab?.key || 'ALL';
  document.querySelectorAll('[data-ozon-product-list-tab]').forEach(button => {
    const tab = getOzonProductStatusTabByKey(button.dataset.ozonProductListTab || 'ALL');
    const count = getOzonProductStatusTabCount(tab, counts);
    button.innerHTML = `${escapeHtml(tab.label || tab.key)} <span class="ozon-tab-count">${escapeHtml(count)}</span>`;
    button.classList.toggle('active', String(ozonState.productListVisibility || 'ALL') === tab.key);
  });
  const hidden = document.getElementById('ozonProductListVisibilityFilter');
  if (hidden && hidden.value !== ozonState.productListVisibility) hidden.value = ozonState.productListVisibility;
}

function updateOzonOrderStatusTabs() {
  const counts = ozonState.orderBucketCounts || {};
  const total = Number(counts.all || ozonState.orderTotal || ozonState.orders.length || 0);
  const labels = {
    all: '全部',
    cancelled: '取消',
    unpurchased: '未采购',
    delivered: '已交付',
    returns: '退货',
    fba: 'FBA'
  };
  document.querySelectorAll('[data-ozon-order-tab]').forEach(button => {
    const status = button.dataset.ozonOrderTab || 'all';
    const count = status === 'all' ? total : Number(counts[status] || 0);
    button.textContent = `${labels[status] || status} ${count}`;
    button.classList.toggle('active', normalizeOzonOrderBucket(ozonState.orderStatus || 'all') === status);
  });
}

function pickOzonImage(value) {
  if (!value || typeof value !== 'object') return '';
  const raw = value.rawData || value.raw || value;
  const asImageValue = (item) => {
    if (!item) return '';
    if (typeof item === 'string') return item;
    if (Array.isArray(item)) return item.map(asImageValue).find(Boolean) || '';
    if (typeof item !== 'object') return '';
    return item.url || item.file_url || item.fileUrl || item.image || item.image_url || item.imageUrl || item.src || '';
  };
  const firstFromArray = (...arrays) => arrays
    .flatMap(item => Array.isArray(item) ? item : [])
    .map(asImageValue)
    .find(Boolean) || '';
  const candidates = [
    value.imageUrl,
    value.primaryImage,
    value.mainImage,
    value.pictureUrl,
    value.pictures?.[0],
    value.images?.[0],
    value.images360?.[0],
    raw.primary_image,
    raw.primaryImage,
    raw.image,
    raw.image_url,
    raw.imageUrl,
    raw.main_image,
    raw.mainImage,
    raw.picture_url,
    raw.pictureUrl,
    raw.photo,
    raw.photo_url,
    raw.photoUrl,
    asImageValue(raw.marketing_price?.image),
    asImageValue(raw.marketingPrice?.image),
    asImageValue(raw.media?.main_image),
    asImageValue(raw.media?.mainImage),
    firstFromArray(
      raw.primary_image,
      raw.primaryImage,
      raw.images,
      raw.images360,
      raw.pictures,
      raw.picture,
      raw.color_image,
      raw.colorImage,
      raw.image_group,
      raw.imageGroup,
      raw.media?.images,
      raw.media?.pictures
    )
  ];
  return normalizeOzonImageUrl(candidates.map(asImageValue).find(Boolean) || '');
}

function normalizeOzonImageUrl(url = '') {
  const text = normalizeOzonText(url);
  if (!text) return '';
  if (text.startsWith('//')) return `https:${text}`;
  if (/^http:\/\//i.test(text)) return text.replace(/^http:\/\//i, 'https://');
  return text;
}

function renderOzonImageThumb(url, className = 'ozon-product-thumb') {
  const safeUrl = normalizeOzonImageUrl(url);
  return safeUrl
    ? `<img class="${escapeHtml(className)}" src="${escapeHtml(safeUrl)}" alt="">`
    : `<div class="${escapeHtml(className)} ozon-thumb-placeholder">无图</div>`;
}

function getOzonProductStatusSummary() {
  return (ozonState.products || []).reduce((acc, product) => {
    const status = product.visibility || product.status || product.rawData?.visibility || 'unknown';
    acc[status] = (acc[status] || 0) + 1;
    return acc;
  }, {});
}

function updateOzonProductSummary() {
  const el = document.getElementById('ozonProductStatusSummary');
  if (!el) return;
  const summary = getOzonProductStatusSummary();
  const rows = Object.entries(summary);
  el.innerHTML = rows.length
    ? `<div class="ozon-list-title">商品状态</div><div class="ozon-list-meta">${rows.map(([status, count]) => `${escapeHtml(status)}：${escapeHtml(count)}`).join('<br>')}</div>`
    : '<div class="ozon-list-title">商品状态</div><div class="ozon-list-meta">暂无商品状态数据</div>';
}

function getOzonProductUrl(product = {}) {
  const id = product.sku || product.productId || product.rawData?.sku || product.rawData?.product_id || '';
  return id ? `https://www.ozon.ru/search/?text=${encodeURIComponent(id)}` : 'https://www.ozon.ru/';
}

function buildCsv(rows = [], headers = []) {
  const escapeCell = (value) => `"${String(value ?? '').replace(/"/g, '""')}"`;
  return [
    headers.map(header => escapeCell(header.label)).join(','),
    ...rows.map(row => headers.map(header => escapeCell(typeof header.value === 'function' ? header.value(row) : row[header.value])).join(','))
  ].join('\r\n');
}

function downloadOzonCsv(filename, rows, headers) {
  triggerOzonCsvDownload(filename, rows, headers);
}

function triggerOzonCsvDownload(filename, rows, headers) {
  const csv = buildCsv(rows, headers);
  const blob = new Blob([`\uFEFF${csv}`], { type: 'text/csv;charset=utf-8' });
  const safeName = filename || `xiaoqingwa-ozon-export-${Date.now()}.csv`;
  if (typeof navigator !== 'undefined' && typeof navigator.msSaveBlob === 'function') {
    navigator.msSaveBlob(blob, safeName);
    return;
  }
  triggerBrowserDownload(safeName, blob);
}

function getSelectedOzonCollectionIds() {
  return Array.from(document.querySelectorAll('[data-ozon-link-check]:checked'))
    .map(input => Number(input.dataset.ozonLinkCheck || 0))
    .filter(id => Number.isInteger(id) && id > 0);
}

function rememberOzonCheckedState() {
  ozonState.selectedCollectionIds = new Set(getSelectedOzonCollectionIds().map(String));
}

function renderOzonAccounts() {
  const count = document.getElementById('ozonAccountCount');
  if (count) count.textContent = String(ozonState.accounts.length || 0);
  syncOzonAccountSelects();
}

function openOzonTaskTarget(task = {}) {
  const accountId = normalizeOzonText(task.accountId || task.ozonAccountId || '');
  if (accountId) {
    if (task.key === 'buyerMessages' || task.key === 'questions') {
      setOzonSelectedMessageAccountId(accountId);
    } else {
      setOzonOrderAccountFilterValue(accountId, { userTouched: true });
    }
  }
  const targetTab = String(task.targetTab || '');
  const targetUrl = String(task.targetUrl || '');
  if (targetTab && OZON_TAB_IDS.has(targetTab)) {
    const tab = document.querySelector(`.tab[data-tab="${targetTab}"]`);
    if (tab) {
      handleTabClick(tab);
      if (targetTab === 'ozonMessages') {
        loadOzonMessages({ forceRefresh: true, sync: false }).catch(err => setOzonResult(err.message || '消息读取失败', 'error'));
      } else if (targetTab === 'ozonOrderList') {
        loadOzonOrders({ forceRefresh: true }).catch(err => setOzonResult(err.message || '订单读取失败', 'error'));
      }
    }
  }
  if (targetUrl) {
    window.open(targetUrl, '_blank');
  }
}

function formatOzonHomeTaskError(error) {
  const key = String(error?.key || '');
  const raw = String(error?.message || error?.error || '').trim();
  if (!raw) return 'OZON 接口读取失败，请检查接口权限';
  if (/permission\s*denied|PermissionDenied|forbidden|access\s*denied|unauthorized|401|403|权限|无权|未授权|checkSellerPremiumPlus|premium\s*plus/i.test(raw)) {
    if (key === 'questions') return '商品问题接口无权限，请确认 Seller API 或 Premium Plus 权限';
    if (key === 'buyerMessages') return '买家消息接口无权限，请确认消息接口权限';
    if (key === 'rfbsReturns') return '退货接口无权限，请确认退货接口权限';
    return 'OZON 接口无权限，请确认店铺 API 权限';
  }
  if (/timeout|timed?\s*out|超时/i.test(raw)) return 'OZON 接口响应超时，请稍后刷新';
  if (/rate\s*limit|too\s*many\s*requests|429|限流|频率/i.test(raw)) return 'OZON 官方接口限流，请稍后刷新';
  return raw.length > 52 ? `${raw.slice(0, 52)}...` : raw;
}

function renderOzonHomeTasks() {
  const listEl = document.getElementById('ozonHomeTaskList');
  const metaEl = document.getElementById('ozonHomeTaskMeta');
  if (!listEl && !metaEl) return;

  const summary = ozonState.homeTasks || {};
  const tasks = Array.isArray(summary.tasks) ? summary.tasks : [];
  const shops = Array.isArray(summary.shops) ? summary.shops : [];
  const totals = summary.totals || {};
  const errors = Array.isArray(summary.errors) ? summary.errors : [];
  const fetchedAt = summary.fetchedAt || summary.__cacheSavedAt || '';
  const numberText = value => {
    if (value === null || value === undefined || value === '') return '--';
    const n = Number(value);
    return Number.isFinite(n) ? n.toLocaleString('zh-CN') : '--';
  };
  const countClass = value => {
    if (value === null || value === undefined || value === '') return 'is-unknown';
    return Number(value || 0) > 0 ? 'is-hot' : 'is-zero';
  };

  if (metaEl) {
    if (summary.loading) {
      metaEl.textContent = '正在读取服务器任务快照...';
    } else if (summary.error) {
      metaEl.textContent = summary.error;
    } else {
      const parts = [
        fetchedAt ? `更新：${formatOzonDateTime(fetchedAt)}` : '服务器任务快照',
        shops.length ? `${shops.length} 个店铺` : '',
        errors.length ? `${errors.length} 项接口需检查权限` : ''
      ].filter(Boolean);
      metaEl.textContent = parts.join(' · ');
    }
  }

  if (!listEl) return;
  if (summary.loading && !tasks.length) {
    listEl.innerHTML = `<div class="ozon-home-task-row is-empty">
      <span class="ozon-home-task-check"></span>
      <span class="ozon-home-task-label">正在读取 OZON 任务快照...</span>
      <span class="ozon-home-task-count">--</span>
    </div>`;
    return;
  }
  if (summary.error && !tasks.length) {
    listEl.innerHTML = `<div class="ozon-home-task-row is-error">
      <span class="ozon-home-task-check"></span>
      <span class="ozon-home-task-label">${escapeHtml(summary.error)}</span>
      <span class="ozon-home-task-count">!</span>
    </div>`;
    return;
  }
  if (shops.length) {
    const totalRows = [
      { key: 'rfbsReturns', label: '退货', value: totals.rfbsReturns },
      { key: 'buyerMessages', label: '消息', value: totals.buyerMessages },
      { key: 'questions', label: '商品问题', value: totals.questions }
    ];
    listEl.innerHTML = `
      <div class="ozon-home-task-total-row">
        ${totalRows.map(item => `<button class="ozon-home-task-total ${countClass(item.value)}" type="button" data-ozon-home-task-total="${escapeHtml(item.key)}">
          <span>${escapeHtml(item.label)}</span>
          <strong>${escapeHtml(numberText(item.value))}</strong>
        </button>`).join('')}
      </div>
      <div class="ozon-home-task-shop-list">
        ${shops.map(shop => {
          const counts = shop.counts || {};
          const shopErrors = Array.isArray(shop.errors) ? shop.errors : [];
          const shopErrorMessages = shopErrors.map(formatOzonHomeTaskError).filter(Boolean);
          const shopErrorText = shopErrorMessages.length > 2
            ? `${shopErrorMessages.slice(0, 2).join('；')}；还有 ${shopErrorMessages.length - 2} 项异常`
            : shopErrorMessages.join('；');
          const status = shop.status === 'warning' || shopErrors.length ? 'is-warning' : '';
          return `<div class="ozon-home-task-shop ${status}" data-ozon-home-task-shop="${escapeHtml(shop.accountId || '')}">
            <div class="ozon-home-task-shop-main">
              <div class="ozon-home-task-shop-name" title="${escapeHtml(shop.accountTitle || 'OZON 店铺')}">${escapeHtml(shop.accountTitle || 'OZON 店铺')}</div>
              <div class="ozon-home-task-shop-meta">${shopErrors.length ? escapeHtml(shopErrorText || `${shopErrors.length} 项接口异常`) : `同步：${escapeHtml(formatOzonDateTime(shop.fetchedAt || fetchedAt) || '--')}`}</div>
            </div>
            <div class="ozon-home-task-shop-actions">
              <button class="ozon-home-task-chip ${countClass(counts.rfbsReturns)}" type="button" data-ozon-shop-task-key="rfbsReturns" data-ozon-shop-task-account="${escapeHtml(shop.accountId || '')}">退 ${escapeHtml(numberText(counts.rfbsReturns))}</button>
              <button class="ozon-home-task-chip ${countClass(counts.buyerMessages)}" type="button" data-ozon-shop-task-key="buyerMessages" data-ozon-shop-task-account="${escapeHtml(shop.accountId || '')}">信 ${escapeHtml(numberText(counts.buyerMessages))}</button>
              <button class="ozon-home-task-chip ${countClass(counts.questions)}" type="button" data-ozon-shop-task-key="questions" data-ozon-shop-task-account="${escapeHtml(shop.accountId || '')}">问 ${escapeHtml(numberText(counts.questions))}</button>
            </div>
          </div>`;
        }).join('')}
      </div>`;
    const taskMeta = {
      rfbsReturns: { key: 'rfbsReturns', targetTab: 'ozonOrderList', targetUrl: 'https://seller.ozon.ru/app/returns' },
      buyerMessages: { key: 'buyerMessages', targetTab: 'ozonMessages' },
      questions: { key: 'questions', targetTab: 'ozonMessages', targetUrl: 'https://seller.ozon.ru/app/reviews/questions' }
    };
    listEl.querySelectorAll('[data-ozon-home-task-total]').forEach(button => {
      button.addEventListener('click', () => {
        const key = button.dataset.ozonHomeTaskTotal || '';
        const firstShop = shops.find(shop => Number(shop.counts?.[key] || 0) > 0) || shops[0] || {};
        openOzonTaskTarget({ ...(taskMeta[key] || {}), accountId: firstShop.accountId || '' });
      });
    });
    listEl.querySelectorAll('[data-ozon-shop-task-key]').forEach(button => {
      button.addEventListener('click', (event) => {
        event.stopPropagation();
        const key = button.dataset.ozonShopTaskKey || '';
        const accountId = button.dataset.ozonShopTaskAccount || '';
        openOzonTaskTarget({ ...(taskMeta[key] || {}), accountId });
      });
    });
    return;
  }
  if (!tasks.length) {
    listEl.innerHTML = `<div class="ozon-home-task-row is-empty">
      <span class="ozon-home-task-check"></span>
      <span class="ozon-home-task-label">服务器任务快照暂无数据，请点击刷新</span>
      <span class="ozon-home-task-count">--</span>
    </div>`;
    return;
  }
  const rows = tasks;
  listEl.innerHTML = rows.map((task) => {
    const countText = numberText(task.count);
    const warning = task.status === 'warning';
    return `<button class="ozon-home-task-row ${warning ? 'is-warning' : ''}" type="button" data-ozon-home-task="${escapeHtml(task.key || '')}">
      <span class="ozon-home-task-check"></span>
      <span class="ozon-home-task-label">${escapeHtml(task.label || 'OZON 任务')}</span>
      <span class="ozon-home-task-count">${escapeHtml(countText)}</span>
    </button>`;
  }).join('');
  listEl.querySelectorAll('[data-ozon-home-task]').forEach((button) => {
    button.addEventListener('click', () => {
      const key = button.dataset.ozonHomeTask || '';
      const task = rows.find(item => String(item.key || '') === key);
      openOzonTaskTarget(task);
    });
  });
}

async function loadOzonHomeTasks(options = {}) {
  const seq = (ozonState.homeTaskRequestSeq || 0) + 1;
  ozonState.homeTaskRequestSeq = seq;
  const refreshBtn = document.getElementById('ozonHomeTaskRefreshBtn');
  if (!ozonState.homeTasks) {
    ozonState.homeTasks = { loading: true };
    renderOzonHomeTasks();
  }
  if (refreshBtn) refreshBtn.disabled = true;
  try {
    return await loadOzonCached('/tasks/summary', (result) => {
      if (seq !== ozonState.homeTaskRequestSeq) return;
      ozonState.homeTasks = result || {};
      renderOzonHomeTasks();
    }, {
      ...options,
      allowStale: false,
      keepStaleOnError: false,
      cache: false
    });
  } catch (err) {
    if (seq === ozonState.homeTaskRequestSeq) {
      ozonState.homeTasks = { error: err.message || 'OZON 今日任务读取失败' };
      renderOzonHomeTasks();
    }
    throw err;
  } finally {
    if (refreshBtn) refreshBtn.disabled = false;
  }
}

async function refreshOzonHomeTaskSnapshots() {
  await ozonRequest('/tasks/summary/refresh', {
    method: 'POST',
    cache: false,
    body: '{}'
  });
  setOzonResult('已开始刷新 OZON 今日任务，系统会按店铺更新服务器数据', 'success');
  for (let index = 0; index < 6; index += 1) {
    await wait(index === 0 ? 800 : 1500);
    const tasks = await ozonRequest('/sync-tasks?type=task-summary&limit=1', { cache: false }).catch(() => null);
    const latest = Array.isArray(tasks?.tasks) ? tasks.tasks[0] : null;
    if (!latest || !['running', 'pending'].includes(String(latest.status || '').toLowerCase())) break;
  }
  return loadOzonHomeTasks({ forceRefresh: true, cache: false });
}

function renderOzonOverview() {
  const overview = ozonState.overview || {};
  const stats = overview.stats || {};
  const quality = overview.quality || {};
  const fulfillment = quality.fulfillment || {};
  const account = overview.selectedAccount || null;
  const user = getCurrentServerUser();
  const hasOverviewData = Boolean(ozonState.overview && Object.keys(ozonState.overview || {}).length);
  const hasAccountData = Array.isArray(ozonState.accounts) && ozonState.accounts.length > 0;
  if (!hasOverviewData && !hasAccountData) {
    const errorText = ozonState.overviewError || ozonState.accountsError || '';
    const valueText = errorText ? '--' : '读取中';
    ['ozonTodayOrders', 'ozonSevenDayOrders', 'ozonMonthQty'].forEach(id => setOzonText(id, valueText));
    ['ozonTodayAmount', 'ozonSevenDayAmount', 'ozonMonthAmount'].forEach(id => setOzonText(id, valueText));
    ['ozonTodayAmountRate', 'ozonMonthAmountRate', 'ozonSevenDayAmountRate'].forEach(id => setOzonText(id, ''));
    setOzonText('ozonAccountApiStatus', errorText ? '读取失败' : '正在读取授权');
    setOzonText('ozonHomeOrderSyncTime', errorText ? '订单缓存：读取失败' : '订单缓存：读取中');
    setOzonText('ozonHomeAuthorizedAccountSummary', errorText ? '读取失败' : '读取中');
    const authorizedList = document.getElementById('ozonHomeAuthorizedAccountList');
    if (authorizedList) authorizedList.innerHTML = errorText
      ? `<div class="ozon-home-authorized-empty">店铺授权读取失败：${escapeHtml(errorText)}</div>`
      : '<div class="ozon-home-authorized-empty">正在读取店铺授权...</div>';
    renderOzonHomeSalesChart([]);
    renderOzonHomeShopSalesDistribution([], []);
    renderOzonHomeTasks();
    return;
  }
  const setText = (id, value) => {
    const el = document.getElementById(id);
    if (el) el.textContent = value;
  };
  const orderDailyRows = Array.isArray(overview.orderDaily) ? overview.orderDaily : [];
  const todayOrders = Number(stats.todayOrders ?? stats.todayOrderCount ?? stats.todayQty ?? 0) || 0;
  const sevenDayOrders = Number(stats.sevenDayOrders ?? stats.sevenDayOrderCount ?? stats.sevenDayQty ?? 0) || 0;
  const todayAmount = Number(stats.todayAmount || 0) || 0;
  const monthOrders = Number(stats.monthOrders ?? stats.monthOrderCount ?? stats.monthQty ?? 0) || 0;
  const monthAmount = Number(stats.monthAmount || 0) || 0;
  const authorizedAccountCount = getAuthorizedOzonAccounts().length;
  const accountCount = Number(stats.accountCount || authorizedAccountCount || 0) || 0;
  const apiAccountCount = ozonState.accounts.length || 0;
  const orderSyncAt = stats.lastOrderCacheAt || stats.lastSyncAt || account?.lastSyncAt;
  setText('ozonTodayOrders', String(todayOrders));
  setText('ozonSevenDayOrders', String(sevenDayOrders));
  setText('ozonTotalOrders', String(stats.totalOrders || 0));
  setText('ozonTodayAmount', formatOzonCnyAmount(todayAmount));
  setText('ozonTodayAmountRate', '');
  setText('ozonMonthQty', String(monthOrders || 0));
  setText('ozonMonthAmount', formatOzonCnyAmount(monthAmount || 0));
  setText('ozonMonthAmountRate', '');
  setText('ozonSevenDayAmount', formatOzonCnyAmount(stats.sevenDayAmount || 0));
  setText('ozonSevenDayAmountRate', '');
  setText('ozonProductCount', String(stats.productCount || ozonState.products.length || 0));
  setText('ozonCollectionCount', String(stats.collectionCount || 0));
  setText('ozonSelectedCount', String(stats.selectedCount || 0));
  setText('ozonReadyCount', String(stats.readyCount || 0));
  setText('ozonListedCount', String(stats.listedCount || 0));
  setText('ozonAccountApiStatus', apiAccountCount ? `已授权：${authorizedAccountCount}个 / 已绑定：${apiAccountCount}个` : '卡密授权：未绑定');
  setText('ozonHomeOrderSyncTime', `订单缓存：${orderSyncAt ? formatOzonDateTime(orderSyncAt) : '--'}`);
  setText('ozonQualityPriceRed', String(quality.priceRiskProducts || quality.priceIndexCounts?.red || 0));
  setText('ozonQualityLowImage', String((quality.lowImageProducts || 0) + (quality.noImageProducts || 0)));
  setText('ozonFulfillmentPackaging', String(fulfillment.awaitingPackaging || 0));
  setText('ozonFulfillmentDeliver', String(fulfillment.awaitingDeliver || 0));
  setText('ozonFulfillmentDelivering', String(fulfillment.delivering || 0));
  setText('ozonFulfillmentCancelled', String(fulfillment.cancelled || 0));
  renderOzonHomeSalesChart(orderDailyRows);
  renderOzonHomeShopSalesDistribution(Array.isArray(overview.accountSales) ? overview.accountSales : Array.isArray(overview.salesByAccount) ? overview.salesByAccount : [], ozonState.accounts);
  renderOzonHomeAuthorizedAccounts(ozonState.accounts);
  renderOzonHomeTasks();
  const statusList = document.getElementById('ozonOrderStatusList');
  if (statusList) {
    const rows = Array.isArray(overview.orderStatus) ? overview.orderStatus : [];
    statusList.innerHTML = rows.length
      ? rows.map(row => `<div class="ozon-list-item"><div class="ozon-list-title">${escapeHtml(row.label || getOzonOrderStatusLabel(row.status) || '未知状态')}</div><div class="ozon-list-meta">缓存订单数：${escapeHtml(row.count || 0)}</div></div>`).join('')
      : '<div class="ozon-empty">暂无订单状态数据</div>';
  }
  const riskList = document.getElementById('ozonQualityRiskList');
  if (riskList) {
    const riskItems = Array.isArray(quality.riskItems) ? quality.riskItems : [];
    riskList.innerHTML = riskItems.length
      ? riskItems.slice(0, 6).map(item => `<div class="ozon-list-item"><div class="ozon-list-title">${escapeHtml(item.name || 'OZON 商品')}</div><div class="ozon-list-meta">Offer: ${escapeHtml(item.offerId || '--')} · SKU: ${escapeHtml(item.sku || '--')} · ${escapeHtml(item.visibility || 'UNKNOWN')}<br>${escapeHtml((item.reasons || []).join(' / '))}</div></div>`).join('')
      : '<div class="ozon-empty">暂无商品质量风险</div>';
  }
}

function renderOzonProductMiniEditIcon() {
  return `<svg class="ozon-product-edit-svg" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
    <rect x="2" y="2" width="12" height="12" rx="2.2" fill="none" stroke="currentColor" stroke-width="1.15"></rect>
    <path d="M10.2 4.5 11.5 5.8 7.1 10.2 5.3 10.7 5.8 8.9 10.2 4.5Z" fill="none" stroke="currentColor" stroke-width="1.15" stroke-linecap="round" stroke-linejoin="round"></path>
  </svg>`;
}

function renderOzonProductPriceLines(ui = {}) {
  const price = ui.price || {};
  const currency = price.currency || 'RUB';
  return [
    { label: '销售价', value: price.price, kind: 'money' },
    { label: '划线价', value: price.oldPrice, kind: 'money' },
    { label: '最低价', value: price.minPrice, kind: 'money' },
    { label: '促销价', value: price.promoPrice, kind: 'money' }
  ].map((item) => {
    const text = item.kind === 'money' ? formatOzonAmount(item.value, currency) : (item.value || '--');
    return `<div class="ozon-product-price-line"><span>${escapeHtml(item.label)}</span><strong>${escapeHtml(text)}</strong></div>`;
  }).join('');
}

function renderOzonProductPriceCell(ui = {}, key = '') {
  const price = ui.price || {};
  const currency = price.currency || 'RUB';
  const primaryPrice = formatOzonAmount(price.price, currency);
  const secondaryLines = [
    { label: '划线价', value: price.oldPrice },
    { label: '最低价', value: price.minPrice },
    { label: '促销价', value: price.promoPrice }
  ].map(item => `<div class="ozon-product-price-line"><span>${escapeHtml(item.label)}</span><strong>${escapeHtml(formatOzonAmount(item.value, currency))}</strong></div>`).join('');
  return `<div class="ozon-product-price-edit-cell">
    <div class="ozon-product-price-stack">
      <div class="ozon-product-price-line is-primary"><span>销售价</span><strong>${escapeHtml(primaryPrice)}</strong></div>
      ${secondaryLines}
    </div>
    <button class="ozon-product-mini-edit" type="button" data-ozon-product-edit-price="${escapeHtml(key)}" title="编辑价格" aria-label="编辑价格">${renderOzonProductMiniEditIcon()}</button>
  </div>`;
}

function renderOzonProductInfoCell(product = {}) {
  const ui = normalizeOzonProductForUi(product);
  const raw = product.rawData || product.raw || {};
  const image = renderOzonClickableImageThumb(ui.image, 'ozon-product-online-thumb', ui.title);
  const productId = normalizeOzonText(ui.productId || raw.product_id || raw.productId || '');
  const offerId = normalizeOzonText(ui.offerId || raw.offer_id || raw.offerId || '');
  const sku = normalizeOzonText(ui.sku || raw.sku || '');
  const category = ui.category && ui.category !== '--' ? ui.category : '';
  return `<div class="ozon-product-online-info">
    ${image}
    <div class="ozon-product-online-main">
      <div class="ozon-product-online-title" title="${escapeHtml(ui.title)}">${escapeHtml(ui.title)}</div>
      <div class="ozon-product-online-codes">
        ${offerId ? `<span>货号：<b class="ozon-mono">${escapeHtml(offerId)}</b></span>` : ''}
        ${sku ? `<span>SKU：<b class="ozon-mono">${escapeHtml(sku)}</b></span>` : ''}
        ${category ? `<span class="ozon-product-info-category" title="${escapeHtml(category)}">类目：<b>${escapeHtml(category)}</b></span>` : ''}
      </div>
    </div>
  </div>`;
}

function renderOzonProductCommissionCell(ui = {}) {
  const commission = ui.commission?.percent ? `${escapeHtml(ui.commission.percent)}%` : '--';
  const amount = ui.commission?.amount ? formatOzonAmount(ui.commission.amount, ui.price?.currency || 'RUB') : '';
  const title = ui.commission?.source
    ? 'OZON 商品详情官方 RFBS 佣金；商品同步后刷新'
    : '当前商品缓存没有 OZON 官方 RFBS 佣金';
  return `<div class="ozon-product-commission">
    <div class="ozon-product-commission-rate" title="${escapeHtml(title)}"><span>RFBS</span><strong>${commission}</strong></div>
    ${amount ? `<div class="ozon-product-commission-amount" title="${escapeHtml(title)}"><span>佣金</span><b>${escapeHtml(amount)}</b></div>` : ''}
  </div>`;
}

function renderOzonProductStatusCell(ui = {}, raw = {}, key = '') {
  const official = getOzonOfficialProductStatusUi(raw, ui.visibility);
  const issues = getOzonProductIssueDetails({ raw });
  const rawStatus = official.rawStatus || normalizeOzonText(raw.statuses?.status_name || raw.statuses?.statusName || raw.statuses?.status || raw.status || ui.visibility || '');
  const title = [
    rawStatus,
    raw.statuses?.status_description || raw.statuses?.statusDescription || '',
    ui.errorText || ''
  ].map(formatOzonStructuredText).filter(Boolean).join(' / ');
  const statusClass = getOzonStatusClass(official.visibility || ui.visibility);
  const statusLabel = official.label || ui.statusLabel || '未知';
  const issueButton = issues.length
    ? `<button class="ozon-product-defect-badge" type="button" data-ozon-product-error-detail="${escapeHtml(key)}" title="查看缺陷明细">${escapeHtml(String(issues.length))}</button>`
    : '<span class="ozon-product-defect-placeholder" aria-hidden="true"></span>';
  return `<div class="ozon-product-status-cell">
    <div class="ozon-product-status-line">
      <span class="ozon-status-pill ${escapeHtml(statusClass || '')}" title="${escapeHtml(title || statusLabel)}">${escapeHtml(statusLabel)}</span>
      ${issueButton}
    </div>
  </div>`;
}

function getOzonProductIdentity(product = {}) {
  const ui = normalizeOzonProductForUi(product);
  const raw = product.rawData || product.raw || {};
  return {
    accountId: normalizeOzonText(product.ozonAccountId || product.ozon_account_id || product.accountId || raw.ozon_account_id || raw.ozonAccountId || ''),
    productId: normalizeOzonText(ui.productId || raw.product_id || raw.productId || raw.id || ''),
    offerId: normalizeOzonText(ui.offerId || raw.offer_id || raw.offerId || ''),
    sku: normalizeOzonText(ui.sku || raw.sku || raw.sku_id || raw.skuId || '')
  };
}

function getOzonProductDiagnosisIdentity(item = {}) {
  const product = item.product || {};
  const productIdentity = getOzonProductIdentity(product);
  const sales = item.sales || {};
  return {
    accountId: normalizeOzonText(productIdentity.accountId || sales.accountId || sales.ozonAccountId || ''),
    productId: normalizeOzonText(productIdentity.productId || sales.productId || ''),
    offerId: normalizeOzonText(productIdentity.offerId || sales.offerId || ''),
    sku: normalizeOzonText(productIdentity.sku || sales.sku || sales.key || '')
  };
}

function getOzonProductIdentityLookupKeys(identity = {}) {
  const accountId = normalizeOzonText(identity.accountId || '');
  const candidates = [
    ['product', identity.productId],
    ['offer', identity.offerId],
    ['sku', identity.sku]
  ].map(([type, value]) => [type, normalizeOzonText(value || '')]).filter(([, value]) => value);
  const keys = [];
  candidates.forEach(([type, value]) => {
    if (accountId) keys.push(`${accountId}|${type}|${value}`);
    keys.push(`*|${type}|${value}`);
  });
  return keys;
}

function buildOzonProductListDiagnosisLookup() {
  const lookup = new Map();
  const items = Array.isArray(ozonState.productListDiagnosis?.items) ? ozonState.productListDiagnosis.items : [];
  items.forEach((item) => {
    const identity = getOzonProductDiagnosisIdentity(item);
    getOzonProductIdentityLookupKeys(identity).forEach((key) => {
      if (!lookup.has(key)) lookup.set(key, item);
    });
  });
  return lookup;
}

function getOzonProductListDiagnosisItem(product = {}, lookup = null) {
  const map = lookup || buildOzonProductListDiagnosisLookup();
  const identity = getOzonProductIdentity(product);
  return getOzonProductIdentityLookupKeys(identity).map(key => map.get(key)).find(Boolean) || null;
}

function renderOzonProductScoreRiskCell(product = {}, lookup = null) {
  const item = getOzonProductListDiagnosisItem(product, lookup);
  if (!item) {
    return `<div class="ozon-product-diagnosis-lite is-empty">${ozonState.productListDiagnosisLoading ? '计算中' : '未计算'}</div>`;
  }
  const diag = item.diagnosis || {};
  const scoreTone = diag.tone === 'good' ? 'ozon-score-good' : diag.tone === 'warn' ? 'ozon-score-warn' : 'ozon-score-bad';
  const scoreText = diag.score === undefined || diag.score === null || diag.score === '' ? '--' : `${diag.score}分`;
  return `<div class="ozon-product-diagnosis-lite">
    <span class="ozon-score-badge ${scoreTone}">${escapeHtml(scoreText)}</span>
    <span class="ozon-risk-badge ${scoreTone}">风险 ${escapeHtml(diag.riskLevel || '--')}</span>
  </div>`;
}

function renderOzonProductSalesTrendCell(product = {}, lookup = null) {
  const item = getOzonProductListDiagnosisItem(product, lookup);
  if (!item) {
    return `<div class="ozon-product-sales-lite is-empty">${ozonState.productListDiagnosisLoading ? '计算中' : '未计算'}</div>`;
  }
  const sales = item.sales || {};
  const trend7 = Number(sales.trend7Percent || 0);
  const trend28 = Number(sales.trend28Percent || 0);
  return `<div class="ozon-product-sales-lite">
    <span class="ozon-trend-pill ${escapeHtml(getOzonTrendClass(sales.trendStatus))}">${escapeHtml(getOzonTrendLabel(sales.trendStatus))}</span>
    <span>今日 <b>${escapeHtml((sales.todayOrders ?? sales.todayQty) || 0)}</b></span>
    <span>7日 <b>${escapeHtml((sales.orderCount7 ?? sales.qty7) || 0)}</b></span>
    <span>28日 <b>${escapeHtml((sales.orderCount28 ?? sales.qty28) || 0)}</b></span>
    <span>7日环比 <b>${escapeHtml(trend7.toFixed(1))}%</b></span>
    <span>28日环比 <b>${escapeHtml(trend28.toFixed(1))}%</b></span>
  </div>`;
}

function ensureOzonProductErrorDetailModal() {
  let modal = document.getElementById('ozonProductErrorDetailModal');
  if (modal) return modal;
  modal = document.createElement('div');
  modal.id = 'ozonProductErrorDetailModal';
  modal.className = 'ozon-modal ozon-product-error-detail-modal';
  modal.setAttribute('aria-hidden', 'true');
  modal.innerHTML = `<div class="ozon-modal-panel ozon-product-error-detail-panel">
    <div id="ozonProductErrorDetailBody" class="ozon-product-error-detail-body"></div>
  </div>`;
  modal.addEventListener('click', (event) => {
    if (event.target === modal) setOzonOrderDetailModalVisible(modal, false);
  });
  document.body.appendChild(modal);
  return modal;
}

function openOzonProductErrorDetailModal(key = '') {
  const product = findOzonProductByKey(key);
  if (!product) return;
  const ui = normalizeOzonProductForUi(product);
  const raw = product.rawData || product.raw || {};
  const issues = getOzonProductIssueDetails(product);
  const modal = ensureOzonProductErrorDetailModal();
  const body = modal.querySelector('#ozonProductErrorDetailBody');
  const visibleIssues = issues.slice(0, 5);
  const restCount = Math.max(0, issues.length - visibleIssues.length);
  if (body) {
    body.innerHTML = `<h3>卡片中有缺陷</h3>
    <div class="ozon-product-error-detail-list">
      ${visibleIssues.map(item => `<div class="ozon-product-error-detail-item">
        <span class="ozon-product-error-dot"></span>
        <span class="ozon-product-error-detail-text">${escapeHtml(formatOzonStructuredText(item.description || item.title || item.code || '缺陷'))}</span>
      </div>`).join('')}
      ${restCount ? `<div class="ozon-product-error-more">以及${escapeHtml(String(restCount))}</div>` : ''}
    </div>
    <div class="ozon-product-error-detail-actions">
      <button class="btn btn-primary" type="button" data-ozon-product-error-close>更正</button>
      <button class="btn btn-default" type="button" data-ozon-product-error-close>查看</button>
    </div>`;
    body.querySelectorAll('[data-ozon-product-error-close]').forEach(button => {
      button.addEventListener('click', () => setOzonOrderDetailModalVisible(modal, false), { once: true });
    });
  }
  setOzonOrderDetailModalVisible(modal, true);
}

function renderOzonProductStockCell(ui = {}, key = '') {
  if (key && ozonState.productStockSyncingKeys.has(key)) {
    return '<div class="ozon-product-stock-syncing" title="正在从 OZON 同步最新库存">正在同步</div>';
  }
  const rawAvailable = ui.stock?.available;
  const available = rawAvailable === undefined || rawAvailable === null || rawAvailable === '' ? '--' : rawAvailable;
  return `<div class="ozon-product-stock">
    <strong>${escapeHtml(available)}</strong>
  </div>`;
}

function renderOzonProductStockEditCell(ui = {}, key = '') {
  const isSyncing = Boolean(key && ozonState.productStockSyncingKeys.has(key));
  return `<div class="ozon-product-stock-edit-cell${isSyncing ? ' is-syncing' : ''}">
    <div class="ozon-product-stock-main${isSyncing ? ' is-syncing' : ''}">
      ${renderOzonProductStockCell(ui, key)}
      ${isSyncing ? '' : `<button class="ozon-product-mini-edit" type="button" data-ozon-product-edit-stock="${escapeHtml(key)}" title="编辑库存" aria-label="编辑库存">${renderOzonProductMiniEditIcon()}</button>`}
    </div>
  </div>`;
}

function renderOzonProductActionCell(product = {}) {
  const key = getOzonProductKey(product);
  const ui = normalizeOzonProductForUi(product);
  const canCopy = Boolean(ui.productId || ui.offerId || ui.sku);
  const productUrl = getOzonProductUrl({ sku: ui.sku, productId: ui.productId, rawData: product.rawData || product.raw || {} });
  const canOpenProduct = Boolean(productUrl);
  return `<div class="ozon-product-row-actions">
    <button class="btn btn-default btn-sm" type="button" data-ozon-product-open-front="${escapeHtml(productUrl)}" ${canOpenProduct ? '' : 'disabled'}>商品页</button>
    <button class="btn btn-default btn-sm" type="button" data-ozon-product-open-seller="${escapeHtml(key)}">后台</button>
    <button class="btn btn-default btn-sm" type="button" data-ozon-product-edit="${escapeHtml(key)}">编辑</button>
    <button class="btn btn-default btn-sm" type="button" data-ozon-product-sync-one="${escapeHtml(key)}">同步</button>
    <button class="btn btn-default btn-sm" type="button" data-ozon-product-copy-key="${escapeHtml(key)}" ${canCopy ? '' : 'disabled'}>复制</button>
  </div>`;
}

function renderOzonProducts(options = {}) {
  const wrap = document.getElementById('ozonProductList');
  const count = document.getElementById('ozonProductCount');
  if (count) count.textContent = String(ozonState.products.length || 0);
  syncOzonAccountSelects();
  updateOzonProductListStatusTabs();
  updateOzonProductSummary();
  const productPagination = document.getElementById('ozonProductPagination');
  if (productPagination) productPagination.style.display = '';
  if (!wrap) {
    updateOzonPagination('product');
    return;
  }
  wrap.style.display = '';
  if (!hasOzonAccounts()) {
    clearOzonProductSelection();
    wrap.innerHTML = renderOzonNeedAccountEmpty('还没有绑定 OZON 店铺。添加授权并同步商品后，这里会展示商品列表。');
    updateOzonPagination('product');
    updateOzonProductSelectionUi();
    return;
  }
  if (!ozonState.products.length) {
    clearOzonProductSelection();
    wrap.innerHTML = '<div class="ozon-empty">当前店铺/状态暂无服务器商品缓存，请点击“同步所有商品”后再查看。</div>';
    updateOzonPagination('product');
    updateOzonProductSelectionUi();
    return;
  }
  (ozonState.products || []).forEach((product) => {
    const key = getOzonProductKey(product);
    if (key && ozonState.productSelectedIds.has(key)) ozonState.productSelectedRows.set(key, product);
  });
  const rows = ozonState.products.map((product) => {
    const ui = normalizeOzonProductForUi(product);
    const raw = product.rawData || product.raw || {};
    const key = getOzonProductKey(product);
    const checked = ozonState.productSelectedIds.has(key) ? ' checked' : '';
    return `<tr>
      <td class="ozon-product-select-cell"><input type="checkbox" data-ozon-product-check="${escapeHtml(key)}"${checked}></td>
      <td>${renderOzonProductInfoCell(product)}</td>
      <td>${renderOzonProductCommissionCell(ui)}</td>
      <td>${renderOzonProductStatusCell(ui, raw, key)}</td>
      <td>${renderOzonProductPriceCell(ui, key)}</td>
      <td>${renderOzonProductStockEditCell(ui, key)}</td>
      <td><div class="ozon-product-date"><strong>${escapeHtml(formatOzonDateTime(ui.createdAt))}</strong><span>更新：${escapeHtml(formatOzonDateTime(ui.updatedAt))}</span></div></td>
      <td>${renderOzonProductActionCell(product)}</td>
    </tr>`;
  }).join('');
  wrap.innerHTML = `<table class="ozon-table ozon-product-online-table">
    ${renderOzonColumnGroup(OZON_PRODUCT_LIST_COLUMN_DEFAULTS, OZON_PRODUCT_LIST_TABLE_KEY)}
    <thead>
      <tr>
        <th class="ozon-product-select-cell"><input type="checkbox" id="ozonProductSelectAll" aria-label="选择当前页全部商品"></th>
        <th>商品信息</th>
        <th>RFBS佣金</th>
        <th>状态</th>
        <th><span class="ozon-product-th-action">价格<button class="ozon-product-mini-edit ozon-product-header-edit" id="ozonProductHeaderBatchPriceBtn" type="button" data-ozon-product-batch-price title="批量改价" aria-label="批量改价" disabled>${renderOzonProductMiniEditIcon()}</button></span></th>
        <th><span class="ozon-product-th-action">库存<button class="ozon-product-mini-edit ozon-product-header-edit" id="ozonProductHeaderBatchStockBtn" type="button" data-ozon-product-batch-stock title="批量改库存" aria-label="批量改库存" disabled>${renderOzonProductMiniEditIcon()}</button></span></th>
        <th>创建日期</th>
        <th>操作</th>
      </tr>
    </thead>
    <tbody>${rows}</tbody>
  </table>`;
  if (Number.isFinite(Number(options.restoreScrollTop))) {
    wrap.scrollTop = Math.max(0, Number(options.restoreScrollTop));
  } else {
    wrap.scrollTop = 0;
  }
  setupOzonResizableTable(wrap.querySelector('.ozon-product-online-table'), OZON_PRODUCT_LIST_TABLE_KEY, OZON_PRODUCT_LIST_COLUMN_DEFAULTS, OZON_PRODUCT_LIST_COLUMN_MINS);
  bindOzonProductListDelegates(wrap);
  updateOzonPagination('product');
  updateOzonProductSelectionUi();
}

function findOzonProductByKey(key = '') {
  const normalized = normalizeOzonText(key);
  if (!normalized) return null;
  return (ozonState.products || []).find(product => getOzonProductKey(product) === normalized) || null;
}

function getCurrentOzonProductPageKeys() {
  return (ozonState.products || []).map(getOzonProductKey).filter(Boolean);
}

function updateOzonProductSelectionUi() {
  const selectedCount = Number(ozonState.productSelectedIds?.size || 0);
  const currentKeys = getCurrentOzonProductPageKeys();
  const selectedCurrent = currentKeys.filter(key => ozonState.productSelectedIds.has(key)).length;
  document.querySelectorAll('[data-ozon-product-check]').forEach((input) => {
    const key = normalizeOzonText(input.dataset?.ozonProductCheck || '');
    input.checked = Boolean(key && ozonState.productSelectedIds.has(key));
  });
  const selectAll = document.getElementById('ozonProductSelectAll');
  if (selectAll) {
    selectAll.checked = Boolean(currentKeys.length && selectedCurrent === currentKeys.length);
    selectAll.indeterminate = Boolean(selectedCurrent > 0 && selectedCurrent < currentKeys.length);
    selectAll.disabled = !currentKeys.length;
  }
  const selectedBtn = document.getElementById('ozonSyncSelectedProductsBtn');
  if (selectedBtn) {
    selectedBtn.disabled = selectedCount <= 0;
    selectedBtn.textContent = selectedCount > 0 ? `同步所选商品(${selectedCount})` : '同步所选商品';
  }
  const copyBtn = document.getElementById('ozonBulkCopyProductIdsBtn');
  if (copyBtn) copyBtn.disabled = selectedCount <= 0;
  [
    'ozonBulkArchiveProductsBtn',
    'ozonProductHeaderBatchPriceBtn',
    'ozonProductHeaderBatchStockBtn',
    'ozonBulkProductPromoBtn',
    'ozonBulkProductRepairImagesBtn',
    'ozonBulkProductRepairCopyBtn'
  ].forEach((id) => {
    const button = document.getElementById(id);
    if (button) button.disabled = selectedCount <= 0;
  });
}

function applyOzonProductSelectionInput(input, checked, wrap = document.getElementById('ozonProductList')) {
  if (!input || input.disabled) return false;
  if (input.id === 'ozonProductSelectAll') {
    const nextChecked = Boolean(checked);
    input.checked = nextChecked;
    (ozonState.products || []).forEach((product) => {
      setOzonProductSelected(product, nextChecked);
    });
    wrap?.querySelectorAll?.('[data-ozon-product-check]')?.forEach(rowInput => {
      rowInput.checked = nextChecked;
    });
    updateOzonProductSelectionUi();
    return true;
  }
  if (input.matches?.('[data-ozon-product-check]')) {
    const key = normalizeOzonText(input.dataset.ozonProductCheck || '');
    if (!key) return false;
    input.checked = Boolean(checked);
    setOzonProductSelected(key, input.checked);
    updateOzonProductSelectionUi();
    return true;
  }
  return false;
}

function toggleOzonProductSelectionInput(input, wrap = document.getElementById('ozonProductList')) {
  if (!input || input.disabled) return false;
  const viewport = captureOzonProductViewport();
  const changed = applyOzonProductSelectionInput(input, !input.checked, wrap);
  if (changed) restoreOzonProductViewport(viewport);
  return changed;
}

function bindOzonProductListDelegates(wrap) {
  if (!wrap || wrap.dataset.ozonProductDelegateBound === '1') return;
  wrap.dataset.ozonProductDelegateBound = '1';
  const handleManualSelectionToggle = (event) => {
    const batchPriceButton = event.target?.closest?.('[data-ozon-product-batch-price]');
    if (event.type === 'pointerdown' && batchPriceButton && wrap.contains(batchPriceButton)) {
      ozonState.productBatchPriceOpenScrollTop = wrap.scrollTop;
      return;
    }
    const batchStockButton = event.target?.closest?.('[data-ozon-product-batch-stock]');
    if (event.type === 'pointerdown' && batchStockButton && wrap.contains(batchStockButton)) {
      ozonState.productBatchStockOpenScrollTop = wrap.scrollTop;
      return;
    }
    const input = event.target?.closest?.('#ozonProductSelectAll,[data-ozon-product-check]');
    if (!input || !wrap.contains(input) || input.disabled) return;
    if (event.type === 'pointerdown' && typeof event.button === 'number' && event.button !== 0) return;
    event.preventDefault();
    event.stopPropagation();
    const now = Date.now();
    const last = Number(input.dataset.ozonManualSelectionAt || 0);
    if (event.type === 'click' && now - last < 350) return;
    input.dataset.ozonManualSelectionAt = String(now);
    toggleOzonProductSelectionInput(input, wrap);
  };
  wrap.addEventListener('pointerdown', handleManualSelectionToggle, true);
  wrap.addEventListener('click', handleManualSelectionToggle, true);
  wrap.addEventListener('change', (event) => {
    const selectAll = event.target?.closest?.('#ozonProductSelectAll');
    if (selectAll) {
      applyOzonProductSelectionInput(selectAll, selectAll.checked, wrap);
      return;
    }
    const checkbox = event.target?.closest?.('[data-ozon-product-check]');
    if (!checkbox) return;
    applyOzonProductSelectionInput(checkbox, checkbox.checked, wrap);
  });
  wrap.addEventListener('click', (event) => {
    const selectCell = event.target?.closest?.('.ozon-product-select-cell');
    if (selectCell && wrap.contains(selectCell)) {
      const input = selectCell.querySelector('input[type="checkbox"]');
      if (!input || input.disabled || event.target === input) return;
      event.preventDefault();
      toggleOzonProductSelectionInput(input, wrap);
      return;
    }
    const imageButton = event.target?.closest?.('[data-ozon-preview-image]');
    if (imageButton) {
      event.preventDefault();
      event.stopPropagation();
      openOzonImagePreview(imageButton.dataset.ozonPreviewImage || '', imageButton.dataset.ozonPreviewTitle || '');
      return;
    }
    const copyButton = event.target?.closest?.('[data-ozon-product-copy-key]');
    if (copyButton) {
      event.preventDefault();
      const product = findOzonProductByKey(copyButton.dataset.ozonProductCopyKey || '');
      const ui = normalizeOzonProductForUi(product || {});
      copyOzonText([ui.productId, ui.offerId, ui.sku].filter(Boolean).join('\n'), '商品编号已复制');
      return;
    }
    const syncButton = event.target?.closest?.('[data-ozon-product-sync-one]');
    if (syncButton) {
      event.preventDefault();
      syncSingleOzonProduct(syncButton.dataset.ozonProductSyncOne || '', syncButton);
      return;
    }
    const batchPriceButton = event.target?.closest?.('[data-ozon-product-batch-price]');
    if (batchPriceButton) {
      event.preventDefault();
      openOzonProductBatchPriceModal();
      return;
    }
    const batchStockButton = event.target?.closest?.('[data-ozon-product-batch-stock]');
    if (batchStockButton) {
      event.preventDefault();
      openOzonProductBatchStockModal();
      return;
    }
    const priceButton = event.target?.closest?.('[data-ozon-product-edit-price]');
    if (priceButton) {
      event.preventDefault();
      openOzonProductBatchPriceModal([findOzonProductByKey(priceButton.dataset.ozonProductEditPrice || '')].filter(Boolean));
      return;
    }
    const stockButton = event.target?.closest?.('[data-ozon-product-edit-stock]');
    if (stockButton) {
      event.preventDefault();
      const product = findOzonProductByKey(stockButton.dataset.ozonProductEditStock || '');
      if (product) openOzonProductBatchStockModal([product]);
      return;
    }
    const editButton = event.target?.closest?.('[data-ozon-product-edit]');
    if (editButton) {
      event.preventDefault();
      openOzonProductEditModal(editButton.dataset.ozonProductEdit || '');
      return;
    }
    const errorDetailButton = event.target?.closest?.('[data-ozon-product-error-detail]');
    if (errorDetailButton) {
      event.preventDefault();
      event.stopPropagation();
      openOzonProductErrorDetailModal(errorDetailButton.dataset.ozonProductErrorDetail || '');
      return;
    }
    const frontButton = event.target?.closest?.('[data-ozon-product-open-front]');
    if (frontButton) {
      event.preventDefault();
      const url = frontButton.dataset.ozonProductOpenFront || '';
      if (url) window.open(url, '_blank', 'noopener');
      return;
    }
    const openButton = event.target?.closest?.('[data-ozon-product-open-seller]');
    if (openButton) {
      event.preventDefault();
      window.open('https://seller.ozon.ru/app/products', '_blank', 'noopener');
    }
  });
}

async function syncSingleOzonProduct(key = '', button = null) {
  const product = findOzonProductByKey(key);
  if (!product) {
    setOzonResult('没有找到要同步的商品，请刷新列表后重试', 'error');
    return;
  }
  const accountId = normalizeOzonText(product.ozonAccountId || ozonState.productAccountId || getDefaultOzonAccountId());
  if (!accountId) {
    setOzonResult('请先选择 OZON 店铺', 'error');
    return;
  }
  const ui = normalizeOzonProductForUi(product);
  if (!ui.productId && !ui.offerId && !ui.sku) {
    setOzonResult('该商品缺少 Product ID / Offer ID / SKU，无法定向同步', 'error');
    return;
  }
  const originalText = button?.textContent || '';
  const viewport = captureOzonProductViewport([product]);
  if (button) {
    button.disabled = true;
    button.textContent = '同步中';
  }
  try {
    const result = await ozonRequest('/products/sync', {
      method: 'POST',
      body: JSON.stringify({
        ozonAccountId: accountId,
        includeList: false,
        productIds: ui.productId ? [Number(ui.productId)].filter(n => Number.isFinite(n) && n > 0) : [],
        offerIds: ui.offerId ? [ui.offerId] : [],
        skus: ui.sku ? [Number(ui.sku)].filter(n => Number.isFinite(n) && n > 0) : []
      })
    });
    await Promise.allSettled([
      loadOzonProducts({ forceRefresh: true, preserveScroll: true }),
      loadOzonProductDiagnosis({ enrichAi: false, forceRefresh: true, resetAiQueue: false })
    ]);
    restoreOzonProductViewport(viewport);
    setOzonResult(`单个商品同步完成：保存 ${Number(result.totalSaved || 0)} 个`, 'success');
  } catch (err) {
    setOzonResult(err.message || '单个商品同步失败', 'error');
  } finally {
    if (button) {
      button.disabled = false;
      button.textContent = originalText || '同步';
    }
  }
}

async function syncOzonProductRows(rows = [], accountId = '') {
  const viewport = captureOzonProductViewport(rows);
  const normalizedAccountId = normalizeOzonText(accountId || ozonState.productAccountId || getDefaultOzonAccountId());
  const productIds = [];
  const offerIds = [];
  const skus = [];
  const seenProductIds = new Set();
  const seenOfferIds = new Set();
  const seenSkus = new Set();
  rows.forEach((row) => {
    const ui = normalizeOzonProductForUi(row || {});
    const productId = Number(ui.productId || 0);
    if (Number.isFinite(productId) && productId > 0 && !seenProductIds.has(productId)) {
      seenProductIds.add(productId);
      productIds.push(productId);
    }
    const offerId = normalizeOzonText(ui.offerId || '');
    if (offerId && !seenOfferIds.has(offerId)) {
      seenOfferIds.add(offerId);
      offerIds.push(offerId);
    }
    const sku = Number(ui.sku || 0);
    if (Number.isFinite(sku) && sku > 0 && !seenSkus.has(sku)) {
      seenSkus.add(sku);
      skus.push(sku);
    }
  });
  if (!normalizedAccountId || (!productIds.length && !offerIds.length && !skus.length)) {
    throw new Error('没有可同步的 OZON 商品标识');
  }
  const result = await ozonRequest('/products/sync', {
    method: 'POST',
    body: JSON.stringify({
      ozonAccountId: normalizedAccountId,
      includeList: false,
      productIds,
      offerIds,
      skus
    })
  });
  await loadOzonProducts({ forceRefresh: true, preserveScroll: true });
  restoreOzonProductViewport(viewport);
  return result;
}

function findOzonProductRowElement(key = '') {
  const normalizedKey = normalizeOzonText(key);
  if (!normalizedKey) return null;
  return Array.from(document.querySelectorAll('#ozonProductList tr')).find((row) => (
    normalizeOzonText(row.querySelector('[data-ozon-product-sync-one]')?.dataset.ozonProductSyncOne || '') === normalizedKey
  )) || null;
}

function getOzonProductScrollContainer() {
  return [
    document.getElementById('ozonProductList'),
    document.getElementById('ozonProductOnline'),
    document.querySelector('.content-area'),
    document.scrollingElement
  ].find(element => element && element.scrollHeight > element.clientHeight + 1) || document.scrollingElement;
}

function captureOzonProductViewport(anchorRows = []) {
  const containers = [
    document.scrollingElement,
    document.querySelector('.content-area'),
    document.getElementById('ozonProductOnline'),
    document.getElementById('ozonProductList')
  ].filter((element, index, list) => element && list.indexOf(element) === index)
    .map(element => ({ element, left: element.scrollLeft, top: element.scrollTop }));
  const anchorProduct = (Array.isArray(anchorRows) ? anchorRows : []).find(Boolean);
  const anchorKey = getOzonProductKey(anchorProduct || {});
  const anchorRow = findOzonProductRowElement(anchorKey);
  const scrollContainer = anchorRow ? getOzonProductScrollContainer() : null;
  return {
    left: window.scrollX || 0,
    top: window.scrollY || 0,
    containers,
    anchor: anchorRow && scrollContainer ? {
      key: anchorKey,
      viewportTop: anchorRow.getBoundingClientRect().top,
      scrollContainer
    } : null
  };
}

function restoreOzonProductViewport(viewport = {}) {
  const left = Math.max(0, Number(viewport.left || 0));
  const top = Math.max(0, Number(viewport.top || 0));
  const restore = () => {
    window.scrollTo(left, top);
    (Array.isArray(viewport.containers) ? viewport.containers : []).forEach(({ element, left: containerLeft, top: containerTop }) => {
      if (!element?.isConnected) return;
      element.scrollLeft = Math.max(0, Number(containerLeft || 0));
      element.scrollTop = Math.max(0, Number(containerTop || 0));
    });
    const anchor = viewport.anchor;
    if (anchor?.scrollContainer?.isConnected) {
      const currentRow = findOzonProductRowElement(anchor.key);
      if (currentRow) {
        const delta = currentRow.getBoundingClientRect().top - Number(anchor.viewportTop || 0);
        if (Math.abs(delta) > 0.5) anchor.scrollContainer.scrollTop += delta;
      }
    }
  };
  restore();
  window.requestAnimationFrame(restore);
  window.setTimeout(restore, 50);
}

function setOzonProductStockSyncing(rows = [], syncing = false) {
  // 商品表重绘会默认回到顶部，库存同步状态切换必须保留用户正在查看的位置。
  const restoreScrollTop = Math.max(0, Number(document.getElementById('ozonProductList')?.scrollTop || 0));
  let changed = false;
  (Array.isArray(rows) ? rows : []).forEach((row) => {
    const key = getOzonProductKey(row || {});
    if (!key) return;
    if (syncing) {
      if (!ozonState.productStockSyncingKeys.has(key)) {
        ozonState.productStockSyncingKeys.add(key);
        changed = true;
      }
      return;
    }
    if (ozonState.productStockSyncingKeys.delete(key)) changed = true;
  });
  if (changed) renderOzonProducts({ restoreScrollTop });
}

function renderOzonHomeSalesChart(dailyRows = []) {
  const wraps = [
    document.getElementById('ozonHomeDailySalesBarChart'),
    document.getElementById('ozonSalesBarChart')
  ].filter(Boolean);
  if (!wraps.length) return;
  const rows = (Array.isArray(dailyRows) ? dailyRows : []).slice(-28);
  if (!rows.length) {
    wraps.forEach(wrap => {
      wrap.innerHTML = '<div class="ozon-empty">服务器订单缓存暂无订单趋势数据，请先确认 OZON 通知已写入订单。</div>';
    });
    return;
  }
  const maxOrders = Math.max(1, ...rows.map(row => Number(row.orders ?? row.qty ?? 0)));
  wraps.forEach(wrap => {
    const isHomeChart = wrap.id === 'ozonHomeDailySalesBarChart';
    const chartHeight = Math.max(120, Math.min(220, wrap.getBoundingClientRect?.().height || (isHomeChart ? 214 : 180)));
    const labelReserve = isHomeChart ? 54 : 42;
    const barMaxHeight = Math.max(56, Math.floor((chartHeight - labelReserve) * 0.76));
    const chartWidth = Math.max(0, Math.floor(wrap.getBoundingClientRect?.().width || 0));
    let barGap = isHomeChart && chartWidth < 520 ? 3 : (isHomeChart && chartWidth < 760 ? 4 : 6);
    const horizontalPadding = isHomeChart ? 16 : 0;
    let availableWidth = Math.max(0, chartWidth - horizontalPadding - Math.max(0, rows.length - 1) * barGap);
    if (isHomeChart && rows.length && Math.floor(availableWidth / rows.length) < 7) {
      barGap = 1;
      availableWidth = Math.max(0, chartWidth - horizontalPadding - Math.max(0, rows.length - 1) * barGap);
    }
    const barWidth = isHomeChart
      ? Math.max(6, Math.min(34, Math.floor(availableWidth / Math.max(1, rows.length))))
      : 28;
    const labelStep = isHomeChart
      ? (barWidth < 13 ? 6 : (barWidth < 17 ? 5 : (barWidth < 22 ? 4 : (barWidth < 28 ? 3 : 2))))
      : (rows.length > 34 ? 3 : (rows.length > 22 ? 2 : 1));
    const lastRegularLabelIndex = Math.floor((rows.length - 2) / labelStep) * labelStep;
    const html = rows.map((row, index) => {
      const orders = Number(row.orders ?? row.qty ?? 0);
      const qty = Number(row.qty || 0);
      const amount = Number(row.amount || 0);
      const height = Math.max(orders ? 8 : 2, Math.round((orders / maxOrders) * barMaxHeight));
      const dateText = String(row.date || '').slice(5).replace('-', '/');
      const showLastDate = index === rows.length - 1 && (!isHomeChart || (index - lastRegularLabelIndex) >= Math.max(2, Math.ceil(labelStep / 2)));
      const showDate = index === 0 || showLastDate || (index % labelStep === 0 && index < rows.length - 1);
      return `<div class="ozon-sales-bar ${orders ? '' : 'is-zero'}" title="${escapeHtml(row.date || '')} 订单数 ${escapeHtml(orders)} 单 / 商品件数 ${escapeHtml(qty)} 件 / 销售额 ${escapeHtml(formatOzonCnyAmount(amount))}">
        <div class="ozon-sales-bar-stack">
          <div class="ozon-sales-bar-value">${escapeHtml(orders)}</div>
          <div class="ozon-sales-bar-fill" style="height:${height}px"></div>
        </div>
        <div class="ozon-sales-bar-date">${showDate ? escapeHtml(dateText || '--') : ''}</div>
      </div>`;
    }).join('');
    const homeStyle = isHomeChart ? ` style="--ozon-home-bar-width:${barWidth}px;--ozon-home-bar-gap:${barGap}px;"` : '';
    wrap.innerHTML = `<div class="ozon-sales-bars"${homeStyle}>${html}</div>`;
  });
}

function getOzonIntegrationTone(status = '') {
  if (['online', 'received', 'connected', 'authorized', 'ok'].includes(status)) return 'ok';
  if (['saved', 'pending', 'untested', 'warning'].includes(status)) return 'warn';
  if (['missing', 'offline', 'error', 'disabled'].includes(status)) return 'missing';
  return 'info';
}

function renderOzonHomeIntegrationBadge(label, status, title = '', attrs = '') {
  const tone = getOzonIntegrationTone(status);
  const attrText = attrs ? ` ${attrs}` : '';
  return `<button class="ozon-home-integration-badge is-${tone}" type="button" title="${escapeHtml(title || label)}"${attrText}>${escapeHtml(label)}</button>`;
}

function renderOzonAccountIntegrationBadges(account = {}) {
  const integrations = account.integrations || {};
  const notification = integrations.notification || {};
  const performance = integrations.performance || {};
  const notificationText = notification.statusText || (notification.status === 'received'
    ? '通知已接入'
    : notification.status === 'connected'
      ? '通知已连通'
      : '通知未授权');
  const notificationTitle = [
    notificationText,
    notification.lastEventAt ? `最近通知：${formatOzonDateTime(notification.lastEventAt)}` : '',
    notification.lastEventType ? `类型：${notification.lastEventType}` : '',
    notification.lastPingAt ? `最近连通：${formatOzonDateTime(notification.lastPingAt)}` : ''
  ].filter(Boolean).join(' · ');
  const performanceStatus = performance.status || performance.onlineStatus || (performance.configured ? 'saved' : 'missing');
  const performanceText = performance.statusText || (performanceStatus === 'online'
    ? 'Performance 已授权'
    : performance.configured
      ? 'Performance 已保存'
      : 'Performance 未授权');
  const performanceTitle = [
    performanceText,
    performance.clientId ? `Client ID：${performance.clientId}` : '',
    performance.lastTestAt ? `最近检测：${formatOzonDateTime(performance.lastTestAt)}` : '',
    performance.lastTestMessage || ''
  ].filter(Boolean).join(' · ');
  const officialLink = 'https://seller.ozon.ru/app/settings/performance-api';
  return `<div class="ozon-home-integration-row">
    ${renderOzonHomeIntegrationBadge(notificationText, notification.status || 'missing', notificationTitle, `data-ozon-notification-guide="${escapeHtml(account.id || '')}"`)}
    ${renderOzonHomeIntegrationBadge(performanceText, performanceStatus, performanceTitle, `data-ozon-performance-auth="${escapeHtml(account.id || '')}"`)}
    <a class="ozon-home-integration-link" href="${officialLink}" target="_blank" rel="noopener">Performance 设置</a>
  </div>`;
}

function renderOzonHomeAuthorizedAccounts(accounts = []) {
  const listEl = document.getElementById('ozonHomeAuthorizedAccountList');
  const summaryEl = document.getElementById('ozonHomeAuthorizedAccountSummary');
  if (!listEl && !summaryEl) return;

  const rows = Array.isArray(accounts) ? accounts : [];
  const visibleRows = sortOzonAccountsByHomeListOrder(rows.filter(account => account && account.status !== 'deleted'));

  if (summaryEl) {
    summaryEl.textContent = `共 ${visibleRows.length} 个`;
  }

  if (!listEl) return;
  if (!visibleRows.length) {
    listEl.innerHTML = '<div class="ozon-home-authorized-empty">还没有绑定 OZON 店铺</div>';
    return;
  }

  listEl.innerHTML = visibleRows.map((account) => {
    const active = account.status !== 'disabled';
    const shopAuthorized = isOzonAccountShopAuthorized(account);
    const statusLabel = shopAuthorized ? '已授权' : (active ? '待授权' : '已停用');
    const statusBg = shopAuthorized ? '#dcfce7' : (active ? '#fef3c7' : '#fee2e2');
    const statusColor = shopAuthorized ? '#166534' : (active ? '#92400e' : '#b91c1c');
    return `
      <div class="ozon-home-authorized-row" title="${escapeHtml(account.title || 'OZON 店铺')}">
        <div class="ozon-home-authorized-main">
          <div class="ozon-home-authorized-name">${escapeHtml(account.title || 'OZON 店铺')}</div>
          ${renderOzonAccountIntegrationBadges(account)}
        </div>
        <div class="ozon-home-authorized-right">
          <span style="display:inline-flex;align-items:center;padding:1px 7px;border-radius:999px;background:${statusBg};color:${statusColor};font-size:11px;font-weight:800;line-height:18px;">${escapeHtml(statusLabel)}</span>
        </div>
      </div>`;
  }).join('');
  bindOzonHomeIntegrationActions(listEl);
}

function bindOzonHomeIntegrationActions(root = document) {
  root.querySelectorAll('[data-ozon-performance-auth]').forEach(button => {
    button.addEventListener('click', () => {
      const accountId = button.dataset.ozonPerformanceAuth || '';
      toggleOzonPerformanceAuthPanel(true, accountId);
    });
  });
  root.querySelectorAll('[data-ozon-notification-guide]').forEach(button => {
    button.addEventListener('click', () => {
      const accountId = button.dataset.ozonNotificationGuide || '';
      toggleOzonNotificationGuideModal(true, accountId);
    });
  });
}

function bindOzonHomeIntegrationDelegates() {
  if (document.body?.dataset?.ozonHomeIntegrationDelegateBound === '1') return;
  if (document.body) document.body.dataset.ozonHomeIntegrationDelegateBound = '1';
  document.addEventListener('click', (event) => {
    const performanceButton = event.target.closest?.('[data-ozon-performance-auth]');
    if (performanceButton) {
      event.preventDefault();
      event.stopPropagation();
      toggleOzonPerformanceAuthPanel(true, performanceButton.dataset.ozonPerformanceAuth || '');
      return;
    }
    const notificationButton = event.target.closest?.('[data-ozon-notification-guide]');
    if (notificationButton) {
      event.preventDefault();
      event.stopPropagation();
      toggleOzonNotificationGuideModal(true, notificationButton.dataset.ozonNotificationGuide || '');
    }
  });
}

function renderOzonHomeShopSalesDistribution(accountSalesRows = [], fallbackAccounts = []) {
  const summaryEl = document.getElementById('ozonHomeShopSalesSummary');
  const listEl = document.getElementById('ozonHomeShopSalesList');
  const donutEl = document.getElementById('ozonHomeShopSalesDonut');
  const donutTitleEl = document.getElementById('ozonHomeShopSalesDonutTitle');
  const donutMetaEl = document.getElementById('ozonHomeShopSalesDonutMeta');
  if (!summaryEl && !listEl && !donutEl) return;

  const rowsFromApi = Array.isArray(accountSalesRows) ? accountSalesRows : [];
  const accounts = Array.isArray(fallbackAccounts) ? fallbackAccounts : [];
  const byAccountId = new Map(rowsFromApi.map(row => [String(row.accountId ?? row.ozonAccountId ?? row.id ?? ''), row]));
  const mergedRows = accounts.length
    ? accounts.map(account => {
        const key = String(account.id ?? '');
        const row = byAccountId.get(key) || {};
        return {
          accountId: account.id,
          accountTitle: account.title || row.accountTitle || `店铺 ${key || '--'}`,
          todayQty: Number(row.todayQty || 0),
          todayOrders: Number(row.todayOrders || 0),
          todayAmount: Number(row.todayAmount || 0),
          sevenDayQty: Number(row.sevenDayQty || row.qty7 || 0),
          monthQty: Number(row.monthQty || 0),
          monthOrders: Number(row.monthOrders || 0),
          monthAmount: Number(row.monthAmount || 0),
          currencyCode: row.currencyCode || account.currencyCode || 'RUB'
        };
      })
    : rowsFromApi.map(row => ({
        accountId: row.accountId ?? row.ozonAccountId ?? row.id ?? '',
        accountTitle: row.accountTitle || row.title || 'OZON 店铺',
        todayQty: Number(row.todayQty || 0),
        todayOrders: Number(row.todayOrders || 0),
        todayAmount: Number(row.todayAmount || 0),
        sevenDayQty: Number(row.sevenDayQty || row.qty7 || 0),
        monthQty: Number(row.monthQty || 0),
        monthOrders: Number(row.monthOrders || 0),
        monthAmount: Number(row.monthAmount || 0),
        currencyCode: row.currencyCode || 'RUB'
      }));

  const mergedKeySet = new Set(mergedRows.map(row => String(row.accountId ?? row.accountTitle ?? '')));
  rowsFromApi.forEach(row => {
    const key = String(row.accountId ?? row.ozonAccountId ?? row.id ?? '');
    if (mergedKeySet.has(key)) return;
    mergedRows.push({
      accountId: key,
      accountTitle: row.accountTitle || row.title || 'OZON 店铺',
      todayQty: Number(row.todayQty || 0),
      todayOrders: Number(row.todayOrders || 0),
      todayAmount: Number(row.todayAmount || 0),
      sevenDayQty: Number(row.sevenDayQty || row.qty7 || 0),
      monthQty: Number(row.monthQty || 0),
      monthOrders: Number(row.monthOrders || 0),
      monthAmount: Number(row.monthAmount || 0),
      currencyCode: row.currencyCode || 'RUB'
    });
  });

  mergedRows.sort((a, b) => Number((b.todayOrders ?? b.todayQty) || 0) - Number((a.todayOrders ?? a.todayQty) || 0) || Number(b.monthOrders || 0) - Number(a.monthOrders || 0) || String(a.accountTitle || '').localeCompare(String(b.accountTitle || ''), 'zh-Hans-CN'));
  if (summaryEl) summaryEl.textContent = '';

  if (donutEl) {
    const activeRows = mergedRows.filter(row => Number(row.todayQty || 0) > 0);
    const totalTodayQty = activeRows.reduce((sum, row) => sum + Number(row.todayQty || 0), 0);
    if (!activeRows.length || totalTodayQty <= 0) {
      donutEl.style.background = 'conic-gradient(#cbd5e1 0 100%)';
      donutEl.title = '暂无当日销量数据';
      if (donutTitleEl) donutTitleEl.textContent = '今日销量占比';
      if (donutMetaEl) donutMetaEl.textContent = '暂无当日销量数据';
    } else {
      const palette = ['#2563eb', '#16a34a', '#7c3aed', '#f59e0b', '#ef4444', '#0891b2', '#f97316', '#4f46e5'];
      const slices = activeRows.slice(0, 6).map((row, index) => ({
        label: row.accountTitle || `店铺 ${index + 1}`,
        value: Number(row.todayQty || 0),
        color: palette[index % palette.length]
      }));
      if (activeRows.length > 6) {
        const otherValue = activeRows.slice(6).reduce((sum, row) => sum + Number(row.todayQty || 0), 0);
        if (otherValue > 0) slices.push({ label: '其他店铺', value: otherValue, color: '#94a3b8' });
      }
      let cursor = 0;
      const gradient = slices
        .filter(slice => Number(slice.value || 0) > 0)
        .map(slice => {
          const start = cursor;
          cursor += (Number(slice.value || 0) / totalTodayQty) * 100;
          return `${slice.color} ${start.toFixed(2)}% ${cursor.toFixed(2)}%`;
        })
        .join(', ');
      donutEl.style.background = gradient ? `conic-gradient(${gradient})` : 'conic-gradient(#cbd5e1 0 100%)';
      donutEl.title = slices.map(slice => `${slice.label}: ${Number(slice.value || 0).toLocaleString()} 件`).join(' / ');
      if (donutTitleEl) donutTitleEl.textContent = `${totalTodayQty.toLocaleString()} 件`;
      if (donutMetaEl) donutMetaEl.textContent = `今日销量 · ${activeRows.length.toLocaleString()} 个店铺`;
    }
  }

  if (!listEl) return;
  if (!mergedRows.length) {
    listEl.innerHTML = '<div class="dh-shop-sales-row"><span class="dh-shop-sales-rank">--</span><div class="dh-shop-sales-name">暂无店铺数据</div><div class="dh-shop-sales-cell">--</div><div class="dh-shop-sales-cell">¥--</div><div class="dh-shop-sales-cell">--</div><div class="dh-shop-sales-cell">¥--</div></div>';
    return;
  }
  listEl.innerHTML = mergedRows.map((row, index) => {
    const monthOrders = Number(row.monthOrders || 0);
    const todayOrders = Number((row.todayOrders ?? row.todayQty) || 0);
    const todayAmount = Number(row.todayAmount || 0);
    const monthAmount = Number(row.monthAmount || 0);
    return `
      <div class="dh-shop-sales-row" title="${escapeHtml(row.accountTitle || 'OZON 店铺')}">
        <span class="dh-shop-sales-rank">${escapeHtml(index + 1)}</span>
        <div class="dh-shop-sales-name">${escapeHtml(row.accountTitle || 'OZON 店铺')}</div>
        <div class="dh-shop-sales-cell">${escapeHtml(todayOrders.toLocaleString())}</div>
        <div class="dh-shop-sales-cell">${escapeHtml(formatOzonCnyAmount(todayAmount))}</div>
        <div class="dh-shop-sales-cell">${escapeHtml(monthOrders.toLocaleString())}</div>
        <div class="dh-shop-sales-cell">${escapeHtml(formatOzonCnyAmount(monthAmount))}</div>
      </div>`;
  }).join('');
}

function setOzonText(id, value) {
  const el = document.getElementById(id);
  if (el && el.textContent !== String(value ?? '')) el.textContent = String(value ?? '');
}

function setOzonHtml(id, html) {
  const el = document.getElementById(id);
  if (el && el.innerHTML !== String(html ?? '')) el.innerHTML = String(html ?? '');
}

function setOzonElementDisplayIfChanged(el, display = '') {
  if (!el) return false;
  const next = String(display || '');
  if (el.style.display === next) return false;
  el.style.display = next;
  return true;
}

function setOzonBadgeCount(id, count = 0) {
  const el = document.getElementById(id);
  if (!el) return false;
  const safeCount = Math.max(0, Number(count || 0));
  const text = formatOzonBadgeCount(safeCount);
  let changed = false;
  if (el.textContent !== text) {
    el.textContent = text;
    changed = true;
  }
  if (setOzonElementDisplayIfChanged(el, safeCount ? 'inline-flex' : 'none')) {
    changed = true;
  }
  return changed;
}

async function withOzonButtonLoading(buttonId, loadingText, task) {
  const button = document.getElementById(buttonId);
  const originalText = button?.textContent || '';
  if (button) {
    button.disabled = true;
    button.textContent = loadingText || '处理中...';
  }
  try {
    return await task();
  } finally {
    if (button) {
      button.disabled = false;
      button.textContent = originalText;
    }
  }
}

function getOzonAccountLabel(accountId = '') {
  const id = normalizeOzonText(accountId);
  if (!id) return '全部店铺';
  return ozonState.accounts.find(account => String(account.id) === id)?.title || `店铺 ${id}`;
}

function getOzonAccountById(accountId = '') {
  const id = normalizeOzonText(accountId);
  if (!id) return null;
  return (ozonState.accounts || []).find(account => String(account.id) === id) || null;
}

function getOzonActiveAccountId(preferred = '') {
  const preferredId = normalizeOzonText(preferred);
  if (preferredId && getAuthorizedOzonAccounts().some(account => String(account.id) === preferredId)) return preferredId;
  return getDefaultOzonAccountId();
}

function getOzonUploadAccountId() {
  const select = document.getElementById('ozonUploadAccountFilter');
  const accountId = getOzonActiveAccountId(select?.value || ozonState.uploadAccountId || '');
  ozonState.uploadAccountId = accountId;
  return accountId;
}

function setOzonUploadTemplateModalVisible(visible) {
  const modal = document.getElementById('ozonUploadTemplateModal');
  if (!modal) return;
  modal.classList.toggle('is-open', Boolean(visible));
  modal.setAttribute('aria-hidden', visible ? 'false' : 'true');
  if (visible) {
    const result = document.getElementById('ozonUploadTemplateModalResult');
    if (result) result.textContent = '';
    window.setTimeout(() => document.getElementById('ozonUploadTemplateName')?.focus(), 0);
  }
}

function openOzonUploadTemplateModalForAccount(accountId = '') {
  const requestedAccountId = String(accountId || '').trim();
  const accountSelect = document.getElementById('ozonUploadAccountFilter');
  if (requestedAccountId) {
    const account = getAuthorizedOzonAccounts().find(item => String(item.id) === requestedAccountId);
    if (!account) return false;
    if (accountSelect) {
      const hasAccount = Array.from(accountSelect.options || []).some(option => String(option.value) === requestedAccountId);
      if (!hasAccount) accountSelect.add(new Option(account.title || account.clientId || `店铺 ${requestedAccountId}`, requestedAccountId));
      accountSelect.value = requestedAccountId;
    }
    ozonState.uploadAccountId = requestedAccountId;
  }
  if (!getOzonUploadAccountId()) return false;
  setOzonUploadTemplateModalVisible(true);
  return true;
}

window.openOzonUploadTemplateModalForAccount = openOzonUploadTemplateModalForAccount;

function setOzonUploadTemplateModalResult(message = '', type = '') {
  const result = document.getElementById('ozonUploadTemplateModalResult');
  if (!result) return;
  result.textContent = message;
  result.className = `ozon-modal-result${type ? ` ${type}` : ''}`;
}

function setOzonUploadResultMessage(message = '', type = 'info') {
  const target = document.getElementById('ozonUploadPrecheckResult');
  if (!target) return;
  const tone = ['success', 'error', 'info'].includes(type) ? type : 'info';
  target.innerHTML = `<div class="ozon-upload-message is-${tone}" role="${tone === 'error' ? 'alert' : 'status'}">${escapeHtml(message || '')}</div>`;
}

function clearOzonUploadPreviewUrls() {
  const urls = Array.isArray(ozonState.uploadPreviewUrls) ? ozonState.uploadPreviewUrls : [];
  urls.forEach((url) => {
    try {
      URL.revokeObjectURL(url);
    } catch (error) {
      console.warn('[OZON upload] revoke preview URL failed:', error?.message || error);
    }
  });
  ozonState.uploadPreviewUrls = [];
}

function createOzonUploadPreviewUrl(file) {
  if (!file || typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function') return '';
  const url = URL.createObjectURL(file);
  ozonState.uploadPreviewUrls.push(url);
  return url;
}

function renderOzonUploadPreviewCell(row = {}) {
  const previews = Array.isArray(row.imagePreviews) ? row.imagePreviews : [];
  if (!previews.length) return '<span class="ozon-upload-preview-empty">无图片</span>';
  const moreCount = Math.max(0, Number(row.imageCount || 0) - previews.length);
  return `<div class="ozon-upload-preview-cell"><div class="ozon-upload-preview-list">${previews.map((image) => `<a href="${escapeHtml(image.url)}" target="_blank" rel="noopener" title="查看第 ${Number(image.order || 0)} 张图片：${escapeHtml(image.name || '')}"><img src="${escapeHtml(image.url)}" alt="第 ${Number(image.order || 0)} 张商品图片"></a>`).join('')}${moreCount ? `<span class="ozon-upload-preview-more">+${moreCount}</span>` : ''}</div><small>共 ${Number(row.imageCount || 0)} 张</small></div>`;
}

function renderOzonUploadTemplates() {
  const list = document.getElementById('ozonUploadTemplateList');
  const select = document.getElementById('ozonUploadTemplateSelect');
  const accountSelect = document.getElementById('ozonUploadAccountFilter');
  if (accountSelect) {
    const current = ozonState.uploadAccountId || getDefaultOzonAccountId();
    accountSelect.innerHTML = getAuthorizedOzonAccounts().map(account => `<option value="${escapeHtml(account.id)}">${escapeHtml(account.title || account.clientId || `店铺 ${account.id}`)}</option>`).join('') || '<option value="">暂无已授权店铺</option>';
    if (current) accountSelect.value = current;
  }
  const templates = Array.isArray(ozonState.uploadTemplates) ? ozonState.uploadTemplates : [];
  if (select) {
    const current = select.value;
    select.innerHTML = '<option value="">请选择模板</option>' + templates.map(template => `<option value="${escapeHtml(template.id)}">${escapeHtml(template.templateName || template.sourceName || '未命名模板')} · Product ID ${escapeHtml(template.sourceProductId || '')}</option>`).join('');
    if (current && templates.some(template => String(template.id) === current)) select.value = current;
  }
  if (!list) return;
  if (!templates.length) {
    list.innerHTML = '<div class="ozon-empty">当前店铺还没有模板。点击右上角“新增模板”保存一条已上架商品。</div>';
    return;
  }
  list.innerHTML = `<table class="ozon-table"><thead><tr><th>模板名称</th><th>来源商品</th><th>Product ID</th><th>类目 / 类型</th><th>属性</th><th>保存时间</th><th>操作</th></tr></thead><tbody>${templates.map(template => {
    const categoryText = [template.categoryFullName, template.typeName].filter(Boolean).filter((value, index, values) => values.indexOf(value) === index).join(' / ') || '官方未返回类目名称';
    return `<tr><td><b>${escapeHtml(template.templateName || '未命名模板')}</b></td><td>${escapeHtml(template.sourceName || '--')}<div class="ozon-list-meta">Offer: ${escapeHtml(template.sourceOfferId || '--')}</div></td><td class="ozon-mono">${escapeHtml(template.sourceProductId || '--')}</td><td>${escapeHtml(categoryText)}</td><td>${escapeHtml(String(template.attributeCount || 0))} 项</td><td>${escapeHtml(formatOzonDateTime(template.updatedAt) || '--')}</td><td><button class="btn btn-danger btn-sm" type="button" data-ozon-delete-upload-template="${escapeHtml(template.id)}">删除</button></td></tr>`;
  }).join('')}</tbody></table>`;
  list.querySelectorAll('[data-ozon-delete-upload-template]').forEach(button => {
    button.addEventListener('click', async () => {
      const templateId = String(button.dataset.ozonDeleteUploadTemplate || '');
      if (!templateId || !window.confirm('确定删除这个上架模板吗？不会删除 OZON 已上架商品。')) return;
      button.disabled = true;
      try {
        const result = await ozonRequest(`/upload-templates/${encodeURIComponent(templateId)}`, { method: 'DELETE' });
        await loadOzonUploadTemplates({ forceRefresh: true });
        setOzonResult(result.message || '模板已删除', 'success');
      } catch (err) {
        setOzonResult(err.message || '删除模板失败', 'error');
      } finally {
        button.disabled = false;
      }
    });
  });
}

async function loadOzonUploadTemplates(options = {}) {
  const accountId = getOzonUploadAccountId();
  if (!accountId) {
    ozonState.uploadTemplates = [];
    renderOzonUploadTemplates();
    return;
  }
  const result = await ozonRequest(`/upload-templates?ozonAccountId=${encodeURIComponent(accountId)}`, { ...options, cache: false });
  ozonState.uploadTemplates = Array.isArray(result.templates) ? result.templates : [];
  renderOzonUploadTemplates();
}

function normalizeOzonUploadHeader(value = '') {
  return String(value || '').trim().replace(/\s+/g, '').toLowerCase();
}

function getOzonUploadCell(row = {}, names = []) {
  const keys = Object.keys(row || {});
  const match = keys.find(key => names.includes(normalizeOzonUploadHeader(key)));
  return match ? String(row[match] ?? '').trim() : '';
}

function getOzonUploadFileItemFile(item) {
  if (item?.file && typeof item.file.arrayBuffer === 'function') return item.file;
  return item;
}

function getOzonUploadFileItemPath(item) {
  const file = getOzonUploadFileItemFile(item);
  return String(item?.relativePath || file?.webkitRelativePath || file?.name || '');
}

function getOzonUploadImagesByNumber(files = []) {
  const groups = new Map();
  Array.from(files || []).forEach(item => {
    const file = getOzonUploadFileItemFile(item);
    const parts = getOzonUploadFileItemPath(item).split('/').filter(Boolean);
    if (parts.length < 2) return;
    const number = String(parts[parts.length - 2] || '').trim();
    const filename = parts[parts.length - 1] || '';
    const match = filename.match(/^(\d+)\.(jpg|jpeg|png)$/i);
    if (!number || !match) return;
    if (!groups.has(number)) groups.set(number, []);
    groups.get(number).push({ file, order: Number(match[1]), name: filename });
  });
  groups.forEach(images => images.sort((a, b) => a.order - b.order || a.name.localeCompare(b.name)));
  return groups;
}

function createOzonUploadFileItem(file, relativePath = '') {
  return { file, relativePath: String(relativePath || file?.webkitRelativePath || file?.name || '') };
}

function readOzonDroppedEntry(entry, prefix = '') {
  return new Promise((resolve) => {
    if (!entry) {
      resolve([]);
      return;
    }
    const entryPath = `${prefix}${entry.name || ''}`;
    if (entry.isFile) {
      entry.file(
        file => resolve([createOzonUploadFileItem(file, entryPath)]),
        () => resolve([])
      );
      return;
    }
    if (!entry.isDirectory) {
      resolve([]);
      return;
    }
    const reader = entry.createReader();
    const children = [];
    const readBatch = () => {
      reader.readEntries(async (entries) => {
        if (!entries.length) {
          const nested = await Promise.all(children.map(child => readOzonDroppedEntry(child, `${entryPath}/`)));
          resolve(nested.flat());
          return;
        }
        children.push(...entries);
        readBatch();
      }, () => resolve([]));
    };
    readBatch();
  });
}

async function getOzonDroppedFileItems(dataTransfer) {
  const items = Array.from(dataTransfer?.items || []);
  const entries = items
    .map(item => typeof item.webkitGetAsEntry === 'function' ? item.webkitGetAsEntry() : null)
    .filter(Boolean);
  if (entries.length) {
    const files = await Promise.all(entries.map(entry => readOzonDroppedEntry(entry)));
    return files.flat();
  }
  return Array.from(dataTransfer?.files || []).map(file => createOzonUploadFileItem(file));
}

function getOzonZipEntryName(bytes, start, length, useUtf8) {
  const raw = bytes.slice(start, start + length);
  try {
    return new TextDecoder(useUtf8 ? 'utf-8' : undefined).decode(raw).replace(/\\/g, '/');
  } catch (error) {
    return new TextDecoder().decode(raw).replace(/\\/g, '/');
  }
}

function findOzonZipEndOffset(bytes) {
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
  const minOffset = Math.max(0, bytes.length - 66000);
  for (let offset = bytes.length - 22; offset >= minOffset; offset -= 1) {
    if (view.getUint32(offset, true) === 0x06054b50) return offset;
  }
  return -1;
}

async function inflateOzonZipData(data) {
  if (typeof DecompressionStream !== 'function') {
    throw new Error('当前浏览器不支持解压 ZIP，请使用最新版 Chrome，或直接拖拽图片文件夹');
  }
  const stream = new Blob([data]).stream().pipeThrough(new DecompressionStream('deflate-raw'));
  return new Uint8Array(await new Response(stream).arrayBuffer());
}

function createOzonUploadBlobFile(bytes, name, type = '') {
  try {
    return new File([bytes], name, { type });
  } catch (error) {
    const blob = new Blob([bytes], { type });
    try {
      Object.defineProperty(blob, 'name', { value: name });
    } catch (defineError) {
      blob.name = name;
    }
    return blob;
  }
}

async function getOzonZipFileItems(zipFile) {
  const bytes = new Uint8Array(await zipFile.arrayBuffer());
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
  const endOffset = findOzonZipEndOffset(bytes);
  if (endOffset < 0) throw new Error('图片 ZIP 文件结构不正确，未找到 ZIP 结束标记');
  const totalEntries = view.getUint16(endOffset + 10, true);
  let offset = view.getUint32(endOffset + 16, true);
  const files = [];
  for (let index = 0; index < totalEntries; index += 1) {
    if (view.getUint32(offset, true) !== 0x02014b50) throw new Error('图片 ZIP 中央目录不正确');
    const flags = view.getUint16(offset + 8, true);
    const method = view.getUint16(offset + 10, true);
    const compressedSize = view.getUint32(offset + 20, true);
    const nameLength = view.getUint16(offset + 28, true);
    const extraLength = view.getUint16(offset + 30, true);
    const commentLength = view.getUint16(offset + 32, true);
    const localOffset = view.getUint32(offset + 42, true);
    const relativePath = getOzonZipEntryName(bytes, offset + 46, nameLength, Boolean(flags & 0x0800));
    offset += 46 + nameLength + extraLength + commentLength;
    if (!relativePath || relativePath.endsWith('/') || relativePath.includes('__MACOSX/')) continue;
    if (!/\.(jpe?g|png)$/i.test(relativePath)) continue;
    if (view.getUint32(localOffset, true) !== 0x04034b50) throw new Error(`图片 ZIP 条目损坏：${relativePath}`);
    const localNameLength = view.getUint16(localOffset + 26, true);
    const localExtraLength = view.getUint16(localOffset + 28, true);
    const dataStart = localOffset + 30 + localNameLength + localExtraLength;
    const compressed = bytes.slice(dataStart, dataStart + compressedSize);
    let content;
    if (method === 0) {
      content = compressed;
    } else if (method === 8) {
      content = await inflateOzonZipData(compressed);
    } else {
      throw new Error(`图片 ZIP 使用了暂不支持的压缩方式：${relativePath}`);
    }
    const fileName = relativePath.split('/').pop() || 'image.jpg';
    const mimeType = /\.png$/i.test(fileName) ? 'image/png' : 'image/jpeg';
    files.push(createOzonUploadFileItem(createOzonUploadBlobFile(content, fileName, mimeType), relativePath));
  }
  return files;
}

async function downloadOzonUploadExcelTemplate(event) {
  if (event?.currentTarget?.tagName === 'A') return;
  event?.preventDefault?.();
  const url = '/ozon/ozon-upload-template.xlsx?v=ozon-upload-test-bulk-actions-20260718-49&download=1';
  try {
    const response = await fetch(url, { credentials: 'same-origin', cache: 'no-store' });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    const blob = await response.blob();
    if (!blob.size) throw new Error('模板文件为空');
    triggerBrowserDownload('OZON批量上架模板.xlsx', blob);
  } catch (error) {
    setOzonUploadResultMessage(`下载 Excel 模板失败：${error?.message || String(error)}`, 'error');
  }
}

function triggerOzonUploadFileInput(inputId = '') {
  const input = document.getElementById(inputId);
  if (!input || input.type !== 'file') {
    setOzonResult('没有找到对应的文件选择控件，请刷新页面后重试。', 'error');
    return;
  }
  const labelMap = {
    ozonUploadExcelInput: 'Excel 文件',
    ozonUploadImageZipInput: '图片 ZIP 包',
    ozonUploadImageFolderInput: '图片文件夹'
  };
  console.log('[OZON upload] trigger file input:', inputId);
  setOzonResult(`已触发${labelMap[inputId] || '文件'}选择窗口，如果没有弹出，请点击下方原生“选择文件”按钮或直接拖拽文件到区域内。`, 'info');
  input.click();
}

function bindOzonUploadFileClickDelegate() {
  // 保留函数名兼容旧调用点，但不再用 JS 代点文件框。
  // Chrome 文件选择必须由用户直接点击原生 input 触发。
}

async function precheckOzonUpload() {
  const templateId = document.getElementById('ozonUploadTemplateSelect')?.value || '';
  const excel = ozonState.uploadExcelFile || document.getElementById('ozonUploadExcelInput')?.files?.[0];
  const imageZip = ozonState.uploadImageZipFile || document.getElementById('ozonUploadImageZipInput')?.files?.[0];
  let imageFiles = (ozonState.uploadImageFiles || []).length ? ozonState.uploadImageFiles : Array.from(document.getElementById('ozonUploadImageFolderInput')?.files || []);
  const submitButton = document.getElementById('ozonSubmitUploadBtn');
  if (submitButton) submitButton.disabled = true;
  clearOzonUploadPreviewUrls();
  setOzonUploadResultMessage('正在读取 Excel 和图片并执行预检...', 'info');
  if (!templateId) throw new Error('请选择一个已保存的 OZON 商品模板');
  if (!excel) throw new Error('请选择 Excel 文件');
  if (imageZip) {
    setOzonUploadResultMessage('正在解压图片 ZIP 并读取编号文件夹...', 'info');
    const zipFiles = await getOzonZipFileItems(imageZip);
    if (!zipFiles.length) throw new Error('图片 ZIP 内没有找到 jpg / png 图片，请保持 编号/1.jpg 的目录结构');
    imageFiles = zipFiles.concat(imageFiles);
  }
  if (!imageFiles.length) throw new Error('请选择图片 ZIP 包，或拖拽/选择包含编号子文件夹的图片总文件夹');
  const XLSX = await ensureXlsxLoaded();
  const workbook = XLSX.read(await excel.arrayBuffer(), { type: 'array' });
  const sheet = workbook.Sheets[workbook.SheetNames[0]];
  if (!sheet) throw new Error('Excel 没有可读取的工作表');
  const data = XLSX.utils.sheet_to_json(sheet, { defval: '' });
  if (!data.length) throw new Error('Excel 没有数据行');
  const imagesByNumber = getOzonUploadImagesByNumber(imageFiles);
  const seenNumbers = new Set();
  const seenOffers = new Set();
  const officialCurrencies = new Set(['RUB', 'BYN', 'KZT', 'EUR', 'USD', 'CNY']);
  const account = getOzonAccountById(getOzonUploadAccountId()) || {};
  const accountCurrencyCode = String(account.currencyCode || account.currency_code || '').trim().toUpperCase();
  const rows = data.map((raw, index) => {
    const number = getOzonUploadCell(raw, ['编号', '链接序号', '序号', 'number', 'linkno']);
    const title = getOzonUploadCell(raw, ['标题', '商品标题', 'title', 'name']);
    const price = getOzonUploadCell(raw, ['价格', '售价', 'price']);
    const enteredCurrencyCode = getOzonUploadCell(raw, ['币种', 'currency', 'currencycode', 'currency_code']).toUpperCase();
    const currencyCode = enteredCurrencyCode || accountCurrencyCode;
    const offerId = getOzonUploadCell(raw, ['货号', 'offerid', 'offer_id']);
    const mergeModel = getOzonUploadCell(raw, ['型号名称（合并卡片）', '型号名称', '合并型号', '合并模型', 'mergemodel', 'merge_model', 'modelname', 'model_name']);
    const barcode = getOzonUploadCell(raw, ['条码', 'barcode']);
    const errors = [];
    if (!number) errors.push('缺少编号');
    else if (seenNumbers.has(number)) errors.push('编号重复');
    seenNumbers.add(number);
    if (!title) errors.push('缺少标题');
    else if (title.length > 500) errors.push('标题超过 OZON 官方上限 500 个字符');
    if (!/^\d+(?:\.\d{1,2})?$/.test(price) || Number(price) <= 0) errors.push('价格必须大于 0，最多两位小数');
    if (!currencyCode) errors.push('Excel 未填写币种，且当前店铺未保存币种');
    else if (!officialCurrencies.has(currencyCode)) errors.push('币种只允许 RUB、BYN、KZT、EUR、USD、CNY');
    else if (enteredCurrencyCode && accountCurrencyCode && enteredCurrencyCode !== accountCurrencyCode) errors.push(`币种必须与当前店铺 ${accountCurrencyCode} 一致`);
    if (!offerId) errors.push('缺少货号');
    else if (offerId.length > 50) errors.push('货号超过 OZON 官方上限 50 个字符');
    else if (seenOffers.has(offerId)) errors.push('货号重复');
    seenOffers.add(offerId);
    const images = imagesByNumber.get(number) || [];
    if (!images.length) errors.push('未找到对应图片文件夹或图片命名不符合 1.jpg、2.png');
    if (images.length > 15) errors.push('图片超过 OZON 官方上限 15 张');
    if (images.length && (images[0].order !== 1 || images.some((image, imageIndex) => image.order !== imageIndex + 1))) errors.push('图片编号必须从 1 连续递增');
    const imagePreviews = images.slice(0, 5).map(image => ({ order: image.order, name: image.name, url: createOzonUploadPreviewUrl(image.file) })).filter(image => image.url);
    return { rowNumber: index + 2, number, title, price, currencyCode, offerId, mergeModel, barcode, images, imagePreviews, imageCount: images.length, errors };
  });
  ozonState.uploadPrecheckRows = rows;
  const target = document.getElementById('ozonUploadPrecheckResult');
  const passed = rows.filter(row => !row.errors.length).length;
  const mergeGroups = new Set(rows.map(row => row.mergeModel).filter(Boolean));
  const mergeSummary = mergeGroups.size
    ? `已填写 ${mergeGroups.size} 个合并组；最终是否合并由 OZON 根据可变属性校验。`
    : '型号名称均为空，本批次商品将按各自货号保持独立。';
  if (target) target.innerHTML = `<div class="ozon-data-note ozon-upload-summary ${passed === rows.length ? 'is-success' : 'is-error'}">预检结果：${passed}/${rows.length} 条通过。${escapeHtml(mergeSummary)}确认后将上传图片、提交 OZON 创建任务并显示官方逐条结果。</div><table class="ozon-table ozon-upload-precheck-table"><thead><tr><th>数据序号</th><th>编号</th><th>图片预览</th><th>标题</th><th>价格</th><th>币种</th><th>货号</th><th title="对应 OZON 后台：型号名称（针对合并为一张商品卡片）">型号名称<br><small>合并卡片</small></th><th>结果</th></tr></thead><tbody>${rows.map((row, rowIndex) => `<tr><td>${rowIndex + 1}</td><td>${escapeHtml(row.number)}</td><td>${renderOzonUploadPreviewCell(row)}</td><td class="ozon-upload-title-cell">${escapeHtml(row.title)}</td><td>${escapeHtml(row.price)}</td><td>${escapeHtml(row.currencyCode)}</td><td class="ozon-mono">${escapeHtml(row.offerId)}</td><td class="ozon-mono">${escapeHtml(row.mergeModel || '独立')}</td><td>${row.errors.length ? `<span class="ozon-status-pill is-error">${escapeHtml(row.errors.join('；'))}</span>` : '<span class="ozon-status-pill">通过</span>'}</td></tr>`).join('')}</tbody></table>`;
  if (submitButton) submitButton.disabled = passed !== rows.length;
}

function readOzonUploadFileAsDataUrl(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(String(reader.result || ''));
    reader.onerror = () => reject(new Error(`读取图片失败：${file?.name || ''}`));
    reader.readAsDataURL(file);
  });
}

function renderOzonUploadStatus(result = {}) {
  const target = document.getElementById('ozonUploadPrecheckResult');
  if (!target) return;
  const items = Array.isArray(result.items) ? result.items : [];
  target.innerHTML = `<div class="ozon-data-note">OZON 任务 ${escapeHtml(String(result.taskId || '--'))}：${escapeHtml(result.status || '--')}；成功 ${Number(result.imported || 0)}，失败 ${Number(result.failed || 0)}，处理中 ${Number(result.pending || 0)}。</div><table class="ozon-table"><thead><tr><th>数据序号</th><th>编号</th><th>货号</th><th>型号名称<br><small>合并卡片</small></th><th>OZON Product ID</th><th>状态</th><th>官方错误</th></tr></thead><tbody>${items.map((item, itemIndex) => `<tr><td>${itemIndex + 1}</td><td>${escapeHtml(item.sourceNumber || '--')}</td><td class="ozon-mono">${escapeHtml(item.offerId || '--')}</td><td class="ozon-mono">${escapeHtml(item.mergeModel || '独立')}</td><td class="ozon-mono">${escapeHtml(String(item.productId || '--'))}</td><td>${escapeHtml(item.status || '--')}</td><td>${escapeHtml(Array.isArray(item.errors) ? item.errors.map(error => error.message || error.code || JSON.stringify(error)).join('；') : '')}</td></tr>`).join('') || '<tr><td colspan="7">OZON 尚未返回单品状态。</td></tr>'}</tbody></table>`;
}

async function pollOzonUploadBatch(batchId) {
  let latestResult = null;
  for (let attempt = 0; attempt < 20; attempt += 1) {
    const result = await ozonRequest(`/upload-batches/${encodeURIComponent(batchId)}/status`, { cache: false, forceRefresh: true });
    latestResult = result;
    renderOzonUploadStatus(result);
    if (['imported', 'failed'].includes(result.status)) {
      return result;
    }
    await new Promise(resolve => window.setTimeout(resolve, 3000));
  }
  if (latestResult) {
    renderOzonUploadStatus(latestResult);
    const note = document.querySelector('#ozonUploadPrecheckResult .ozon-data-note');
    if (note) note.textContent += '；OZON 仍在处理，请保留该任务号并稍后继续查询。';
  }
  return latestResult;
}

async function submitOzonUpload() {
  const templateId = document.getElementById('ozonUploadTemplateSelect')?.value || '';
  const accountId = getOzonUploadAccountId();
  const rows = (ozonState.uploadPrecheckRows || []).filter(row => !row.errors?.length);
  if (!templateId || !accountId || !rows.length || rows.length !== ozonState.uploadPrecheckRows.length) throw new Error('请先完成全部通过的预检');
  if (!window.confirm(`确定上传 ${rows.length} 条商品到 OZON 吗？图片会先上传到服务器，再调用 OZON 官方创建接口。`)) return;
  const submitButton = document.getElementById('ozonSubmitUploadBtn');
  if (submitButton) submitButton.disabled = true;
  try {
    const submittedRows = [];
    for (let index = 0; index < rows.length; index += 1) {
      const row = rows[index];
      setOzonUploadResultMessage(`正在上传图片：${index + 1}/${rows.length}，${row.offerId}`, 'info');
      const imageUrls = [];
      for (const image of row.images) {
        const dataUrl = await readOzonUploadFileAsDataUrl(image.file);
        const uploaded = await ozonRequest('/upload-images', { method: 'POST', body: JSON.stringify({ ozonAccountId: accountId, files: [{ dataUrl }] }) });
        const url = uploaded.images?.[0]?.url || '';
        if (!url) throw new Error(`货号 ${row.offerId} 图片服务器未返回公开 URL`);
        imageUrls.push(url);
      }
      if (imageUrls.length !== row.images.length) throw new Error(`货号 ${row.offerId} 图片上传数量不完整`);
      submittedRows.push({ ...row, imageUrls });
    }
    setOzonUploadResultMessage('图片已上传，正在提交 OZON 官方创建任务...', 'info');
    const result = await ozonRequest('/upload-batches/submit', { method: 'POST', body: JSON.stringify({ ozonAccountId: accountId, templateId, rows: submittedRows }) });
    setOzonUploadResultMessage(`OZON 已接受创建任务 ${result.taskId}，正在读取真实结果...`, 'info');
    await pollOzonUploadBatch(result.batchId);
    await Promise.allSettled([loadOzonProducts({ forceRefresh: true }), loadOzonUploadTemplates({ forceRefresh: true })]);
  } finally {
    if (submitButton) submitButton.disabled = false;
  }
}

function getOzonTabAccountStateKey(tabId = '') {
  const map = {
    ozonProducts: 'productDiagnosisAccountId',
    ozonProductOnline: 'productAccountId',
    ozonUpload: 'uploadAccountId',
    ozonOrders: 'orderAnalysisAccountId',
    ozonOrderList: 'orderAccountId',
    ozonTraffic: 'trafficAccountId',
    ozonMarketing: 'marketingAccountId',
    ozonFinance: 'financeAccountId',
    ozonMessages: 'messageAccountId'
  };
  return map[tabId] || '';
}

function getOzonTabSelectedAccountId(tabId = '') {
  const stateKey = getOzonTabAccountStateKey(tabId);
  const selectIdMap = {
    ozonProducts: 'ozonProductDiagnosisAccountFilter',
    ozonProductOnline: 'ozonProductAccountFilter',
    ozonOrders: 'ozonOrderAnalysisAccountFilter',
    ozonOrderList: 'ozonOrderAccountFilter',
    ozonTraffic: 'ozonTrafficAccountFilter',
    ozonMarketing: 'ozonMarketingAccountFilter',
    ozonFinance: 'ozonFinanceAccountFilter'
  };
  const select = selectIdMap[tabId] ? document.getElementById(selectIdMap[tabId]) : null;
  const preferred = select ? select.value : (stateKey ? ozonState[stateKey] : '');
  return getOzonScopedAccountId(preferred, { allowAll: tabId === 'ozonOrders' || tabId === 'ozonOrderList' });
}

function getOzonTabSyncLabel(tabId = '') {
  const accountId = getOzonTabSelectedAccountId(tabId);
  if (!accountId && (tabId === 'ozonOrders' || tabId === 'ozonOrderList')) {
    const syncAt = ozonState.overview?.stats?.lastOrderCacheAt || ozonState.overview?.stats?.lastSyncAt || '';
    return `全部店铺 · 最后订单缓存：${syncAt ? formatOzonDateTime(syncAt) : '暂无同步记录'}`;
  }
  const account = getOzonAccountById(accountId);
  const syncAt = account?.lastSyncAt || account?.last_sync_at || '';
  return `最后同步：${syncAt ? formatOzonDateTime(syncAt) : '暂无同步记录'}`;
}

function shouldShowOzonTabSyncStamp(tabId = '') {
  return Boolean(tabId && OZON_TAB_IDS.has(tabId) && !['ozonHome', 'ozonCollection', 'ozonUploadTest', 'ozonMarketing', 'ozonFreight'].includes(tabId));
}

function ensureOzonTabSyncStamp(tabId = '') {
  if (!tabId) return;
  const section = document.getElementById(tabId);
  const panel = section?.querySelector?.('.panel-section') || section?.querySelector?.('.dh-home') || section?.firstElementChild;
  if (!section || !panel) return;
  const legacy = panel.querySelector(':scope > .ozon-tab-sync-stamp');
  if (legacy) legacy.remove();
  if (!shouldShowOzonTabSyncStamp(tabId)) {
    panel.querySelectorAll?.('.ozon-tab-sync-inline')?.forEach(item => item.remove());
    return;
  }
  const authGate = document.getElementById('authGate');
  if (authGate && getComputedStyle(authGate).display !== 'none') {
    panel.querySelectorAll?.('.ozon-tab-sync-inline')?.forEach(item => item.remove());
    return;
  }
  const head = panel.querySelector(':scope > .ozon-section-head');
  if (!head) return;
  head.querySelectorAll(':scope > .ozon-actions > .ozon-tab-sync-inline').forEach(item => item.remove());
  const titleWrap = Array.from(head.children).find(child => !child.classList?.contains('ozon-actions')) || head;
  const title = titleWrap.querySelector(':scope > .section-title');
  let titleLine = titleWrap.querySelector(':scope > .ozon-section-title-line');
  if (title && !titleLine) {
    titleLine = document.createElement('div');
    titleLine.className = 'ozon-section-title-line';
    titleWrap.insertBefore(titleLine, title);
    titleLine.appendChild(title);
  }
  const stampHost = titleLine || titleWrap;
  let stamp = stampHost.querySelector(':scope > .ozon-tab-sync-inline');
  if (!stamp) {
    stamp = document.createElement('span');
    stamp.className = 'ozon-tab-sync-inline';
    stampHost.appendChild(stamp);
  }
  stamp.textContent = getOzonTabSyncLabel(tabId);
}

function updateOzonTabSyncStamps(tabId = '') {
  const ids = tabId ? [tabId] : Array.from(OZON_TAB_IDS);
  ids.forEach(ensureOzonTabSyncStamp);
}

function buildOzonHydrationText(hydration = {}) {
  const attempted = Number(hydration.attempted || 0);
  const saved = Number(hydration.saved || 0);
  const errors = Array.isArray(hydration.errors) ? hydration.errors.length : 0;
  if (!attempted && !saved && !errors) return '商品资料缓存正常';
  return `已自动回查商品详情 ${attempted} 条，补充/更新 ${saved} 条${errors ? `，失败 ${errors} 批` : ''}`;
}

function hasOzonAccounts() {
  return getAuthorizedOzonAccounts().length > 0;
}

function renderOzonNeedAccountEmpty(actionText = '请先在首页添加 OZON 店铺授权。') {
  return `<div class="ozon-empty">${escapeHtml(actionText)}</div>`;
}

function renderOzonErrorEmpty(message = '读取失败') {
  return `<div class="ozon-empty">读取失败：${escapeHtml(message)}</div>`;
}

function renderOzonLoadingEmpty(message = '正在读取数据...') {
  return `<div class="ozon-empty ozon-loading-empty">${escapeHtml(message)}</div>`;
}

function formatOzonBadgeCount(value = 0) {
  const count = Math.max(0, Number(value || 0));
  if (count >= 1000) return '999+';
  if (count > 99) return '99+';
  return String(count);
}

function renderOzonMetaItems(items = []) {
  return items
    .filter(Boolean)
    .map(item => `<span>${item}</span>`)
    .join('');
}

function getOzonTrendLabel(status = '') {
  const labels = { up: '上涨', down: '下滑', flat: '持平' };
  return labels[status] || '持平';
}

function getOzonTrendClass(status = '') {
  if (status === 'up') return 'ozon-trend-up';
  if (status === 'down') return 'ozon-trend-down';
  return 'ozon-trend-flat';
}

function renderOzonAnalysisMetric(label, value) {
  return `<div class="ozon-analysis-metric"><span>${escapeHtml(label)}</span><strong>${escapeHtml(value ?? '--')}</strong></div>`;
}

function isOzonTemporaryAiFailure(message = '') {
  return /failed to fetch|network|timeout|超时|concurrency|并发|rate limit|temporar|稍后|重试/i.test(String(message || ''));
}

function getOzonSalesAdviceKey(item = {}) {
  const parts = [
    item.accountId || item.ozonAccountId || '',
    item.productId || '',
    item.offerId || '',
    item.sku || item.key || '',
    item.todayQty || 0,
    item.monthAvg || 0,
    item.qty7 || 0,
    item.prevQty7 || 0,
    item.trend7Percent || 0,
    item.qty28 || 0,
    item.prevQty28 || 0,
    item.trend28Percent || 0,
    item.share28Percent || 0
  ];
  return parts.map(value => normalizeOzonText(value)).join('|');
}

function getOzonSalesAdviceTargetKey(item = {}) {
  return [
    item.accountId || item.ozonAccountId || '',
    item.productId || '',
    item.offerId || '',
    item.sku || item.key || ''
  ].map(value => normalizeOzonText(value)).join('|');
}

function getOzonSalesAdviceTime(state = {}) {
  return state.aiDiagnosedAt || state.diagnosedAt || state.savedAt || state.updatedAt || '';
}

function renderOzonSalesAdviceTime(state = {}) {
  const time = getOzonSalesAdviceTime(state);
  return time ? `<div class="ozon-list-meta">诊断时间：${escapeHtml(formatOzonDateTime(time))}</div>` : '<div class="ozon-list-meta">未诊断</div>';
}

function normalizeOzonSavedSalesAdviceState(advice = null) {
  if (!advice || typeof advice !== 'object') return null;
  return {
    status: advice.status || 'success',
    advice: advice.advice || '',
    basis: Array.isArray(advice.basis) ? advice.basis : [],
    priority: advice.priority || 'medium',
    model: advice.model || '',
    provider: advice.provider || '',
    error: advice.error || '',
    diagnosedAt: advice.diagnosedAt || advice.aiDiagnosedAt || '',
    aiDiagnosedAt: advice.aiDiagnosedAt || advice.diagnosedAt || '',
    savedAt: advice.savedAt || advice.updatedAt || ''
  };
}

function getOzonSalesAdviceState(item = {}) {
  const key = getOzonSalesAdviceKey(item);
  const state = ozonState.orderAnalysisAiAdvice?.[key];
  if (state) return state;
  const saved = normalizeOzonSavedSalesAdviceState(item.aiAdvice || item.latestAiAdvice?.advice || null);
  if (saved && (saved.advice || saved.error)) {
    ozonState.orderAnalysisAiAdvice[key] = saved;
    return saved;
  }
  return null;
}

function buildOzonSalesAdviceCachePayload(item = {}) {
  const state = getOzonSalesAdviceState(item) || {};
  return {
    ozonAccountId: normalizeOzonText(item.accountId || item.ozonAccountId || ''),
    productId: normalizeOzonText(item.productId || ''),
    offerId: normalizeOzonText(item.offerId || ''),
    sku: normalizeOzonText(item.sku || ''),
    advice: {
      status: state.status || '',
      advice: state.advice || '',
      basis: Array.isArray(state.basis) ? state.basis : [],
      priority: state.priority || 'medium',
      model: state.model || '',
      provider: state.provider || '',
      error: state.error || ''
    }
  };
}

async function saveOzonSalesAdviceCache(item = {}) {
  const payload = buildOzonSalesAdviceCachePayload(item);
  if (!payload.ozonAccountId || (!payload.productId && !payload.offerId && !payload.sku)) {
    throw new Error('缺少店铺或商品标识，无法保存订单分析 AI 诊断');
  }
  const result = await ozonRequest('/order-analysis/ai-advice-cache', {
    method: 'POST',
    cache: false,
    body: JSON.stringify(payload)
  });
  const saved = result.advice || {};
  if (saved.advice) {
    const key = getOzonSalesAdviceKey(item);
    const savedState = normalizeOzonSavedSalesAdviceState(saved.advice);
    if (savedState) {
      ozonState.orderAnalysisAiAdvice[key] = savedState;
      item.aiAdvice = savedState;
      item.latestAiAdvice = saved;
    }
  }
  return saved;
}

function setOzonSalesAdviceUnavailable(items = [], message = '') {
  const reason = normalizeOzonAiUnavailableMessage(message || 'AI不可用：请稍后再试');
  const temporary = isOzonTemporaryAiFailure(reason);
  items.forEach(item => {
    const key = getOzonSalesAdviceKey(item);
    ozonState.orderAnalysisAiAdvice[key] = {
      status: temporary ? 'pending' : 'unavailable',
      error: reason,
      advice: '',
      basis: [],
      priority: 'medium'
    };
  });
}

function resetOzonSalesAiAdvice() {
  ozonState.orderAnalysisAiAdvice = {};
  ozonState.orderAnalysisAiAdviceSeq = (ozonState.orderAnalysisAiAdviceSeq || 0) + 1;
  ozonState.orderAnalysisAiAdviceRunning = false;
  ozonState.orderAnalysisAiAdviceRunningKeys?.clear?.();
}

function buildOzonSalesAdvicePayload(item = {}, trafficRow = null) {
  return {
    key: getOzonSalesAdviceKey(item),
    accountTitle: item.accountTitle || getOzonAccountLabel(item.accountId) || 'OZON',
    name: item.name || item.title || item.offerId || item.sku || 'OZON 商品',
    productId: item.productId || '',
    offerId: item.offerId || '',
    sku: item.sku || '',
    currencyCode: item.currencyCode || 'RUB',
    sales: {
      todayQty: Number(item.todayQty || 0),
      monthAvg: Number(item.monthAvg || 0),
      qty7: Number(item.qty7 || 0),
      prevQty7: Number(item.prevQty7 || 0),
      trend7Percent: Number(item.trend7Percent || 0),
      qty28: Number(item.qty28 || 0),
      prevQty28: Number(item.prevQty28 || 0),
      trend28Percent: Number(item.trend28Percent || 0),
      share28Percent: Number(item.share28Percent || 0),
      alert: Boolean(item.alert)
    },
    traffic: trafficRow ? {
      hasData: true,
      impressions: Number(trafficRow.impressions || 0),
      productViews: Number(trafficRow.productViews || 0),
      cartAdds: Number(trafficRow.cartAdds || 0),
      conversion: Number(trafficRow.conversion || 0)
    } : { hasData: false },
    notes: [
      '订单数、商品件数和趋势均只来自服务器订单列表缓存',
      ozonState.traffic && Array.isArray(ozonState.traffic.products)
        ? '流量数据只作为曝光、浏览、加购参考，不作为销量来源'
        : '当前未读取流量数据'
    ]
  };
}

function renderOzonSalesAiAdvice(item = {}, trafficRow = null, itemIndex = 0) {
  const key = getOzonSalesAdviceKey(item);
  const targetKey = getOzonSalesAdviceTargetKey(item);
  const running = targetKey && ozonState.orderAnalysisAiAdviceRunningKeys?.has?.(targetKey);
  const state = getOzonSalesAdviceState(item);
  const button = `<button class="btn btn-primary btn-sm" type="button" data-ozon-run-sales-ai="${escapeHtml(String(itemIndex))}" ${running ? 'disabled' : ''}>${running ? '诊断中...' : 'AI诊断'}</button>`;
  if (!state) {
    return `<div class="ozon-suggestion-text ozon-ai-pending ozon-advice-card is-empty">
      <div class="ozon-advice-copy">待AI生成：点击本行“AI诊断”后生成整改方案。</div>
      ${renderOzonSalesAdviceTime({})}
      <div class="ozon-actions">${button}</div>
    </div>`;
  }
  if (state.status === 'loading') {
    return `<div class="ozon-suggestion-text ozon-ai-pending ozon-advice-card is-loading">
      <div class="ozon-advice-copy">AI正在生成整改方案...</div>
      ${renderOzonSalesAdviceTime(state)}
    </div>`;
  }
  if (state.status === 'pending') {
    return `<div class="ozon-suggestion-text ozon-ai-pending ozon-advice-card is-empty">
      <div class="ozon-advice-copy">${escapeHtml(state.error || 'AI临时不可用，请稍后重试。')}</div>
      ${renderOzonSalesAdviceTime(state)}
      <div class="ozon-actions">${button}</div>
    </div>`;
  }
  if (state.status === 'unavailable') {
    return `<div class="ozon-suggestion-text ozon-ai-unavailable ozon-advice-card is-empty">
      <div class="ozon-advice-copy">${escapeHtml(state.error || 'AI不可用：请稍后再试')}</div>
      ${renderOzonSalesAdviceTime(state)}
      <div class="ozon-actions">${button}</div>
    </div>`;
  }
  const basis = (Array.isArray(state.basis) ? state.basis : []).filter(Boolean).slice(0, 4);
  const priorityLabel = state.priority === 'high' ? '高优先级' : state.priority === 'low' ? '低优先级' : '中优先级';
  const priorityClass = state.priority === 'high' ? 'is-high' : state.priority === 'low' ? 'is-low' : 'is-medium';
  const advice = normalizeOzonText(state.advice || 'AI未返回整改方案');
  const summary = advice.length > 96 ? `${advice.slice(0, 96)}...` : advice;
  return `<div class="ozon-suggestion-text ozon-ai-sales-advice ozon-advice-card ${priorityClass}">
    <div class="ozon-advice-head">
      <span class="ozon-priority-chip">${escapeHtml(priorityLabel)}</span>
      ${state.model ? `<span class="ozon-ai-model">${escapeHtml(state.model)}</span>` : ''}
    </div>
    <div class="ozon-ai-advice-summary ozon-advice-copy">${escapeHtml(summary)}</div>
    ${basis.length ? `<div class="ozon-ai-basis">${basis.slice(0, 1).map(text => `<span>依据：${escapeHtml(text)}</span>`).join('')}</div>` : ''}
    <div class="ozon-advice-foot">
      ${renderOzonSalesAdviceTime(state)}
      <div class="ozon-actions">
        ${button}
        <button class="btn btn-default btn-sm ozon-ai-full-advice-btn" type="button" data-ozon-sales-advice-key="${escapeHtml(key)}">完整方案</button>
      </div>
    </div>
  </div>`;
}

function buildOzonTrafficLookup() {
  const products = Array.isArray(ozonState.traffic?.products) ? ozonState.traffic.products : [];
  const map = new Map();
  const add = (accountId, key, row) => {
    const k = normalizeOzonText(key);
    if (!k) return;
    const scoped = `${normalizeOzonText(accountId || '')}|${k}`;
    map.set(scoped, row);
    if (!map.has(`|${k}`)) map.set(`|${k}`, row);
  };
  products.forEach(row => {
    add(row.accountId, row.sku, row);
    add(row.accountId, row.offerId, row);
    add(row.accountId, row.productId, row);
  });
  return map;
}

function getOzonTrafficForSalesItem(item = {}, lookup = null) {
  const map = lookup || buildOzonTrafficLookup();
  const accountId = normalizeOzonText(item.accountId || item.ozonAccountId || '');
  const keys = [item.sku, item.offerId, item.productId, item.key].map(normalizeOzonText).filter(Boolean);
  for (const key of keys) {
    const scoped = `${accountId}|${key}`;
    if (map.has(scoped)) return map.get(scoped);
    const unscoped = `|${key}`;
    if (map.has(unscoped)) return map.get(unscoped);
  }
  return null;
}

function renderOzonTrafficInsight(row = null) {
  if (!ozonState.traffic || !Array.isArray(ozonState.traffic.products)) {
    return '<div class="ozon-suggestion-text">未读取流量。到广告分析刷新流量后，可查看曝光、浏览、加购和转化。</div>';
  }
  if (!row) {
    return '<div class="ozon-suggestion-text">未匹配到流量数据。请确认 SKU/Offer 是否进入 Seller API 流量报表。</div>';
  }
  const views = Number(row.productViews || 0);
  const cartAdds = Number(row.cartAdds || 0);
  const orderedUnits = Number(row.orderedUnits || 0);
  const conversion = Number(row.conversion || 0);
  const tone = views > 0 && conversion >= 0.03 ? 'ozon-trend-up' : views > 0 && conversion > 0 ? 'ozon-trend-flat' : 'ozon-trend-down';
  const suggestion = views <= 0
    ? '浏览为 0，优先检查标题关键词、主图和广告/活动曝光。'
    : cartAdds > 0 && orderedUnits <= 0
      ? '有加购但成交弱，优先检查价格、物流时效、评价和活动门槛。'
      : conversion < 0.01
        ? '有浏览但转化偏低，优先优化价格竞争力、首图卖点和详情页承诺。'
        : '流量转化相对可用，继续观察关键词和库存稳定性。';
  return `<div class="ozon-suggestion-text">
    曝光 ${escapeHtml(Number(row.impressions || 0).toLocaleString())} · 浏览 ${escapeHtml(views.toLocaleString())} · 加购 ${escapeHtml(cartAdds.toLocaleString())}<br>
    转化率 <span class="${tone}">${escapeHtml((conversion * 100).toFixed(2))}%</span> · 流量成交参考 ${escapeHtml(orderedUnits.toLocaleString())}<br>
    ${escapeHtml(suggestion)}
  </div>`;
}

function getOzonNoteTargetFromSalesItem(item = {}) {
  return {
    accountId: normalizeOzonText(item.accountId || item.ozonAccountId || ''),
    productId: normalizeOzonText(item.productId || ''),
    offerId: normalizeOzonText(item.offerId || ''),
    sku: normalizeOzonText(item.sku || item.key || ''),
    title: normalizeOzonText(item.name || item.title || item.offerId || item.sku || 'OZON 商品')
  };
}

function getOzonNoteTargetFromDiagnosisItem(item = {}) {
  const ui = normalizeOzonProductForUi(item.product || {});
  return {
    accountId: normalizeOzonText(item.product?.ozonAccountId || item.product?.ozon_account_id || ui.accountId || item.sales?.accountId || ''),
    productId: normalizeOzonText(ui.productId || ''),
    offerId: normalizeOzonText(ui.offerId || ''),
    sku: normalizeOzonText(ui.sku || ''),
    title: normalizeOzonText(ui.title || ui.offerId || ui.sku || 'OZON 商品')
  };
}

function getOzonDiagnosisTargetKey(item = {}) {
  const target = getOzonNoteTargetFromDiagnosisItem(item);
  return [
    target.accountId || '',
    target.productId || '',
    target.offerId || '',
    target.sku || ''
  ].join('|');
}

function getOzonAiDiagnosisTime(diag = {}) {
  return diag.aiDiagnosedAt || diag.diagnosedAt || diag.aiDiagnosisSavedAt || diag.updatedAt || '';
}

function renderOzonAiDiagnosisTime(diag = {}) {
  const time = getOzonAiDiagnosisTime(diag);
  return time ? `<div class="ozon-list-meta">诊断时间：${escapeHtml(formatOzonDateTime(time))}</div>` : '<div class="ozon-list-meta">未诊断</div>';
}

function buildOzonProductAiDiagnosisCachePayload(item = {}) {
  const target = getOzonNoteTargetFromDiagnosisItem(item);
  const diag = item.diagnosis || {};
  return {
    ozonAccountId: target.accountId,
    productId: target.productId,
    offerId: target.offerId,
    sku: target.sku,
    diagnosis: {
      aiDiagnosisStatus: diag.aiDiagnosisStatus || '',
      aiProblems: Array.isArray(diag.aiProblems) ? diag.aiProblems : [],
      aiSuggestions: Array.isArray(diag.aiSuggestions) ? diag.aiSuggestions : [],
      aiBasis: Array.isArray(diag.aiBasis) ? diag.aiBasis : [],
      aiDiagnosisError: diag.aiDiagnosisError || '',
      aiTitleStatus: diag.aiTitleStatus || '',
      aiOptimizedTitle: diag.aiOptimizedTitle || '',
      aiTitleBasis: diag.aiTitleBasis || '',
      aiKeywordBasis: diag.aiKeywordBasis || '',
      aiTitleError: diag.aiTitleError || '',
      aiModel: diag.aiModel || '',
      aiProvider: diag.aiProvider || ''
    }
  };
}

async function saveOzonProductAiDiagnosisCache(item = {}) {
  const payload = buildOzonProductAiDiagnosisCachePayload(item);
  if (!payload.ozonAccountId || (!payload.productId && !payload.offerId && !payload.sku)) {
    throw new Error('缺少店铺或商品标识，无法保存 AI 诊断');
  }
  const result = await ozonRequest('/product-diagnosis/ai-cache', {
    method: 'POST',
    cache: false,
    body: JSON.stringify(payload)
  });
  const saved = result.diagnosis || {};
  if (saved.diagnosis) {
    item.diagnosis = {
      ...(item.diagnosis || {}),
      ...saved.diagnosis
    };
    item.latestAiDiagnosis = saved;
  }
  return saved;
}

function buildOzonNoteTargetAttrs(target = {}) {
  return [
    `data-ozon-note-account="${escapeHtml(target.accountId || '')}"`,
    `data-ozon-note-product="${escapeHtml(target.productId || '')}"`,
    `data-ozon-note-offer="${escapeHtml(target.offerId || '')}"`,
    `data-ozon-note-sku="${escapeHtml(target.sku || '')}"`,
    `data-ozon-note-title="${escapeHtml(target.title || '')}"`
  ].join(' ');
}

function getOzonNoteTargetFromElement(el) {
  const source = el?.dataset || {};
  return {
    accountId: normalizeOzonText(source.ozonNoteAccount || ''),
    productId: normalizeOzonText(source.ozonNoteProduct || ''),
    offerId: normalizeOzonText(source.ozonNoteOffer || ''),
    sku: normalizeOzonText(source.ozonNoteSku || ''),
    title: normalizeOzonText(source.ozonNoteTitle || 'OZON 商品')
  };
}

function renderOzonLatestNote(note = null) {
  if (!note || !note.text) return '<div class="ozon-note-empty ozon-record-preview is-empty"><span>暂无整改记录</span></div>';
  return `<div class="ozon-note-summary ozon-record-preview">
    <time>${escapeHtml(formatOzonDateTime(note.createdAt) || '--')}</time>
    <span>${escapeHtml(note.text)}</span>
  </div>`;
}

function renderOzonNoteButton(target = {}, label = '备注') {
  return `<button class="btn btn-default btn-sm" type="button" data-ozon-note-open ${buildOzonNoteTargetAttrs(target)}>${escapeHtml(label)}</button>`;
}

function bindOzonNoteButtons(root) {
  if (!root) return;
  root.querySelectorAll('[data-ozon-note-open]').forEach(button => {
    button.addEventListener('click', event => {
      event.preventDefault();
      event.stopPropagation();
      openOzonNoteModal(getOzonNoteTargetFromElement(button));
    });
  });
}

async function loadOzonProductNotes(target = {}) {
  const params = new URLSearchParams();
  if (target.accountId) params.set('ozonAccountId', target.accountId);
  if (target.productId) params.set('productId', target.productId);
  if (target.offerId) params.set('offerId', target.offerId);
  if (target.sku) params.set('sku', target.sku);
  const query = params.toString();
  return ozonRequest(`/product-notes${query ? `?${query}` : ''}`, { cache: false });
}

function applyOzonSavedNoteToState(target = {}, note = null) {
  if (!note) return;
  const sameTarget = (itemTarget = {}) => {
    const accountMatches = !target.accountId || !itemTarget.accountId || String(target.accountId) === String(itemTarget.accountId);
    const identityMatches = Boolean(
      (target.productId && itemTarget.productId && String(target.productId) === String(itemTarget.productId)) ||
      (target.offerId && itemTarget.offerId && String(target.offerId) === String(itemTarget.offerId)) ||
      (target.sku && itemTarget.sku && String(target.sku) === String(itemTarget.sku))
    );
    return accountMatches && identityMatches;
  };
  if (Array.isArray(ozonState.orderAnalysis?.items)) {
    ozonState.orderAnalysis.items.forEach(item => {
      if (sameTarget(getOzonNoteTargetFromSalesItem(item))) item.latestNote = note;
    });
  }
  if (Array.isArray(ozonState.productDiagnosis?.items)) {
    ozonState.productDiagnosis.items.forEach(item => {
      if (sameTarget(getOzonNoteTargetFromDiagnosisItem(item))) item.latestNote = note;
    });
  }
}

function ensureOzonNoteModal() {
  let modal = document.getElementById('ozonNoteModal');
  if (modal) return modal;
  modal = document.createElement('div');
  modal.id = 'ozonNoteModal';
  modal.className = 'ozon-modal';
  modal.setAttribute('aria-hidden', 'true');
  modal.innerHTML = `<div style="width:min(720px,96vw);max-height:86vh;overflow:auto;background:#fff;border-radius:12px;box-shadow:0 24px 70px rgba(15,23,42,.28);">
    <div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--border);gap:12px;">
      <h3 style="margin:0;font-size:16px;color:var(--text-primary);">整改记录</h3>
      <button class="btn btn-default btn-sm" type="button" data-ozon-note-close>关闭</button>
    </div>
    <div id="ozonNoteModalBody" style="padding:16px;"></div>
  </div>`;
  modal.querySelector('[data-ozon-note-close]')?.addEventListener('click', () => setOzonOrderDetailModalVisible(modal, false));
  modal.addEventListener('click', event => {
    if (event.target === modal) setOzonOrderDetailModalVisible(modal, false);
  });
  document.body.appendChild(modal);
  return modal;
}

async function openOzonNoteModal(target = {}) {
  const modal = ensureOzonNoteModal();
  const body = document.getElementById('ozonNoteModalBody');
  if (!body) return;
  body.innerHTML = '<div class="ozon-empty">正在读取整改记录...</div>';
  setOzonOrderDetailModalVisible(modal, true);
  try {
    const result = await loadOzonProductNotes(target);
    const notes = Array.isArray(result.notes) ? result.notes : [];
    body.innerHTML = `<div class="ozon-list-title">${escapeHtml(target.title || 'OZON 商品')}</div>
      <div class="ozon-list-meta">Product ID: ${escapeHtml(target.productId || '--')} · Offer: ${escapeHtml(target.offerId || '--')} · SKU: ${escapeHtml(target.sku || '--')}</div>
      <div class="ozon-note-panel">
        <textarea id="ozonNoteInput" placeholder="添加本次整改记录，例如：已更换主图、已调整标题关键词、已补库存、已参加活动..."></textarea>
        <div class="ozon-actions">
          <button class="btn btn-primary btn-sm" id="ozonSaveNoteBtn" type="button">添加记录</button>
          <span class="ozon-list-meta" id="ozonNoteSaveResult"></span>
        </div>
        <div class="ozon-note-history" id="ozonNoteHistory">
          ${notes.length ? notes.map(note => `<div class="ozon-note-row"><time class="ozon-note-time">${escapeHtml(formatOzonDateTime(note.createdAt) || '--')}</time><div class="ozon-note-text">${escapeHtml(note.text || '')}</div></div>`).join('') : '<div class="ozon-note-empty">暂无历史记录</div>'}
        </div>
      </div>`;
    document.getElementById('ozonSaveNoteBtn')?.addEventListener('click', () => saveOzonProductNote(target));
  } catch (err) {
    body.innerHTML = renderOzonErrorEmpty(err.message || '整改记录读取失败');
  }
}

async function saveOzonProductNote(target = {}) {
  const input = document.getElementById('ozonNoteInput');
  const resultEl = document.getElementById('ozonNoteSaveResult');
  const text = normalizeOzonText(input?.value || '');
  if (!text) {
    if (resultEl) resultEl.textContent = '请输入整改记录';
    return;
  }
  try {
    const result = await ozonRequest('/product-notes', {
      method: 'POST',
      body: JSON.stringify({
        ozonAccountId: target.accountId || '',
        productId: target.productId || '',
        offerId: target.offerId || '',
        sku: target.sku || '',
        text
      })
    });
    const note = result.note;
    applyOzonSavedNoteToState(target, note);
    if (input) input.value = '';
    if (resultEl) resultEl.textContent = '已保存';
    await openOzonNoteModal(target);
    renderOzonOrderAnalysis();
    renderOzonProductDiagnosis();
  } catch (err) {
    if (resultEl) resultEl.textContent = err.message || '保存失败';
  }
}

function renderOzonProblemLines(items = [], fallback = '暂无明显问题') {
  const lines = (Array.isArray(items) ? items : [])
    .filter(Boolean)
    .slice(0, 3);
  if (!lines.length) return `<span>${escapeHtml(fallback)}</span>`;
  return lines.map(text => `<span>• ${escapeHtml(text)}</span>`).join('');
}

function renderOzonAnalysisNumber(value = 0) {
  return `<span class="ozon-analysis-number">${escapeHtml(Number(value || 0).toLocaleString())}</span>`;
}

function renderOzonAnalysisPercent(value = 0) {
  const n = Number(value || 0);
  const text = `${n > 0 ? '+' : ''}${n.toFixed(1)}%`;
  const cls = n > 0 ? 'ozon-trend-up' : n < 0 ? 'ozon-trend-down' : 'ozon-trend-flat';
  return `<span class="ozon-trend-pill ${cls}">${escapeHtml(text)}</span>`;
}

function getOzonDiagnosisKey(item = {}, index = 0) {
  const ui = normalizeOzonProductForUi(item.product || {});
  return String(ui.productId || ui.offerId || ui.sku || ui.title || `item-${index + 1}`);
}

function getOzonDiagnosisKeyAliases(item = {}, index = 0) {
  const product = item.product || {};
  const raw = product.rawData || product.raw || {};
  const ui = normalizeOzonProductForUi(product);
  return [
    getOzonDiagnosisKey(item, index),
    ui.productId,
    ui.offerId,
    ui.sku,
    ui.title,
    product.productId,
    product.product_id,
    product.offerId,
    product.offer_id,
    product.sku,
    product.name,
    raw.product_id,
    raw.id,
    raw.offer_id,
    raw.sku,
    raw.name,
    raw.title,
    `item-${index + 1}`
  ].map(value => normalizeOzonText(value)).filter(Boolean);
}

function normalizeOzonAiUnavailableMessage(message = '') {
  const text = String(message || '').trim();
  if (!text) return 'AI不可用：请稍后再试';
  return text.startsWith('AI不可用') ? text : `AI不可用：${text}`;
}

function setOzonDiagnosisAiUnavailable(diag = {}, message = '') {
  const reason = normalizeOzonAiUnavailableMessage(message);
  diag.aiDiagnosisStatus = 'unavailable';
  diag.aiDiagnosisError = reason;
  diag.aiTitleStatus = 'unavailable';
  diag.aiTitleError = reason;
  diag.aiProblems = [];
  diag.aiSuggestions = [];
  diag.aiBasis = [];
  diag.aiOptimizedTitle = '';
  diag.aiTitleBasis = '';
  diag.aiKeywordBasis = '';
  return diag;
}

function getOzonAiDiagnosisState(diag = {}) {
  if (diag.aiDiagnosisStatus === 'loading' || diag.aiTitleStatus === 'loading') {
    return {
      status: 'loading',
      unavailable: false,
      canCopy: false,
      problems: ['AI 正在生成现存问题...'],
      suggestions: ['AI 正在生成整改建议...'],
      basis: ['正在根据 OZON 商品数据、订单趋势和官方商品卡片要求生成诊断。'],
      title: 'AI 正在生成建议标题...',
      titleBasis: '正在根据 OZON 标题规范、订单趋势和关键词热度代理生成标题。'
    };
  }
  if (diag.aiDiagnosisStatus === 'pending' || diag.aiTitleStatus === 'pending') {
    const reason = diag.aiDiagnosisError || diag.aiTitleError || '';
    return {
      status: 'pending',
      unavailable: false,
      canCopy: false,
      problems: [reason ? `AI 临时不可用：${reason.replace(/^AI不可用：/, '')}` : '待AI生成：点击本行“AI诊断”后生成现存问题。'],
      suggestions: [reason ? `AI 临时不可用：${reason.replace(/^AI不可用：/, '')}` : '待AI生成：点击本行“AI诊断”后生成整改建议。'],
      basis: [reason ? `AI 临时不可用：${reason.replace(/^AI不可用：/, '')}` : '待AI生成：等待模型根据 OZON 商品数据、订单趋势和官方商品卡片要求生成诊断。'],
      title: reason ? `AI 临时不可用：${reason.replace(/^AI不可用：/, '')}` : '待AI生成：点击本行“AI诊断”后生成建议标题。',
      titleBasis: reason ? `AI 临时不可用：${reason.replace(/^AI不可用：/, '')}` : '待AI生成：等待模型根据 OZON 标题规范、订单趋势和关键词热度生成标题。'
    };
  }
  const problems = (Array.isArray(diag.aiProblems) ? diag.aiProblems : []).filter(Boolean);
  const suggestions = (Array.isArray(diag.aiSuggestions) ? diag.aiSuggestions : []).filter(Boolean);
  const basis = (Array.isArray(diag.aiBasis) ? diag.aiBasis : []).filter(Boolean);
  const hasAiDiagnosis = diag.aiDiagnosisStatus === 'success' && problems.length && suggestions.length;
  const hasAiTitle = diag.aiTitleStatus === 'success' && Boolean(diag.aiOptimizedTitle);
  if (hasAiDiagnosis && hasAiTitle) {
    return {
      status: 'success',
      unavailable: false,
      canCopy: true,
      problems,
      suggestions,
      basis,
      title: diag.aiOptimizedTitle,
      titleBasis: diag.aiTitleBasis || diag.aiKeywordBasis || basis.join('；') || 'AI 已根据 OZON 官方标题规范、商品数据和订单趋势生成。'
    };
  }
  if (hasAiDiagnosis || hasAiTitle) {
    const missing = hasAiDiagnosis ? 'AI未返回建议标题' : 'AI未返回完整诊断';
    return {
      status: 'partial',
      unavailable: false,
      canCopy: hasAiTitle,
      problems: problems.length ? problems : [missing],
      suggestions: suggestions.length ? suggestions : [missing],
      basis: basis.length ? basis : [missing],
      title: hasAiTitle ? diag.aiOptimizedTitle : missing,
      titleBasis: hasAiTitle ? (diag.aiTitleBasis || diag.aiKeywordBasis || basis.join('；')) : missing
    };
  }
  if (!diag.aiDiagnosisStatus && !diag.aiTitleStatus && !diag.aiDiagnosisError && !diag.aiTitleError) {
    return {
      status: 'pending',
      unavailable: false,
      canCopy: false,
      problems: ['待AI生成：点击本行“AI诊断”后生成现存问题。'],
      suggestions: ['待AI生成：点击本行“AI诊断”后生成整改建议。'],
      basis: ['待AI生成：等待模型根据 OZON 商品数据、订单趋势和官方商品卡片要求生成诊断。'],
      title: '待AI生成：点击本行“AI诊断”后生成建议标题。',
      titleBasis: '待AI生成：等待模型根据 OZON 标题规范、订单趋势和关键词热度代理生成标题。'
    };
  }
  const reason = normalizeOzonAiUnavailableMessage(diag.aiDiagnosisError || diag.aiTitleError || 'AI不可用：请稍后再试');
  return {
    status: 'unavailable',
    unavailable: true,
    canCopy: false,
    problems: [reason],
    suggestions: [reason],
    basis: [reason],
    title: reason,
    titleBasis: reason
  };
}

function getOzonAiTitleState(diag = {}) {
  const state = getOzonAiDiagnosisState(diag);
  return {
    title: state.title,
    basis: state.titleBasis,
    canCopy: state.canCopy,
    unavailable: state.unavailable
  };
}

function isOzonDiagnosisMissingAi(item = {}) {
  const diag = item.diagnosis || {};
  const state = getOzonAiDiagnosisState(diag);
  return state.status === 'pending' || state.status === 'partial';
}

function getOzonProductDiagnosisAiFingerprint(items = []) {
  const accountId = ozonState.productDiagnosisAccountId || ozonState.productAccountId || '';
  const risk = ozonState.productDiagnosisRisk || '';
  const keyword = ozonState.productDiagnosisKeyword || '';
  const parts = (Array.isArray(items) ? items : []).map((item, index) => {
    const ui = normalizeOzonProductForUi(item.product || {});
    const sales = item.sales || {};
    return [
      ui.productId || ui.offerId || ui.sku || `row-${index}`,
      ui.visibility || '',
      sales.todayQty || 0,
      sales.qty7 || 0,
      sales.qty28 || 0,
      sales.trend7Percent || 0,
      sales.trend28Percent || 0
    ].map(value => normalizeOzonText(value)).join(':');
  });
  return [accountId, risk, keyword, parts.length, parts.join('|')].map(value => normalizeOzonText(value)).join('::');
}

function buildOzonTitleSuggestionPayload(items = []) {
  return items.slice(0, OZON_AI_DIAGNOSIS_MAX_ITEMS).map((item, index) => {
    const ui = normalizeOzonProductForUi(item.product || {});
    const raw = item.product?.rawData || item.product?.raw || {};
    const diag = item.diagnosis || {};
    const sales = item.sales || {};
    const attributes = [];
    const rawAttributes = raw.attributes || raw.attribute_values || raw.attributes_values || raw.characteristics || [];
    if (Array.isArray(rawAttributes)) {
      rawAttributes.slice(0, 12).forEach(attr => {
        if (typeof attr === 'string') attributes.push(attr);
        else {
          const name = attr?.name || attr?.attribute_name || attr?.attributeName || attr?.id || '';
          const value = attr?.value || attr?.values?.[0]?.value || attr?.values?.[0] || attr?.text || '';
          const text = [name, value].filter(Boolean).join(': ');
          if (text) attributes.push(text);
        }
      });
    }
    return {
      key: getOzonDiagnosisKey(item, index),
      title: ui.title,
      category: ui.category,
      productId: ui.productId,
      offerId: ui.offerId,
      sku: ui.sku,
      status: ui.statusLabel,
      attributes,
      sales: {
        todayQty: sales.todayQty || 0,
        qty7: sales.qty7 || 0,
        prevQty7: sales.prevQty7 || 0,
        qty28: sales.qty28 || 0,
        prevQty28: sales.prevQty28 || 0,
        trend7Percent: sales.trend7Percent || 0,
        trend28Percent: sales.trend28Percent || 0,
        share28Percent: sales.share28Percent || 0
      },
      quality: {
        score: diag.score || 0,
        riskLevel: diag.riskLevel || '',
        imageCount: diag.imageCount || 0,
        attributeCount: diag.attributeCount || 0,
        stock: diag.stock ?? ui.stock?.available ?? '',
        priceIndex: diag.priceIndex?.label || ui.priceIndex?.label || ''
      },
      problems: (diag.problems || []).slice(0, 5),
      suggestions: (diag.suggestions || []).slice(0, 5)
    };
  });
}

function renderOzonOrderAnalysis() {
  const wrap = document.getElementById('ozonOrderAnalysisList');
  const pagination = document.getElementById('ozonOrderAnalysisPagination');
  const metaEl = document.getElementById('ozonOrderAnalysisMeta');
  renderOzonRollingSalesWindowHint();
  if (metaEl) {
    metaEl.innerHTML = '';
    metaEl.style.display = 'none';
  }
  const analysis = ozonState.orderAnalysis || {};
  const summary = analysis.accountSalesAnalysis?.summary || analysis.summary || {};
  const todayOrders = Number(summary.totalTodayOrders ?? summary.todayOrders ?? summary.todayOrderCount ?? summary.totalTodayQty ?? summary.todayQty ?? 0);
  setOzonText('ozonAnalysisTodayQty', String(todayOrders || 0));
  setOzonText('ozonAnalysisValidOrders', String(summary.validOrderCount || 0));
  setOzonText('ozonAnalysisQty28', String(summary.totalOrderCount28 || summary.orderCount28 || summary.qty28 || summary.totalQty28 || 0));
  setOzonText('ozonAnalysisTrend28', `${Number((summary.orderTrend28Percent ?? summary.trend28Percent) || 0).toFixed(1)}%`);
  setOzonText('ozonAnalysisAmount28', formatOzonCnyAmount(summary.totalAmount28 || 0));
  setOzonText('ozonAnalysisWarningCount', String(summary.warningCount || 0));
  setOzonText('ozonAnalysisMultiOrders', String(summary.activeAccountCount || summary.accountCount || 0));
  if (!wrap) return;
  if (ozonState.orderAnalysisLoading && !Array.isArray(analysis.items)) {
    wrap.innerHTML = renderOzonLoadingEmpty('正在读取订单分析...');
    if (pagination) pagination.innerHTML = '';
    return;
  }
  if (!hasOzonAccounts()) {
    wrap.innerHTML = renderOzonNeedAccountEmpty('还没有绑定 OZON 店铺。添加授权并开启通知后，这里会按服务器订单缓存展示今日订单数、近7日、近28日和环比。');
    if (pagination) pagination.innerHTML = '';
    return;
  }
  const productRows = Array.isArray(analysis.items) ? analysis.items : [];
  const viewMode = document.getElementById('ozonOrderAnalysisViewFilter')?.value || ozonState.orderAnalysisView || 'all';
  ozonState.orderAnalysisView = viewMode;
  const historicalItems = productRows.filter(item => Number(item.orderCount28 || item.prevOrderCount28 || item.orderCount7 || item.prevOrderCount7 || item.todayOrders || item.monthOrders || item.qty28 || item.qty7 || item.todayQty || 0) > 0);
  const items = historicalItems.filter(item => {
    if (viewMode === 'today') return Number((item.todayOrders ?? item.todayQty) || 0) > 0;
    if (viewMode === 'warning') return Boolean(item.alert) || Number((item.orderTrend7Percent ?? item.trend7Percent) || 0) <= -20 || Number((item.orderTrend28Percent ?? item.trend28Percent) || 0) < -5;
    if (viewMode === 'active28') return Number((item.orderCount28 ?? item.qty28) || 0) > 0;
    return true;
  });
  const historicalTotal = Number(summary.historicalProductCount || historicalItems.length || summary.activeProductCount || ozonState.orderAnalysisTotal || 0);
  const total = items.length;
  const page = Math.max(1, Number(ozonState.orderAnalysisPage || 1) || 1);
  const limit = Math.max(1, Number(ozonState.orderAnalysisLimit || 50) || 50);
  const maxPage = Math.max(1, Math.ceil(total / limit));
  const safePage = Math.min(page, maxPage);
  if (safePage !== page) ozonState.orderAnalysisPage = safePage;
  const start = (safePage - 1) * limit;
  const visibleItems = items.slice(start, start + limit);
  const trafficLookup = buildOzonTrafficLookup();
  if (!items.length) {
    wrap.innerHTML = '<div class="ozon-empty">暂无商品链接订单分析数据。请确认 OZON 通知已写入服务器订单缓存；如果订单列表有数据，请检查当前店铺筛选、SKU筛选和订单时间。</div>';
    if (pagination) pagination.innerHTML = '';
    return;
  }
  const rowsHtml = visibleItems.map((item, index) => {
    const image = renderOzonImageThumb(item.image || '', 'ozon-order-product-thumb');
    const trafficRow = getOzonTrafficForSalesItem(item, trafficLookup);
    const noteTarget = getOzonNoteTargetFromSalesItem(item);
    const storeLabel = item.accountTitle || getOzonAccountLabel(item.accountId) || 'OZON';
    return `<tr class="${item.alert ? 'is-warning' : ''}">
      <td><div class="ozon-product-cell ozon-sales-product-cell">${image}</div></td>
      <td><button class="btn btn-default btn-sm ozon-sales-detail-btn" type="button" data-ozon-sales-detail="${escapeHtml(String(index))}">查看详情</button></td>
      <td><div class="ozon-list-meta">${escapeHtml(storeLabel)}</div></td>
      <td>${renderOzonAnalysisNumber((item.todayOrders ?? item.todayQty) || 0)}</td>
      <td>${renderOzonAnalysisNumber((item.monthOrderAvg ?? item.monthAvg) || 0)}</td>
      <td>${renderOzonAnalysisNumber((item.orderCount7 ?? item.qty7) || 0)}</td>
      <td>${renderOzonAnalysisNumber((item.prevOrderCount7 ?? item.prevQty7) || 0)}</td>
      <td>${renderOzonAnalysisPercent((item.orderTrend7Percent ?? item.trend7Percent) || 0)}</td>
      <td>${renderOzonAnalysisNumber((item.orderCount28 ?? item.qty28) || 0)}</td>
      <td>${renderOzonAnalysisNumber((item.prevOrderCount28 ?? item.prevQty28) || 0)}</td>
      <td>${renderOzonAnalysisPercent((item.orderTrend28Percent ?? item.trend28Percent) || 0)}</td>
      <td><span class="ozon-analysis-share">${escapeHtml(Number(item.share28Percent || 0).toFixed(2))}%</span></td>
      <td class="ozon-order-analysis-traffic-cell">${renderOzonTrafficInsight(trafficRow)}</td>
      <td class="ozon-order-analysis-advice-cell">${renderOzonSalesAiAdvice(item, trafficRow, index)}</td>
      <td class="ozon-order-analysis-record-cell">${renderOzonLatestNote(item.latestNote)}<div class="ozon-actions">${renderOzonNoteButton(noteTarget, '查看记录')}</div></td>
    </tr>`;
  }).join('');
  const totalRows = total || items.length;
  wrap.innerHTML = `<table class="ozon-table ozon-analysis-table">
    ${renderOzonColumnGroup(OZON_SALES_ANALYSIS_COLUMN_DEFAULTS, 'salesAnalysisRecords')}
    <thead>
      <tr>
        <th>商品链接</th>
        <th>详情</th>
        <th>店铺</th>
        <th>今日订单数</th>
        <th>当月<br>日均订单</th>
        <th>近7日订单</th>
        <th>上个7日订单</th>
        <th>7日环比</th>
        <th>近28日<br>订单</th>
        <th>上个28日<br>订单</th>
        <th>28日环比</th>
        <th>订单占比</th>
        <th>流量诊断</th>
        <th>建议整改方案</th>
        <th>整改记录</th>
      </tr>
    </thead>
    <tbody>${rowsHtml || '<tr><td colspan="15">暂无数据</td></tr>'}</tbody>
  </table>`;
  setupOzonResizableTable(wrap.querySelector('.ozon-analysis-table'), 'salesAnalysisRecords', OZON_SALES_ANALYSIS_COLUMN_DEFAULTS, OZON_SALES_ANALYSIS_COLUMN_MINS);
  wrap.querySelectorAll('[data-ozon-sales-detail]').forEach(button => {
    button.addEventListener('click', () => openOzonSalesProductDetail(visibleItems[Number(button.dataset.ozonSalesDetail || 0)] || {}));
  });
  wrap.querySelectorAll('[data-ozon-sales-advice-key]').forEach(button => {
    button.addEventListener('click', () => openOzonSalesAdviceModal(button.dataset.ozonSalesAdviceKey || ''));
  });
  wrap.querySelectorAll('[data-ozon-run-sales-ai]').forEach(button => {
    button.addEventListener('click', () => runOzonSingleOrderAiAdvice(visibleItems[Number(button.dataset.ozonRunSalesAi || 0)] || null));
  });
  if (pagination) {
    pagination.innerHTML = `
      <button type="button" id="ozonOrderAnalysisPrevPageBtn"${safePage <= 1 ? ' disabled' : ''}>上一页</button>
      <span id="ozonOrderAnalysisPageInfo">第 ${safePage} 页 / 共 ${maxPage} 页 · ${totalRows} 条</span>
      <button type="button" id="ozonOrderAnalysisNextPageBtn"${safePage >= maxPage ? ' disabled' : ''}>下一页</button>
    `;
  }
  pagination?.querySelector('#ozonOrderAnalysisPrevPageBtn')?.addEventListener('click', () => {
    ozonState.orderAnalysisPage = Math.max(1, (ozonState.orderAnalysisPage || 1) - 1);
    renderOzonOrderAnalysis();
  });
  pagination?.querySelector('#ozonOrderAnalysisNextPageBtn')?.addEventListener('click', () => {
    ozonState.orderAnalysisPage = Math.min(maxPage, (ozonState.orderAnalysisPage || 1) + 1);
    renderOzonOrderAnalysis();
  });
  bindOzonNoteButtons(wrap);
}

function getFilteredOzonDiagnosisItems() {
  const data = ozonState.productDiagnosis || {};
  let items = Array.isArray(data.items) ? data.items : [];
  const visibility = document.getElementById('ozonProductVisibilityFilter')?.value || ozonState.productVisibility || '';
  const risk = document.getElementById('ozonProductRiskFilter')?.value || ozonState.productDiagnosisRisk || '';
  const keyword = normalizeOzonText(document.getElementById('ozonProductDiagnosisKeywordInput')?.value || ozonState.productDiagnosisKeyword).toLowerCase();
  const normalizedVisibility = normalizeOzonVisibilityForUi(visibility);
  ozonState.productDiagnosisRisk = risk;
  ozonState.productDiagnosisKeyword = keyword;
  if (normalizedVisibility) {
    items = items.filter(item => normalizeOzonProductForUi(item.product || {}).visibility === normalizedVisibility);
  }
  if (risk === 'sales-warning') {
    items = items.filter(item => item.sales?.alert || Number(item.sales?.trend7Percent || 0) <= -20);
  } else if (risk === 'good') {
    items = items.filter(item => Number(item.diagnosis?.score || 0) >= 85);
  } else if (risk) {
    const riskCodeMap = { '高': 'high', '中': 'medium', '低': 'low' };
    items = items.filter(item => item.diagnosis?.riskCode === riskCodeMap[risk] || item.diagnosis?.riskLevel === risk);
  }
  if (keyword) {
    items = items.filter(item => {
      const ui = normalizeOzonProductForUi(item.product || {});
      const diag = item.diagnosis || {};
      return [
        ui.title, ui.productId, ui.offerId, ui.sku, ui.accountTitle,
        ...(diag.aiProblems || []), ...(diag.aiSuggestions || []), ...(diag.aiBasis || []), diag.aiOptimizedTitle, diag.aiTitleBasis, diag.aiKeywordBasis
      ].some(value => String(value || '').toLowerCase().includes(keyword));
    });
  }
  return items;
}

function renderOzonProductDiagnosis() {
  const wrap = document.getElementById('ozonProductDiagnosisList');
  const pagination = document.getElementById('ozonProductDiagnosisPagination');
  const data = ozonState.productDiagnosis || {};
  const summary = data.summary || {};
  setOzonText('ozonProductAiAvgScore', String(summary.avgScore || 0));
  setOzonText('ozonProductAiHighRisk', String(summary.highRisk || 0));
  setOzonText('ozonProductAiMediumRisk', String(summary.mediumRisk || 0));
  setOzonText('ozonProductAiSalesWarnings', String(summary.salesWarnings || 0));
  setOzonText('ozonProductAiGood', String(summary.good || 0));
  if (!wrap) return;
  if (ozonState.productDiagnosisLoading && !Array.isArray(data.items)) {
    setOzonHtml('ozonProductDiagnosisMeta', renderOzonMetaItems(['状态：<strong>正在读取服务器商品缓存/官方商品数据</strong>']));
    wrap.innerHTML = renderOzonLoadingEmpty('正在读取商品诊断...');
    if (pagination) pagination.innerHTML = '';
    return;
  }
  if (!hasOzonAccounts()) {
    setOzonHtml('ozonProductDiagnosisMeta', renderOzonMetaItems(['统计范围：<strong>未绑定店铺</strong>', '请先在首页添加 OZON 店铺授权']));
    wrap.innerHTML = renderOzonNeedAccountEmpty('还没有绑定 OZON 店铺。添加授权并同步商品后，这里会展示 AI 商品诊断结果。');
    if (pagination) pagination.innerHTML = '';
    return;
  }
  const items = getFilteredOzonDiagnosisItems();
  const rawItems = Array.isArray(data.items) ? data.items : [];
  const totalItems = Number(data.totalItems || data.summary?.totalProducts || rawItems.length || 0);
  const filteredTotal = Number(data.filteredItems || rawItems.length || 0);
  const page = Math.max(1, Number(ozonState.productDiagnosisPage || 1) || 1);
  const limit = Math.max(1, Number(ozonState.productDiagnosisLimit || 50) || 50);
  const serverPaged = Number(data.responseLimit || 0) > 0 || Number(data.responseOffset || 0) > 0;
  const totalRows = serverPaged ? (filteredTotal || items.length) : items.length;
  const maxPage = Math.max(1, Math.ceil(totalRows / limit));
  const safePage = Math.min(page, maxPage);
  if (safePage !== page) ozonState.productDiagnosisPage = safePage;
  const start = serverPaged ? Number(data.responseOffset || ((safePage - 1) * limit)) : ((safePage - 1) * limit);
  const visibleItems = serverPaged ? items : items.slice(start, start + limit);
  const missingImageCount = items.filter(item => !normalizeOzonProductForUi(item.product || {}).image).length;
  const accountId = document.getElementById('ozonProductDiagnosisAccountFilter')?.value || ozonState.productDiagnosisAccountId || ozonState.productAccountId || '';
  const skuFilter = document.getElementById('ozonProductDiagnosisSkuInput')?.value?.trim() || ozonState.productDiagnosisSku || '';
  const risk = document.getElementById('ozonProductRiskFilter')?.value || ozonState.productDiagnosisRisk || '';
  const visibility = normalizeOzonVisibilityForUi(document.getElementById('ozonProductVisibilityFilter')?.value || ozonState.productVisibility || 'IN_SALE') || 'IN_SALE';
  const visibilityLabel = visibility === 'TO_SUPPLY' ? '准备出售' : '在售中';
  setOzonHtml('ozonProductDiagnosisMeta', renderOzonMetaItems([
    `统计范围：<strong>${escapeHtml(getOzonAccountLabel(accountId))}</strong>`,
    skuFilter ? `筛选：<strong>${escapeHtml(skuFilter)}</strong>` : '筛选：全部 SKU / Offer',
    risk ? `风险：<strong>${escapeHtml(risk)}</strong>` : '风险：全部',
    `状态：<strong>${escapeHtml(visibilityLabel)}</strong>`,
    `显示：<strong>${escapeHtml(totalRows ? start + 1 : 0)}-${escapeHtml(Math.min(start + visibleItems.length, totalRows))}</strong> / ${escapeHtml(filteredTotal || totalRows)} 个诊断`,
    filteredTotal && totalItems && filteredTotal !== totalItems ? `全量：${escapeHtml(totalItems)} 个` : '',
    rawItems.length !== items.length ? `当前结果：${escapeHtml(rawItems.length)} 个` : '',
    `缺主图：<strong>${escapeHtml(missingImageCount)}</strong> 个`,
    escapeHtml(buildOzonHydrationText(data.hydration || {}))
  ].filter(Boolean).map(item => item.replace(/^([^：]+)：/, '<b>$1：</b>'))));
  if (!items.length) {
    const filterText = [
      visibilityLabel,
      skuFilter ? `SKU/Offer：${skuFilter}` : '',
      risk ? `风险：${risk}` : ''
    ].filter(Boolean).join('，');
    wrap.innerHTML = '<div class="ozon-empty">暂无匹配的商品诊断结果。请先同步商品，或调整筛选条件。</div>';
    setOzonHtml('ozonProductDiagnosisMeta', renderOzonMetaItems([
      `状态：<strong>${escapeHtml(filterText || visibilityLabel)}</strong>`,
      `显示：<strong>0-0</strong> / ${escapeHtml(filteredTotal || 0)} 个诊断`,
      escapeHtml(buildOzonHydrationText(data.hydration || {}))
    ].filter(Boolean).map(item => item.replace(/^([^：]+)：/, '<b>$1：</b>'))));
    if (pagination) pagination.innerHTML = '';
    return;
  }
  wrap.innerHTML = `<table class="ozon-table ozon-diagnosis-table ozon-product-diagnosis-table">
    ${renderOzonColumnGroup(OZON_PRODUCT_DIAGNOSIS_COLUMN_DEFAULTS, 'productDiagnosisCompact')}
    <thead>
      <tr>
        <th>商品</th>
        <th>评分 / 风险</th>
        <th>销量趋势</th>
        <th>商品资料</th>
        <th>现存问题</th>
        <th>整改建议</th>
        <th>建议标题</th>
        <th>操作</th>
      </tr>
    </thead>
    <tbody>${visibleItems.map((item, index) => {
    const product = item.product || {};
    const ui = normalizeOzonProductForUi(product);
    const diag = item.diagnosis || {};
    const sales = item.sales || {};
    const scoreTone = diag.tone === 'good' ? 'ozon-score-good' : diag.tone === 'warn' ? 'ozon-score-warn' : 'ozon-score-bad';
    const trendClass = getOzonTrendClass(sales.trendStatus);
    const image = renderOzonImageThumb(ui.image, 'ozon-diagnosis-product-thumb');
    const trend28 = Number(sales.trend28Percent || 0);
    const aiState = getOzonAiDiagnosisState(diag);
    const titleState = getOzonAiTitleState(diag);
    const noteTarget = getOzonNoteTargetFromDiagnosisItem(item);
    const itemIndex = serverPaged ? index : start + index;
    const aiTargetKey = getOzonDiagnosisTargetKey(item);
    const aiRunning = aiTargetKey && ozonState.productDiagnosisAiRunningKeys?.has?.(aiTargetKey);
    return `<tr class="${diag.riskLevel === '高' || diag.riskCode === 'high' ? 'is-warning' : ''}">
      <td>
        <div class="ozon-product-cell ozon-diagnosis-product-cell">${image}<div>
          <div class="ozon-product-title">${escapeHtml(ui.title)}</div>
          <div class="ozon-product-meta">Offer: <span class="ozon-mono">${escapeHtml(ui.offerId || '--')}</span><br>${escapeHtml(ui.accountTitle || 'OZON')} · ${escapeHtml(ui.statusLabel || '未知状态')}</div>
        </div></div>
      </td>
      <td>
        <div class="ozon-diagnosis-badge-stack">
          <span class="ozon-score-badge ${scoreTone}">${escapeHtml(diag.score ?? 0)}分</span>
          <span class="ozon-risk-badge ${scoreTone}">风险 ${escapeHtml(diag.riskLevel || '低')}</span>
          <span class="ozon-trend-pill ${trendClass}">${escapeHtml(getOzonTrendLabel(sales.trendStatus))}</span>
        </div>
      </td>
      <td>
        <div class="ozon-diagnosis-column">
          <span>今日 ${escapeHtml((sales.todayOrders ?? sales.todayQty) || 0)}</span>
          <span>7日 ${escapeHtml((sales.orderCount7 ?? sales.qty7) || 0)}</span>
          <span>28日 ${escapeHtml((sales.orderCount28 ?? sales.qty28) || 0)}</span>
          <span>7日环比 ${escapeHtml(Number(sales.trend7Percent || 0).toFixed(1))}%</span>
          <span>28日环比 ${escapeHtml(trend28.toFixed(1))}%</span>
        </div>
      </td>
      <td>
        <div class="ozon-diagnosis-column">
          <span>图片 ${escapeHtml(diag.imageCount || 0)}</span>
          <span>属性 ${escapeHtml(diag.attributeCount || 0)}</span>
          <span>库存 ${escapeHtml(product.stock ?? '--')}</span>
          <span>${escapeHtml(product.priceIndex?.label || diag.priceIndex || '--')}</span>
        </div>
      </td>
      <td><div class="ozon-diagnosis-column">${renderOzonProblemLines(aiState.problems || [], aiState.unavailable ? 'AI不可用：请稍后再试' : 'AI暂未返回问题')}</div></td>
      <td><div class="ozon-diagnosis-column ${aiState.unavailable ? 'ozon-ai-unavailable' : ''}">${renderOzonProblemLines(aiState.suggestions || [], aiState.unavailable ? 'AI不可用：请稍后再试' : 'AI暂未返回建议')}</div></td>
      <td>
        <div class="ozon-diagnosis-copy is-title-card">
          <strong>建议标题</strong>
          <span class="${titleState.unavailable ? 'ozon-ai-unavailable' : ''}">${escapeHtml(titleState.title)}</span>
          <strong>为什么更好</strong>
          <span class="ozon-title-basis">${escapeHtml(titleState.basis)}</span>
        </div>
        ${renderOzonAiDiagnosisTime(diag)}
        ${item.latestNote?.text ? `<div class="ozon-product-remark">${renderOzonLatestNote(item.latestNote)}</div>` : ''}
      </td>
      <td>
        <div class="ozon-diagnosis-actions">
          <button class="btn btn-primary btn-sm" type="button" data-ozon-run-product-ai="${itemIndex}" ${aiRunning ? 'disabled' : ''}>${aiRunning ? '诊断中...' : 'AI诊断'}</button>
          <button class="btn btn-default btn-sm" type="button" data-ozon-diagnosis-detail="${itemIndex}">完整方案</button>
          <button class="btn btn-default btn-sm" type="button" data-ozon-copy-title="${escapeHtml(titleState.canCopy ? titleState.title : '')}" ${titleState.canCopy ? '' : 'disabled'}>复制标题</button>
          ${renderOzonNoteButton(noteTarget, '备注')}
        </div>
      </td>
    </tr>`;
  }).join('')}</tbody></table>`;
  setupOzonResizableTable(wrap.querySelector('.ozon-product-diagnosis-table'), 'productDiagnosisCompact', OZON_PRODUCT_DIAGNOSIS_COLUMN_DEFAULTS, OZON_PRODUCT_DIAGNOSIS_COLUMN_MINS);
  if (pagination) {
    pagination.innerHTML = `
      <button type="button" id="ozonProductDiagnosisPrevPageBtn"${safePage <= 1 ? ' disabled' : ''}>上一页</button>
      <span>第 ${safePage} 页 / 共 ${maxPage} 页 · ${totalRows} 条</span>
      <button type="button" id="ozonProductDiagnosisNextPageBtn"${safePage >= maxPage ? ' disabled' : ''}>下一页</button>
    `;
  }
  wrap.querySelectorAll('[data-ozon-diagnosis-detail]').forEach(button => {
    button.addEventListener('click', () => showOzonDiagnosisDetail(items[Number(button.dataset.ozonDiagnosisDetail || 0)]));
  });
  wrap.querySelectorAll('[data-ozon-run-product-ai]').forEach(button => {
    button.addEventListener('click', () => runOzonSingleProductDiagnosis(Number(button.dataset.ozonRunProductAi || 0)));
  });
  wrap.querySelectorAll('[data-ozon-copy-title]').forEach(button => {
    button.addEventListener('click', () => copyOzonText(button.dataset.ozonCopyTitle || ''));
  });
  bindOzonNoteButtons(wrap);
  pagination?.querySelector('#ozonProductDiagnosisPrevPageBtn')?.addEventListener('click', () => {
    ozonState.productDiagnosisPage = Math.max(1, (ozonState.productDiagnosisPage || 1) - 1);
    loadOzonProductDiagnosis({ enrichAi: false, resetAiQueue: false }).catch(err => setOzonResult(err.message || '商品诊断读取失败', 'error'));
  });
  pagination?.querySelector('#ozonProductDiagnosisNextPageBtn')?.addEventListener('click', () => {
    ozonState.productDiagnosisPage = Math.min(maxPage, (ozonState.productDiagnosisPage || 1) + 1);
    loadOzonProductDiagnosis({ enrichAi: false, resetAiQueue: false }).catch(err => setOzonResult(err.message || '商品诊断读取失败', 'error'));
  });
}

function renderOzonAds() {
  syncOzonAccountSelects();
  const data = ozonState.ads || {};
  const summary = data.summary || {};
  const campaigns = Array.isArray(data.campaigns) ? data.campaigns : [];
  const accountId = getOzonPerformanceAccountId();
  const keyword = normalizeOzonText(document.getElementById('ozonAdsKeywordInput')?.value || ozonState.adsKeyword).toLowerCase();
  const filtered = keyword
    ? campaigns.filter(item => [item.id, item.title, item.stateLabel, item.advObjectTypeLabel].some(value => String(value || '').toLowerCase().includes(keyword)))
    : campaigns;
  setOzonText('ozonAdsTotal', String(summary.total ?? campaigns.length ?? 0));
  setOzonText('ozonAdsRunning', String(summary.running || 0));
  setOzonText('ozonAdsInactive', String(summary.inactive || 0));
  setOzonText('ozonAdsWeeklyBudget', formatOzonRubAmount(summary.weeklyBudget || 0));
  const hasAdsError = Array.isArray(data.errors) && data.errors.length > 0;
  setOzonHtml('ozonAdsMeta', renderOzonMetaItems([
    `店铺：<strong>${escapeHtml(getOzonAccountLabel(accountId) || '默认店铺')}</strong>`,
    ozonState.performanceConfig ? `授权：<strong>${escapeHtml(ozonState.performanceConfig.clientSecretMasked || '已保存')}</strong>` : '授权：未绑定',
    `筛选状态：<strong>${escapeHtml(document.getElementById('ozonAdsStateFilter')?.selectedOptions?.[0]?.textContent || '全部状态')}</strong>`,
    `广告类型：<strong>${escapeHtml(document.getElementById('ozonAdsTypeFilter')?.selectedOptions?.[0]?.textContent || '全部类型')}</strong>`,
    `活动数量：<strong>${escapeHtml(filtered.length)}</strong> / ${escapeHtml(campaigns.length)}`,
    data.fetchedAt ? `读取时间：${escapeHtml(formatOzonDateTime(data.fetchedAt))}` : '',
    hasAdsError ? '' : 'OZON Performance API 限流严格，请避免连续高频刷新。'
  ].filter(Boolean).map(item => item.replace(/^([^：]+)：/, '<b>$1：</b>'))));
  const wrap = document.getElementById('ozonAdsList');
  if (!wrap) return;
  if (ozonState.adsLoading && !Array.isArray(data.campaigns)) {
    wrap.innerHTML = renderOzonLoadingEmpty('正在读取广告活动...');
    return;
  }
  if (!hasOzonAccounts()) {
    wrap.innerHTML = renderOzonNeedAccountEmpty('还没有绑定 OZON 店铺。广告分析需要先在首页添加 Seller API 授权，Performance API 授权在本页单独绑定。');
    return;
  }
  if (!ozonState.performanceConfig) {
    wrap.innerHTML = '<div class="ozon-empty">请先点击“Performance 授权”绑定 Client ID 和 Client Secret。</div>';
    return;
  }
  if (!campaigns.length) {
    const errors = Array.isArray(data.errors) ? data.errors : [];
    const errorText = errors.map(item => item.message || item.error).filter(Boolean).join('；');
    const emptyText = errorText || data.sourceStatusText || (data.fetchedAt
      ? '当前筛选条件下暂无广告活动；可切换状态/类型后重新刷新。'
      : '已保存 Performance API 授权，请点击“刷新广告活动”读取活动数据。');
    wrap.innerHTML = `<div class="ozon-empty">${escapeHtml(emptyText)}</div>`;
    return;
  }
  wrap.innerHTML = `<table class="ozon-table ozon-ads-table">
    <thead><tr><th>广告活动</th><th>状态</th><th>类型</th><th>付费</th><th>预算</th><th>投放位置</th><th>时间</th></tr></thead>
    <tbody>${filtered.map(item => {
      const stateClass = item.state === 'CAMPAIGN_STATE_RUNNING' ? 'ozon-trend-up' : item.state === 'CAMPAIGN_STATE_INACTIVE' ? 'ozon-trend-flat' : 'ozon-trend-down';
      const budgetParts = [
        Number(item.weeklyBudget || 0) ? `周 ${formatOzonRubAmount(item.weeklyBudget)} RUB` : '',
        Number(item.dailyBudget || 0) ? `日 ${formatOzonRubAmount(item.dailyBudget)} RUB` : '',
        Number(item.budget || 0) ? `总 ${formatOzonRubAmount(item.budget)} RUB` : ''
      ].filter(Boolean);
      return `<tr>
        <td><div class="ozon-list-title">${escapeHtml(item.title || 'OZON 广告活动')}</div><div class="ozon-list-meta">ID: <span class="ozon-mono">${escapeHtml(item.id || '--')}</span></div></td>
        <td><span class="${stateClass}">${escapeHtml(item.stateLabel || item.state || '--')}</span></td>
        <td>${escapeHtml(item.advObjectTypeLabel || item.advObjectType || '--')}</td>
        <td>${escapeHtml(item.paymentType || '--')}</td>
        <td>${escapeHtml(budgetParts.join(' / ') || '--')}<div class="ozon-list-meta">${escapeHtml(item.budgetType || '')}</div></td>
        <td>${escapeHtml((item.placement || []).join(' / ') || '--')}</td>
        <td><div class="ozon-list-meta">开始：${escapeHtml(item.fromDate || '--')}<br>结束：${escapeHtml(item.toDate || '未设置')}<br>更新：${escapeHtml(formatOzonDateTime(item.updatedAt))}</div></td>
      </tr>`;
    }).join('') || '<tr><td colspan="7">暂无匹配广告活动</td></tr>'}</tbody>
  </table>`;
}

function renderOzonTraffic() {
  syncOzonAccountSelects();
  const data = ozonState.traffic || {};
  const summary = data.summary || {};
  const wrap = document.getElementById('ozonTrafficList');
  const pagination = document.getElementById('ozonTrafficPagination');
  const keyword = normalizeOzonText(document.getElementById('ozonTrafficKeywordInput')?.value || ozonState.trafficKeyword).toLowerCase();
  const products = (Array.isArray(data.products) ? data.products : []).filter(item => {
    if (!keyword) return true;
    return [item.name, item.sku, item.offerId, item.productId, item.accountTitle].some(value => String(value || '').toLowerCase().includes(keyword));
  });
  const totalRows = products.length;
  const page = Math.max(1, Number(ozonState.trafficPage || 1) || 1);
  const limit = Math.max(1, Number(ozonState.trafficLimit || 50) || 50);
  const maxPage = Math.max(1, Math.ceil(totalRows / limit));
  const safePage = Math.min(page, maxPage);
  if (safePage !== page) ozonState.trafficPage = safePage;
  const start = (safePage - 1) * limit;
  const visibleProducts = products.slice(start, start + limit);
  const displayStart = totalRows && visibleProducts.length ? start + 1 : 0;
  const displayEnd = totalRows && visibleProducts.length ? Math.min(start + visibleProducts.length, totalRows) : 0;
  setOzonText('ozonTrafficImpressions', Number(summary.impressions || 0).toLocaleString());
  setOzonText('ozonTrafficViews', Number(summary.productViews || 0).toLocaleString());
  setOzonText('ozonTrafficCartAdds', Number(summary.cartAdds || 0).toLocaleString());
  setOzonText('ozonTrafficOrderedUnits', Number(summary.orderedUnits || 0).toLocaleString());
  setOzonText('ozonTrafficRevenue', formatOzonAmount(summary.revenue || 0, 'RUB'));
  setOzonText('ozonTrafficConversion', `${(Number(summary.conversion || 0) * 100).toFixed(2)}%`);
  setOzonHtml('ozonTrafficMeta', renderOzonMetaItems([
    `店铺：<strong>${escapeHtml(getOzonAccountLabel(ozonState.trafficAccountId || ''))}</strong>`,
    keyword ? `筛选：<strong>${escapeHtml(keyword)}</strong>` : '筛选：全部 SKU / Offer',
    `显示：<strong>${escapeHtml(displayStart)}-${escapeHtml(displayEnd)}</strong> / ${escapeHtml(totalRows)} 个商品`,
    `全量：<strong>${escapeHtml(Array.isArray(data.products) ? data.products.length : 0)}</strong> 个`,
    data.range ? `时间：<strong>${escapeHtml(data.range.dateFrom || '--')}</strong> 至 <strong>${escapeHtml(data.range.dateTo || '--')}</strong>` : '',
    data.__fromServerCache ? `服务器缓存：${escapeHtml(formatOzonDateTime(data.__serverCacheSavedAt))}${data.__serverCacheStale ? '（已过期，点击刷新可更新）' : ''}` : '',
    data.__fromOzonCache ? `本地缓存：${escapeHtml(formatOzonDateTime(data.__cacheSavedAt))}` : '',
    data.sourceStatusText ? escapeHtml(data.sourceStatusText) : '',
    Array.isArray(data.errors) && data.errors.length ? `异常店铺：<strong>${escapeHtml(data.errors.length)}</strong> 个` : '',
    data.fetchedAt ? `读取时间：${escapeHtml(formatOzonDateTime(data.fetchedAt))}` : ''
  ].filter(Boolean).map(item => item.replace(/^([^：]+)：/, '<b>$1：</b>'))));
  if (!wrap) return;
  if (ozonState.trafficLoading && !Array.isArray(data.products)) {
    wrap.innerHTML = renderOzonLoadingEmpty('正在读取流量数据...');
    if (pagination) pagination.innerHTML = '';
    return;
  }
  if (!hasOzonAccounts()) {
    wrap.innerHTML = renderOzonNeedAccountEmpty('还没有绑定 OZON 店铺，暂时无法读取商品流量数据。');
    if (pagination) pagination.innerHTML = '';
    return;
  }
  if (!data || !Object.keys(data).length) {
    wrap.innerHTML = '<div class="ozon-empty">正在读取服务器保存的流量数据。服务器暂无保存记录时，请点击“刷新流量”读取 OZON 官方接口并保存。</div>';
    if (pagination) pagination.innerHTML = '';
    return;
  }
  if (!products.length) {
    const errors = Array.isArray(data.errors) ? data.errors : [];
    const hasRateLimit = errors.some(item => item.code === 'OZON_RATE_LIMIT' || /rate\s*limit|限流|速率限制|limit\s+per\s+second/i.test(String(item.message || '')));
    const emptyText = keyword
      ? '当前筛选条件下暂无流量数据。'
      : (data.sourceStatusText || '暂无流量数据');
    const hint = hasRateLimit ? '<br>建议等待 1-2 分钟后再次点击“刷新流量”，系统会按店铺排队读取。' : '';
    wrap.innerHTML = `<div class="ozon-empty">${escapeHtml(emptyText)}${errors.length ? `<br>${escapeHtml(errors.map(item => `${item.accountTitle || item.accountId}: ${item.message}`).join('；'))}` : ''}${hint}</div>`;
    if (pagination) pagination.innerHTML = '';
    return;
  }
  wrap.innerHTML = `<table class="ozon-table ozon-traffic-table">
    ${renderOzonColumnGroup(OZON_TRAFFIC_COLUMN_DEFAULTS, 'trafficAnalysis')}
    <thead><tr><th>图片</th><th>查看详情</th><th>店铺</th><th>曝光</th><th>浏览</th><th>加购</th><th>销量</th><th>销售额</th><th>转化率</th><th>诊断</th></tr></thead>
    <tbody>${visibleProducts.map((item, index) => `<tr>
      <td><div class="ozon-traffic-image-cell">${renderOzonImageThumb(item.image || '', 'ozon-order-product-thumb')}</div></td>
      <td><button class="btn btn-default btn-sm ozon-traffic-detail-btn" type="button" data-ozon-traffic-detail="${escapeHtml(String(index))}">查看详情</button></td>
      <td>${escapeHtml(item.accountTitle || 'OZON 店铺')}</td>
      <td>${escapeHtml(Number(item.impressions || 0).toLocaleString())}</td>
      <td>${escapeHtml(Number(item.productViews || 0).toLocaleString())}</td>
      <td>${escapeHtml(Number(item.cartAdds || 0).toLocaleString())}</td>
      <td>${escapeHtml(Number(item.orderedUnits || 0).toLocaleString())}</td>
      <td>${escapeHtml(formatOzonAmount(item.revenue || 0, 'RUB'))}</td>
      <td><span class="${Number(item.conversion || 0) >= 0.03 ? 'ozon-trend-up' : Number(item.conversion || 0) > 0 ? 'ozon-trend-flat' : 'ozon-trend-down'}">${escapeHtml((Number(item.conversion || 0) * 100).toFixed(2))}%</span></td>
      <td>${renderOzonTrafficInsight(item)}</td>
    </tr>`).join('')}</tbody>
  </table>`;
  setupOzonResizableTable(wrap.querySelector('.ozon-traffic-table'), 'trafficAnalysis', OZON_TRAFFIC_COLUMN_DEFAULTS, OZON_TRAFFIC_COLUMN_MINS);
  wrap.querySelectorAll('[data-ozon-traffic-detail]').forEach(button => {
    button.addEventListener('click', () => openOzonTrafficProductDetail(visibleProducts[Number(button.dataset.ozonTrafficDetail || 0)] || {}));
  });
  if (pagination) {
    pagination.innerHTML = `
      <button type="button" id="ozonTrafficPrevPageBtn"${safePage <= 1 ? ' disabled' : ''}>上一页</button>
      <span id="ozonTrafficPageInfo">第 ${safePage} 页 / 共 ${maxPage} 页 · ${totalRows} 条</span>
      <button type="button" id="ozonTrafficNextPageBtn"${safePage >= maxPage ? ' disabled' : ''}>下一页</button>
    `;
  }
  pagination?.querySelector('#ozonTrafficPrevPageBtn')?.addEventListener('click', () => {
    ozonState.trafficPage = Math.max(1, (ozonState.trafficPage || 1) - 1);
    renderOzonTraffic();
  });
  pagination?.querySelector('#ozonTrafficNextPageBtn')?.addEventListener('click', () => {
    ozonState.trafficPage = Math.min(maxPage, (ozonState.trafficPage || 1) + 1);
    renderOzonTraffic();
  });
}

async function loadOzonAds(options = {}) {
  const hasPrevious = Boolean(ozonState.ads && Array.isArray(ozonState.ads.campaigns));
  if (!options.silent && (!hasPrevious || options.forceRefresh)) {
    setOzonText('ozonAdsMeta', hasPrevious ? '正在刷新广告活动，已保留上次结果...' : '正在读取 OZON Performance 广告活动...');
  }
  ozonState.adsLoading = true;
  if (!options.silent && !hasPrevious) renderOzonAds();
  try {
    const accountId = getOzonPerformanceAccountId();
    ozonState.adsAccountId = accountId;
    ozonState.performanceAccountId = accountId;
    if (!ozonState.performanceConfig || String(ozonState.performanceConfig.accountId || '') !== String(accountId)) await loadOzonPerformanceConfig(options);
    if (!ozonState.performanceConfig) {
      ozonState.ads = null;
      ozonState.adsLoading = false;
      renderOzonAds();
      return;
    }
    const state = document.getElementById('ozonAdsStateFilter')?.value || ozonState.adsState || '';
    const advObjectType = document.getElementById('ozonAdsTypeFilter')?.value || ozonState.adsType || '';
    const qs = new URLSearchParams();
    if (accountId) qs.set('accountId', accountId);
    if (state) qs.set('state', state);
    if (advObjectType) qs.set('advObjectType', advObjectType);
    const path = `/performance/campaigns${qs.toString() ? `?${qs.toString()}` : ''}`;
    await loadOzonCached(path, (result) => {
      ozonState.ads = result;
      ozonState.adsLoading = false;
      ozonState.performanceConfig = result.config || ozonState.performanceConfig;
      renderOzonAds();
    }, { allowStale: true, keepStaleOnError: true, ...options });
    ozonState.adsLoading = false;
    renderOzonAds();
  } catch (err) {
    ozonState.adsLoading = false;
    const wrap = document.getElementById('ozonAdsList');
    if (hasPrevious) {
      renderOzonAds();
      setOzonText('ozonAdsMeta', `显示上次广告数据，最新刷新失败：${err.message || '广告活动读取失败'}`);
      return;
    }
    if (wrap) wrap.innerHTML = `<div class="ozon-empty">${escapeHtml(err.message || '广告活动读取失败')}</div>`;
    setOzonHtml('ozonAdsMeta', renderOzonMetaItems([
      ozonState.performanceConfig ? `授权：<strong>${escapeHtml(ozonState.performanceConfig.clientSecretMasked || '已保存')}</strong>` : '授权：未绑定',
      `筛选状态：<strong>${escapeHtml(document.getElementById('ozonAdsStateFilter')?.selectedOptions?.[0]?.textContent || '全部状态')}</strong>`,
      `广告类型：<strong>${escapeHtml(document.getElementById('ozonAdsTypeFilter')?.selectedOptions?.[0]?.textContent || '全部类型')}</strong>`
    ].map(item => item.replace(/^([^：]+)：/, '<b>$1：</b>'))));
  }
}

async function loadOzonTraffic(options = {}) {
  const hasPrevious = Boolean(ozonState.traffic && Array.isArray(ozonState.traffic.products));
  if (!options.silent && (!hasPrevious || options.forceRefresh)) {
    const loadingText = options.forceRefresh
      ? '正在同步 OZON 官方流量数据，同步成功后会覆盖服务器保存的数据...'
      : (hasPrevious ? '正在读取服务器保存的流量数据，已保留上次结果...' : '正在读取服务器保存的流量数据...');
    setOzonText('ozonTrafficMeta', loadingText);
  }
  ozonState.trafficLoading = true;
  if (!options.silent && !hasPrevious) renderOzonTraffic();
  try {
    const accountId = getOzonActiveAccountId(document.getElementById('ozonTrafficAccountFilter')?.value || ozonState.trafficAccountId || '');
    ozonState.trafficAccountId = accountId;
    ozonState.trafficKeyword = document.getElementById('ozonTrafficKeywordInput')?.value?.trim() || ozonState.trafficKeyword || '';
    const qs = new URLSearchParams();
    if (accountId) qs.set('ozonAccountId', accountId);
    if (options.forceRefresh) qs.set('refresh', '1');
    const path = `/performance/traffic${qs.toString() ? `?${qs.toString()}` : ''}`;
    await loadOzonCached(path, (result) => {
      ozonState.traffic = result;
      ozonState.trafficLoading = false;
      if (options.forceRefresh) {
        clearOzonReadCacheByBase('/performance/traffic');
        clearOzonReadCacheByBase('/analytics/orders');
        clearOzonReadCacheByBase('/analytics/products');
      }
      renderOzonTraffic();
      renderOzonOrderAnalysis();
    }, { allowStale: true, keepStaleOnError: true, cache: false, ...options });
    ozonState.trafficLoading = false;
  } catch (err) {
    ozonState.trafficLoading = false;
    const wrap = document.getElementById('ozonTrafficList');
    const message = err.message || '流量数据读取失败';
    if (ozonState.traffic && Array.isArray(ozonState.traffic.products)) {
      renderOzonTraffic();
      setOzonHtml('ozonTrafficMeta', renderOzonMetaItems([
        '状态：<strong>显示上次读取数据</strong>',
        `最新刷新失败：${escapeHtml(message)}`,
        ozonState.traffic.fetchedAt ? `上次读取：${escapeHtml(formatOzonDateTime(ozonState.traffic.fetchedAt))}` : ''
      ].filter(Boolean)));
      return;
    }
    if (wrap) wrap.innerHTML = `<div class="ozon-empty">${escapeHtml(message)}<br>如果店铺未开通 Premium 流量指标，系统会自动尝试展示官方基础销量与销售额。</div>`;
    setOzonText('ozonTrafficMeta', message);
  }
}

function getOzonMarketingFilteredData() {
  const data = ozonState.marketing || {};
  const actions = Array.isArray(data.actions) ? data.actions : [];
  const actionIds = new Set(actions.map(action => String(action.id || '')));
  const products = (Array.isArray(data.products) ? data.products : []).filter(product => {
    return !actionIds.size || actionIds.has(String(product.actionId || ''));
  });
  return { actions, products };
}

function resetOzonMarketingAccountView(accountId = '') {
  ozonState.marketingAccountId = getOzonActiveAccountId(accountId || '');
  ozonState.marketing = null;
  ozonState.marketingProducts = null;
  ozonState.marketingSelectedAction = null;
  ozonState.marketingProductPage = 1;
  ozonState.marketingProductKeyword = '';
  ozonState.marketingProductSource = 'joined';
  ozonState.marketingDetailTab = 'joined';
  ozonState.marketingDetailAutoAddDate = '';
  ozonState.marketingDetailSelectedIds = new Set();
  ozonState.marketingCandidateProducts = null;
  ozonState.marketingCandidatePage = 1;
  ozonState.marketingCandidateKeyword = '';
  ozonState.marketingCandidateSelectedIds = new Set();
  ozonState.marketingCandidatePriceEdits = {};
  ozonState.discountTasks = null;
  setOzonMarketingActiveTitle('');
  setOzonMarketingActionModalVisible(false);
  setOzonMarketingView('actions');
  const productWrap = document.getElementById('ozonMarketingProductList');
  if (productWrap) productWrap.innerHTML = '<div class="ozon-empty">请选择当前店铺的活动。</div>';
}

const OZON_ACTION_TITLE_ZH_RULES = [
  [/эластичный\s+бустинг/i, '弹性提升。无有效期限制'],
  [/супербустинг/i, '超级提升'],
  [/максимальный\s+бустинг:\s*усиление/i, '最大提升：加强'],
  [/максимальный\s+бустинг/i, '最大提升'],
  [/больш[а-яё]*\s+скид/i, '最大提升：大幅折扣'],
  [/учебные\s+скид/i, '学校用品折扣'],
  [/школьн[а-яё]*\s+товар/i, '学校用品折扣'],
  [/распродаж/i, '大促销'],
  [/скидк/i, '折扣促销'],
  [/бустинг/i, '提升活动']
];

function translateOzonActionTitleZh(title = '') {
  const text = normalizeOzonText(title);
  if (!text) return '';
  if (/[\u3400-\u9fff]/.test(text)) return text;
  for (const [pattern, label] of OZON_ACTION_TITLE_ZH_RULES) {
    if (pattern.test(text)) return label;
  }
  return text;
}

function getOzonActionDisplayTitle(action = {}) {
  return translateOzonActionTitleZh(action.title || action.originalTitle || action.name || '') || 'OZON 活动';
}

function setOzonMarketingView(view = 'actions') {
  const normalized = ['actions', 'products', 'discounts'].includes(view) ? view : 'actions';
  ozonState.marketingView = normalized;
  document.querySelectorAll('[data-ozon-marketing-view]').forEach((button) => {
    button.classList.toggle('active', button.dataset.ozonMarketingView === normalized);
  });
  [
    ['actions', 'ozonMarketingActionsPanel'],
    ['products', 'ozonMarketingProductsPanel'],
    ['discounts', 'ozonMarketingDiscountPanel']
  ].forEach(([key, id]) => {
    const panel = document.getElementById(id);
    if (panel) panel.classList.toggle('is-active', key === normalized);
  });
}

function setOzonMarketingActiveTitle(title = '') {
  const el = document.getElementById('ozonMarketingActiveTitle');
  if (!el) return;
  const text = normalizeOzonText(title);
  el.textContent = text;
  el.title = text;
  el.classList.toggle('has-action', Boolean(text));
}

function getOzonMoscowDateParts(value) {
  if (!value) return '--';
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return String(value);
  const pad = num => String(num).padStart(2, '0');
  try {
    const parts = new Intl.DateTimeFormat('en-GB', {
      timeZone: 'Europe/Moscow',
      day: '2-digit',
      month: '2-digit',
      year: 'numeric'
    }).formatToParts(date);
    const lookup = Object.fromEntries(parts.map(part => [part.type, part.value]));
    if (lookup.day && lookup.month && lookup.year) {
      return { day: lookup.day, month: lookup.month, year: lookup.year };
    }
  } catch (_) {}
  const moscowDate = new Date(date.getTime() + 3 * 60 * 60 * 1000);
  return {
    day: pad(moscowDate.getUTCDate()),
    month: pad(moscowDate.getUTCMonth() + 1),
    year: String(moscowDate.getUTCFullYear())
  };
}

function isOzonMoscowStartOfDay(value) {
  if (!value) return false;
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return false;
  try {
    const parts = new Intl.DateTimeFormat('en-GB', {
      timeZone: 'Europe/Moscow',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      hourCycle: 'h23'
    }).formatToParts(date);
    const lookup = Object.fromEntries(parts.map(part => [part.type, part.value]));
    return lookup.hour === '00' && lookup.minute === '00' && lookup.second === '00';
  } catch (_) {
    const moscowDate = new Date(date.getTime() + 3 * 60 * 60 * 1000);
    return moscowDate.getUTCHours() === 0
      && moscowDate.getUTCMinutes() === 0
      && moscowDate.getUTCSeconds() === 0;
  }
}

function formatOzonActionDate(value, options = {}) {
  if (!value) return '--';
  const displayValue = options.exclusiveEnd && isOzonMoscowStartOfDay(value)
    ? new Date(new Date(value).getTime() - 1000)
    : value;
  const parts = getOzonMoscowDateParts(displayValue);
  if (!parts || typeof parts === 'string') return parts || String(value);
  const year = options.fullYear ? parts.year : parts.year.slice(-2);
  return `${parts.day}.${parts.month}.${year}`;
}

function formatOzonActionFullDate(value) {
  return formatOzonActionDate(value, { fullYear: true });
}

function formatOzonActionEndDate(value, options = {}) {
  return formatOzonActionDate(value, { ...options, exclusiveEnd: true });
}

function formatOzonActionFullEndDate(value) {
  return formatOzonActionEndDate(value, { fullYear: true });
}

const OZON_ACTION_MONTH_NAMES = [
  'January',
  'February',
  'March',
  'April',
  'May',
  'June',
  'July',
  'August',
  'September',
  'October',
  'November',
  'December'
];

function formatOzonActionLongDate(value) {
  if (!value) return '';
  const parts = getOzonMoscowDateParts(value);
  if (!parts || typeof parts === 'string') return parts || String(value);
  const monthIndex = Math.max(0, Math.min(11, Number(parts.month || 1) - 1));
  return `${Number(parts.day)} ${OZON_ACTION_MONTH_NAMES[monthIndex]} ${parts.year}`;
}

function getOzonActionTypeLabel(action = {}) {
  const raw = normalizeOzonText(action.actionType || action.type || '');
  if (/marketplace_multi_level_discount_on_amount/i.test(raw)) return '弹性提升';
  if (/stock_discount/i.test(raw)) return '库存折扣';
  if (/stock|inventory/i.test(raw)) return '库存折扣';
  if (/discount|sale|price/i.test(raw)) return '折扣促销';
  if (/boost|multi|amount|super/i.test(raw)) return '弹性提升';
  if (/voucher|coupon/i.test(raw)) return '优惠券';
  return raw || '促销活动';
}

function getOzonActionJoinText(action = {}) {
  const joined = Number(action.participantsCount || action.productCount || 0);
  if (joined > 0) return `${joined.toLocaleString()}件商品参加`;
  return '我们还没有添加商品';
}

function getOzonActionAutoAddText(action = {}) {
  const dates = Array.isArray(action.autoAddDates) ? action.autoAddDates : [];
  const formatted = dates.map(formatOzonActionLongDate).filter(Boolean);
  if (formatted.length) return `自动添加日期：${formatted.join('、')}`;
  return '';
}

function getOzonActionFreezeText(action = {}) {
  if (!action.freezeDate) return '';
  const formatted = formatOzonActionDate(action.freezeDate);
  return formatted && formatted !== '--' ? `从 ${formatted}` : '';
}

function findOzonMarketingActionById(actionId = '', accountId = '') {
  const data = ozonState.marketing || {};
  const actions = Array.isArray(data.actions) ? data.actions : [];
  return actions.find(action =>
    String(action.id || '') === String(actionId || '')
    && (!accountId || String(action.accountId || '') === String(accountId || ''))
  ) || null;
}

function getOzonMarketingActionCounts(action = {}) {
  const allProducts = Array.isArray(ozonState.marketing?.products) ? ozonState.marketing.products : [];
  const related = allProducts.filter(product =>
    String(product.actionId || '') === String(action.id || '')
    && (!action.accountId || String(product.accountId || '') === String(action.accountId || ''))
  );
  const joinedFromProducts = related.filter(product => product.source === 'joined').length;
  const candidateFromProducts = related.filter(product => product.source === 'candidate').length;
  const current = ozonState.marketingProducts || {};
  const sameAction = String(current.actionId || '') === String(action.id || '')
    && (!action.accountId || String(current.account?.id || action.accountId || '') === String(action.accountId || ''));
  const hasCurrentJoinedTotal = sameAction
    && normalizeOzonMarketingProductSource(current.source || '') === 'joined'
    && current.total !== undefined;
  return {
    joined: hasCurrentJoinedTotal
      ? Number(current.total || 0)
      : Number(action.participantsCount || action.productCount || joinedFromProducts || 0),
    candidates: Number(action.candidatesCount || candidateFromProducts || 0)
  };
}

function getOzonMarketingAutoAddCount(action = {}, dateValue = '') {
  const dateKey = normalizeOzonPromoDateKey(dateValue);
  if (!dateKey) return 0;
  const current = ozonState.marketingProducts || {};
  const currentDateKey = normalizeOzonPromoDateKey(ozonState.marketingDetailAutoAddDate || '');
  if (
    String(current.actionId || '') === String(action.id || '')
    && normalizeOzonMarketingProductSource(current.source || '') === 'auto'
    && currentDateKey === dateKey
    && current.total !== undefined
  ) {
    return Number(current.total || 0);
  }
  const counts = action.autoAddCounts && typeof action.autoAddCounts === 'object' ? action.autoAddCounts : {};
  return Number(counts[dateKey] || 0);
}

function updateOzonMarketingSelectedActionProductCount(source = '', total = 0) {
  const action = ozonState.marketingSelectedAction || {};
  if (!action.id) return;
  const normalizedSource = normalizeOzonMarketingProductSource(source || '');
  const nextTotal = Math.max(0, Number(total || 0));
  const nextAction = { ...action };
  if (normalizedSource === 'joined') {
    nextAction.participantsCount = nextTotal;
    nextAction.productCount = nextTotal;
  } else if (normalizedSource === 'auto') {
    const dateKey = normalizeOzonPromoDateKey(ozonState.marketingDetailAutoAddDate || '');
    if (dateKey) {
      nextAction.autoAddCounts = {
        ...(nextAction.autoAddCounts || {}),
        [dateKey]: nextTotal
      };
    }
  }
  ozonState.marketingSelectedAction = nextAction;
  const actions = Array.isArray(ozonState.marketing?.actions) ? ozonState.marketing.actions : [];
  if (actions.length) {
    ozonState.marketing = {
      ...ozonState.marketing,
      actions: actions.map(item => (
        String(item.id || '') === String(nextAction.id || '')
        && (!nextAction.accountId || String(item.accountId || '') === String(nextAction.accountId || ''))
          ? { ...item, ...nextAction }
          : item
      ))
    };
  }
}

function getOzonMarketingAutoAddDates(action = {}) {
  const rawDates = Array.isArray(action.autoAddDates) ? action.autoAddDates : [];
  return [...new Set(rawDates.map(item => normalizeOzonText(item)).filter(Boolean))]
    .sort((a, b) => {
      const left = new Date(a).getTime();
      const right = new Date(b).getTime();
      if (Number.isFinite(left) && Number.isFinite(right)) return left - right;
      return String(a).localeCompare(String(b));
    });
}

function normalizeOzonPromoDateKey(value = '') {
  const text = normalizeOzonText(value);
  if (!text) return '';
  const parsed = new Date(text).getTime();
  if (Number.isFinite(parsed)) return new Date(parsed).toISOString().slice(0, 10);
  const dotted = text.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})/);
  if (dotted) return `${dotted[3]}-${String(dotted[2]).padStart(2, '0')}-${String(dotted[1]).padStart(2, '0')}`;
  const slashed = text.match(/^(\d{4})[\/.-](\d{1,2})[\/.-](\d{1,2})/);
  if (slashed) return `${slashed[1]}-${String(slashed[2]).padStart(2, '0')}-${String(slashed[3]).padStart(2, '0')}`;
  return text.slice(0, 10);
}

function getOzonPromoProductAutoAddDateKeys(product = {}) {
  const raw = product.raw || product.rawData || {};
  const keys = [
    product.autoAddDate,
    product.auto_add_date,
    product.participatingFrom,
    product.participating_from,
    product.dateStart,
    product.date_start,
    product.addedAt,
    product.added_at,
    product.createdAt,
    product.created_at,
    raw.auto_add_date,
    raw.autoAddDate,
    raw.participating_from,
    raw.participatingFrom,
    raw.date_start,
    raw.dateStart,
    raw.added_at,
    raw.addedAt,
    raw.created_at,
    raw.createdAt
  ];
  const rawDates = Array.isArray(raw.auto_add_dates) ? raw.auto_add_dates : (Array.isArray(raw.autoAddDates) ? raw.autoAddDates : []);
  keys.push(...rawDates);
  return [...new Set(keys.map(item => normalizeOzonPromoDateKey(item)).filter(Boolean))];
}

function matchesOzonPromoProductAutoAddDate(product = {}, dateValue = '') {
  const dateKey = normalizeOzonPromoDateKey(dateValue);
  if (!dateKey) return false;
  return getOzonPromoProductAutoAddDateKeys(product).includes(dateKey);
}

function getOzonPromoMoney(value, fallback = '--', currencyCode = 'RUB') {
  const num = Number(value || 0);
  return num > 0 ? formatOzonAmount(num, currencyCode || 'RUB') : fallback;
}

function getOzonPromoProductImage(product = {}) {
  const raw = product.raw || product.rawData || {};
  return normalizeOzonImageUrl(product.image || product.imageUrl || product.primaryImage || product.mainImage || raw.xqwProductCache?.image || '')
    || pickOzonImage(product)
    || pickOzonImage(raw)
    || pickOzonImage(product.rawData || {});
}

function getOzonPromoProductRowId(product = {}) {
  return String(product.productId || product.sku || product.offerId || '').trim();
}

function normalizeOzonMarketingProductSource(source = 'joined') {
  const value = normalizeOzonText(source).toLowerCase();
  if (value === 'candidate') return 'candidate';
  if (value === 'auto') return 'auto';
  return 'joined';
}

function roundOzonPromoNumber(value, digits = 2) {
  const num = Number(value);
  if (!Number.isFinite(num)) return 0;
  const factor = 10 ** digits;
  return Math.round(num * factor) / factor;
}

function getOzonPromoFirstPositiveNumber(...values) {
  for (const value of values) {
    const num = Number(value);
    if (Number.isFinite(num) && num > 0) return num;
  }
  return 0;
}

function formatOzonPromoInputNumber(value) {
  const num = Number(value);
  if (!Number.isFinite(num)) return '';
  return String(roundOzonPromoNumber(num, 2));
}

function getOzonPromoDiscountFromPrice(basePrice, actionPrice) {
  const base = Number(basePrice || 0);
  const price = Number(actionPrice || 0);
  if (!(base > 0) || !(price > 0)) return 0;
  return roundOzonPromoNumber((1 - price / base) * 100, 2);
}

function getOzonPromoSuggestedActionPrice(product = {}) {
  const raw = product.raw || product.rawData || {};
  return getOzonPromoFirstPositiveNumber(
    product.actionPrice,
    raw.action_price,
    raw.actionPrice,
    raw.discount_price,
    raw.discountPrice,
    product.suggestedActionPrice,
    raw.suggested_action_price,
    raw.suggestedActionPrice,
    product.maxActionPrice,
    raw.max_action_price,
    raw.maxActionPrice,
    raw.max_price,
    raw.maxPrice,
    product.minActionPrice,
    raw.min_action_price,
    raw.minActionPrice,
    raw.min_price,
    raw.minPrice
  );
}

function getOzonPromoSuggestedDiscount(product = {}) {
  const currentPrice = Number(product.currentPrice || product.price || product.raw?.price || product.rawData?.price || 0);
  const actionPrice = getOzonPromoSuggestedActionPrice(product);
  if (!(currentPrice > 0) || !(actionPrice > 0)) return null;
  return getOzonPromoDiscountFromPrice(currentPrice, actionPrice);
}

function getOzonPromoSubmissionStock(product = {}) {
  const raw = product.raw || product.rawData || {};
  const candidates = [
    product.stock,
    raw.stock,
    raw.available_stock,
    raw.availableStock,
    product.minStock,
    raw.min_stock,
    raw.minStock,
    product.suggestionCount,
    raw.recommended_stock,
    raw.recommendedStock,
    raw.suggested_stock,
    raw.suggestedStock
  ];
  for (const value of candidates) {
    if (value === undefined || value === null || value === '') continue;
    const num = Number(value);
    if (Number.isFinite(num) && num >= 0) return num;
  }
  return 0;
}

function getOzonPromoDiscountAmountText(basePrice, actionPrice, currencyCode = 'RUB') {
  const base = Number(basePrice || 0);
  const price = Number(actionPrice || 0);
  const amount = Math.max(0, base - price);
  return amount > 0 ? getOzonPromoMoney(amount, '--', currencyCode) : '--';
}

function getOzonMarketingCandidateEdit(rowId) {
  const edits = ozonState.marketingCandidatePriceEdits || {};
  return edits[String(rowId || '')] || null;
}

function setOzonMarketingCandidateEdit(rowId, edit = {}) {
  const key = String(rowId || '').trim();
  if (!key) return;
  if (!ozonState.marketingCandidatePriceEdits) ozonState.marketingCandidatePriceEdits = {};
  ozonState.marketingCandidatePriceEdits[key] = {
    ...(ozonState.marketingCandidatePriceEdits[key] || {}),
    ...edit
  };
}

function clearOzonMarketingCandidateEdit(rowId) {
  const key = String(rowId || '').trim();
  if (!key || !ozonState.marketingCandidatePriceEdits) return;
  delete ozonState.marketingCandidatePriceEdits[key];
}

function applyOzonMarketingCandidateBulkDiscount(discountValue, modal = document.getElementById('ozonMarketingActionModal')) {
  const discount = Math.max(0, Math.min(99.99, Number(discountValue || 0)));
  if (!Number.isFinite(discount)) throw new Error('请填写有效的促销比例');
  const selectedSet = getOzonMarketingCandidateSelectionSet();
  const products = (Array.isArray(ozonState.marketingCandidateProducts?.products) ? ozonState.marketingCandidateProducts.products : [])
    .map(product => normalizeOzonPromoProductRow(product));
  let applied = 0;
  let skipped = 0;
  products.forEach((product) => {
    const rowId = getOzonPromoProductRowId(product);
    if (!rowId || !selectedSet.has(rowId)) return;
    const basePrice = Number(product.currentPrice || 0);
    if (!(basePrice > 0)) {
      skipped += 1;
      return;
    }
    const currencyCode = product.currencyCode || 'RUB';
    const actionPrice = roundOzonPromoNumber(basePrice * (1 - discount / 100), 2);
    setOzonMarketingCandidateEdit(rowId, { discount, actionPrice });
    const discountInput = modal?.querySelector?.(`[data-ozon-promo-discount="${CSS.escape(rowId)}"]`);
    if (discountInput) discountInput.value = formatOzonPromoInputNumber(discount);
    const priceInput = modal?.querySelector?.(`[data-ozon-promo-action-price="${CSS.escape(rowId)}"]`);
    if (priceInput) priceInput.value = actionPrice ? formatOzonPromoInputNumber(actionPrice) : '';
    const amountEl = modal?.querySelector?.(`[data-ozon-promo-discount-amount="${CSS.escape(rowId)}"]`);
    if (amountEl) amountEl.textContent = getOzonPromoDiscountAmountText(basePrice, actionPrice, currencyCode);
    applied += 1;
  });
  if (!applied) {
    throw new Error(skipped ? '已勾选链接缺少您的价格，无法批量计算促销比例' : '请先勾选要批量填写的链接');
  }
  setOzonResult(`已为 ${applied} 条勾选链接批量填写 ${formatOzonPromoInputNumber(discount)}%${skipped ? `，${skipped} 条因缺少您的价格未处理` : ''}`, skipped ? 'warn' : 'success');
  return { applied, skipped };
}

function applyOzonMarketingCandidateEdit(product = {}) {
  const rowId = getOzonPromoProductRowId(product);
  const edit = getOzonMarketingCandidateEdit(rowId);
  if (!edit || !(Number(edit.actionPrice) > 0)) return product;
  const actionPrice = roundOzonPromoNumber(edit.actionPrice, 2);
  const discount = getOzonPromoDiscountFromPrice(product.currentPrice, actionPrice);
  return {
    ...product,
    actionPrice,
    suggestedActionPrice: actionPrice,
    suggestedDiscount: discount,
    hasSuggestedActionPrice: true,
    hasSuggestedDiscount: true,
    discount
  };
}

function normalizeOzonPromoProductRow(product = {}) {
  const raw = product.raw || product.rawData || {};
  const cached = raw.xqwProductCache || {};
  const currentPrice = Number(product.currentPrice || raw.price || raw.current_price || raw.currentPrice || raw.old_price || 0);
  const actionPrice = Number(product.actionPrice || raw.action_price || raw.actionPrice || raw.discount_price || raw.discountPrice || 0);
  const minPrice = Number(product.minActionPrice || raw.min_action_price || raw.minActionPrice || raw.min_price || raw.minPrice || 0);
  const maxPrice = Number(product.maxActionPrice || raw.max_action_price || raw.maxActionPrice || raw.max_price || raw.maxPrice || 0);
  const explicitDiscount = Number(product.discount || raw.discount || raw.discount_percent || raw.discountPercent || 0);
  const suggestedActionPrice = getOzonPromoFirstPositiveNumber(actionPrice, maxPrice, minPrice);
  const suggestedDiscount = currentPrice > 0 && suggestedActionPrice > 0
    ? getOzonPromoDiscountFromPrice(currentPrice, suggestedActionPrice)
    : null;
  const discount = Number.isFinite(explicitDiscount) && explicitDiscount > 0
    ? roundOzonPromoNumber(explicitDiscount, 2)
    : (suggestedDiscount === null ? 0 : suggestedDiscount);
  const boostSource = [
    product.currentBoost,
    product.minBoost,
    raw.current_boost,
    raw.currentBoost,
    raw.min_boost,
    raw.minBoost
  ].find(value => value !== undefined && value !== null && value !== '');
  const boost = boostSource === undefined ? null : Number(boostSource);
  const stock = Number(product.stock || raw.stock || raw.available_stock || raw.availableStock || raw.quantity || 0);
  const minStock = Number(product.minStock || raw.min_stock || raw.minStock || 0);
  const warehouseStock = Number(raw.warehouse_stock || raw.warehouseStock || raw.fbo_stock || raw.fbs_stock || stock || 0);
  const category = normalizeOzonText(product.category || cached.category || raw.category_name || raw.categoryName || raw.category || raw.type_name || raw.description_category_name || raw.descriptionCategoryName || '');
  const offerId = normalizeOzonText(product.offerId || cached.offerId || raw.offer_id || raw.offerId || raw.marketing_price?.offer_id || raw.marketingPrice?.offerId || '');
  const sku = normalizeOzonText(product.sku || cached.sku || raw.sku || raw.sku_id || raw.skuId || raw.marketing_price?.sku || raw.marketingPrice?.sku || '');
  const productId = normalizeOzonText(product.productId || cached.productId || raw.product_id || raw.productId || raw.id || raw.marketing_price?.product_id || raw.marketingPrice?.productId || '');
  const currencyCode = normalizeOzonCurrencyCode(product.currencyCode || product.currency || cached.currencyCode || cached.currency || raw.currency_code || raw.currencyCode || raw.currency || 'RUB');
  return {
    ...product,
    raw,
    image: getOzonPromoProductImage(product),
    productId,
    sku,
    offerId,
    name: normalizeOzonText(product.name || cached.name || raw.name || raw.product_name || raw.productName || raw.title || raw.marketing_price?.name || raw.marketing_price?.product_name || raw.marketingPrice?.name || raw.marketingPrice?.productName || ''),
    category,
    currentPrice,
    actionPrice,
    currencyCode,
    minActionPrice: minPrice,
    maxActionPrice: maxPrice,
    minPrice,
    maxPrice,
    suggestedActionPrice,
    suggestedDiscount,
    hasSuggestedActionPrice: suggestedActionPrice > 0,
    hasSuggestedDiscount: suggestedDiscount !== null,
    discount,
    boost: Number.isFinite(boost) ? boost : null,
    stock,
    minStock,
    warehouseStock,
    addMode: normalizeOzonText(product.addMode || raw.add_mode || raw.addMode || ''),
    otherActionsCount: getOzonPromoOfficialRawNumber({ raw }, ['other_actions_count', 'otherActionsCount', 'actions_count', 'actionsCount']),
    salesLimit: normalizeOzonText(getOzonPromoOfficialRawValue({ raw }, ['sales_limit', 'salesLimit', 'limit']) || ''),
    suggestionCount: getOzonPromoOfficialRawNumber({ raw }, ['recommended_stock', 'recommendedStock', 'suggested_stock', 'suggestedStock'])
  };
}

function isOzonPromoProductMissingDisplayInfo(product = {}) {
  const normalized = normalizeOzonPromoProductRow(product);
  return !normalizeOzonText(normalized.image)
    || !normalizeOzonText(normalized.offerId)
    || !normalizeOzonText(normalized.name);
}

function shouldHydrateOzonMarketingProducts(data = {}) {
  const products = Array.isArray(data.products) ? data.products : [];
  return products.some(isOzonPromoProductMissingDisplayInfo);
}

function getOzonMarketingProductHydrateKey(actionId = '', source = '', page = 1, limit = 50, autoAddDate = '') {
  return [
    normalizeOzonText(ozonState.marketingAccountId || ''),
    normalizeOzonText(actionId),
    normalizeOzonText(source),
    String(Math.max(1, Number(page || 1))),
    String(Math.max(1, Number(limit || 50))),
    normalizeOzonText(autoAddDate || '')
  ].join(':');
}

function matchesOzonPromoProductKeyword(product = {}, keyword = '') {
  const key = normalizeOzonText(keyword).toLowerCase();
  if (!key) return true;
  return [
    product.name,
    product.category,
    product.productId,
    product.sku,
    product.offerId,
    product.accountTitle
  ].some(value => normalizeOzonText(value).toLowerCase().includes(key));
}

function renderOzonPromoPageNumbers(page = 1, totalPages = 1, pageAttr = 'data-ozon-candidate-page') {
  const current = Math.max(1, Number(page || 1));
  const total = Math.max(1, Number(totalPages || 1));
  const pages = [];
  if (total <= 9) {
    for (let index = 1; index <= total; index += 1) pages.push(index);
  } else {
    const set = new Set([1, 2, total - 1, total, current - 1, current, current + 1]);
    Array.from(set)
      .filter(value => value >= 1 && value <= total)
      .sort((a, b) => a - b)
      .forEach(value => {
        if (pages.length && value - pages[pages.length - 1] > 1) pages.push('...');
        pages.push(value);
      });
  }
  return `<div class="ozon-promo-page-numbers">${pages.map(value => {
    if (value === '...') return '<span class="ozon-promo-page-ellipsis">...</span>';
    const active = Number(value) === current;
    return `<button class="ozon-promo-page-number ${active ? 'is-active' : ''}" type="button" ${pageAttr}="${escapeHtml(value)}" ${active ? 'disabled' : ''}>${escapeHtml(value)}</button>`;
  }).join('')}</div>`;
}

function getOzonPromoRawValue(product = {}, keys = []) {
  const raw = product.raw || product.rawData || {};
  for (const key of keys) {
    const value = product[key] ?? raw[key];
    if (value !== undefined && value !== null && value !== '') return value;
  }
  return '';
}

function getOzonPromoRawNumber(product = {}, keys = []) {
  const value = getOzonPromoRawValue(product, keys);
  const num = Number(value);
  return Number.isFinite(num) ? num : null;
}

function getOzonPromoOfficialRawValue(product = {}, keys = []) {
  const raw = product.raw || product.rawData || {};
  for (const key of keys) {
    if (Object.prototype.hasOwnProperty.call(raw, key)) {
      const value = raw[key];
      if (value !== undefined && value !== null && value !== '') return value;
    }
  }
  return null;
}

function getOzonPromoOfficialRawNumber(product = {}, keys = []) {
  const value = getOzonPromoOfficialRawValue(product, keys);
  if (value === null) return null;
  const num = Number(value);
  return Number.isFinite(num) ? num : null;
}

function getOzonPromoMetricText(product = {}, keys = [], fallback = '--') {
  const num = getOzonPromoRawNumber(product, keys);
  if (num === null) return fallback;
  return num.toLocaleString();
}

function getOzonPromoPercentDeltaText(product = {}, keys = []) {
  const value = getOzonPromoRawNumber(product, keys);
  if (value === null) return '';
  const sign = value > 0 ? '+' : '';
  return `${sign}${roundOzonPromoNumber(value, 2)}%`;
}

function renderOzonPromoProductInfoMain(product = {}, options = {}) {
  const offerText = product.offerId || '--';
  const skuText = product.sku || product.productId || '--';
  const missingTitle = !normalizeOzonText(product.name);
  const missingImage = !normalizeOzonText(product.image);
  const displayInfoNote = options.showDisplayInfoNote && (missingTitle || missingImage)
    ? `<div class="ozon-promo-meta ozon-promo-detail-status">${missingTitle && missingImage ? '官方候选接口未返回标题和图片' : (missingTitle ? '官方候选接口未返回标题' : '官方候选接口未返回图片')}${options.hydrating ? '，正在补齐...' : '，可刷新商品同步后再补齐'}</div>`
    : '';
  return `<div class="ozon-promo-product-info-main">
    <div class="ozon-promo-product-name ozon-promo-detail-title">${escapeHtml(product.name || '--')}</div>
    <div class="ozon-promo-meta ozon-promo-offer-meta">货号: ${escapeHtml(offerText)}</div>
    <div class="ozon-promo-meta">SKU: ${escapeHtml(skuText)}</div>
    ${displayInfoNote}
  </div>`;
}

function renderOzonPromoProductPhotoCell(product = {}, imageClass = 'ozon-promo-photo') {
  return `<td class="ozon-promo-photo-cell">${renderOzonImageThumb(product.image, imageClass)}</td>`;
}

function renderOzonPromoProductInfoCell(product = {}, imageClass = 'ozon-promo-photo') {
  return `<td class="ozon-promo-product-info-cell">
    <div class="ozon-promo-product-info">
      ${renderOzonImageThumb(product.image, imageClass)}
      ${renderOzonPromoProductInfoMain(product)}
    </div>
  </td>`;
}

function renderOzonPromoProductTextCell(product = {}, options = {}) {
  return `<td class="ozon-promo-product-info-cell">
    <div class="ozon-promo-product-info is-text-only">
      ${renderOzonPromoProductInfoMain(product, options)}
    </div>
  </td>`;
}

function renderOzonPromoDetailProductTable(products = []) {
  const selectedSet = getOzonMarketingDetailSelectionSet();
  const rows = products.map(item => normalizeOzonPromoProductRow(item)).map(product => {
    const raw = product.raw || {};
    const rowId = getOzonPromoProductRowId(product);
    const productId = String(product.productId || '').trim();
    const selectable = Boolean(productId);
    const checked = Boolean(rowId && selectedSet.has(rowId));
    const actionPrice = getOzonPromoSuggestedActionPrice(product);
    const suggestedDiscount = getOzonPromoSuggestedDiscount(product);
    const discountValue = suggestedDiscount !== null ? suggestedDiscount : Number(product.discount || 0);
    const discountText = suggestedDiscount !== null || discountValue > 0 ? `${Number(discountValue).toFixed(discountValue % 1 ? 2 : 0)}%` : '--';
    const discountAmountText = getOzonPromoDiscountAmountText(product.currentPrice, actionPrice, product.currencyCode || 'RUB');
    const currentPromo = product.actionPrice || product.currentPrice || 0;
    const boost = Number.isFinite(Number(product.boost)) ? Math.max(0, Math.min(100, Number(product.boost || 0))) : null;
    const officialStock = getOzonPromoOfficialRawNumber(product, ['stock']);
    const officialMinStock = getOzonPromoOfficialRawNumber(product, ['min_stock']);
    const stockText = officialStock === null ? '--' : officialStock.toLocaleString();
    const minStockText = officialMinStock === null ? '' : `最低 ${officialMinStock.toLocaleString()}`;
    const hasActionWarning = Boolean(product.alertMaxActionPriceFailed || raw.alert_max_action_price_failed || raw.alertMaxActionPriceFailed);
    return `<tr data-ozon-detail-row="${escapeHtml(rowId)}" class="${checked ? 'is-selected' : ''}">
      <td data-ozon-detail-check-row><input type="checkbox" data-ozon-detail-check="${escapeHtml(rowId)}" ${checked ? 'checked' : ''} ${selectable ? '' : 'disabled'} aria-label="选择商品"></td>
      ${renderOzonPromoProductPhotoCell(product, 'ozon-promo-photo ozon-promo-detail-photo')}
      ${renderOzonPromoProductTextCell(product)}
      <td><div class="ozon-promo-money">${escapeHtml(getOzonPromoMoney(product.currentPrice, '--', product.currencyCode))}</div><div class="ozon-promo-sub">${escapeHtml(getOzonPromoMoney(product.minPrice, '--', product.currencyCode))}</div></td>
      <td><div class="ozon-promo-money">${escapeHtml(getOzonPromoMoney(currentPromo, '--', product.currencyCode))}</div><div class="ozon-promo-sub">${escapeHtml(discountText)}</div></td>
      <td><div class="${Number(discountValue || 0) >= 10 ? 'ozon-promo-blue' : ''}">${escapeHtml(discountText)}</div><div class="ozon-promo-sub">${escapeHtml(discountAmountText)}</div></td>
      <td><div class="ozon-promo-money ${hasActionWarning ? 'ozon-trend-down' : ''}">${escapeHtml(getOzonPromoMoney(actionPrice, '--', product.currencyCode))}</div><div class="ozon-promo-sub">我们建议 ${escapeHtml(getOzonPromoMoney(product.minActionPrice || product.maxActionPrice || actionPrice || 0, '--', product.currencyCode))}</div></td>
      <td>${boost !== null ? `<div class="ozon-promo-progress"><span><i style="--w:${escapeHtml(boost)}%"></i></span><div class="ozon-promo-sub">${escapeHtml(boost)}%</div></div>` : '<span class="ozon-promo-sub">--</span>'}</td>
      <td><div class="ozon-promo-money">${escapeHtml(getOzonPromoMoney(product.maxActionPrice || actionPrice || 0, '--', product.currencyCode))}</div><div class="ozon-promo-sub">${escapeHtml(getOzonPromoMoney(product.minActionPrice || actionPrice || 0, '--', product.currencyCode))}</div></td>
      <td><div class="ozon-promo-money">${escapeHtml(stockText)}</div>${minStockText ? `<div class="ozon-promo-sub">${escapeHtml(minStockText)}</div>` : ''}</td>
      <td><span class="ozon-promo-sub">--</span></td>
      <td><button class="ozon-promo-delete" type="button" data-ozon-deactivate-product="${escapeHtml(product.productId || '')}" ${selectable ? '' : 'disabled'}>移出</button></td>
    </tr>`;
  }).join('');
  const selectableCount = products.map(item => normalizeOzonPromoProductRow(item)).filter(product => product.productId).length;
  const selectedVisibleCount = products.map(item => normalizeOzonPromoProductRow(item)).filter(product => {
    const rowId = getOzonPromoProductRowId(product);
    return product.productId && rowId && selectedSet.has(rowId);
  }).length;
  return `<div class="ozon-promo-table-wrap ozon-promo-detail-table-wrap">
    <table class="ozon-promo-table ozon-promo-detail-table">
      <thead><tr>
        <th><input type="checkbox" data-ozon-detail-check-all ${selectableCount && selectedVisibleCount === selectableCount ? 'checked' : ''} ${selectableCount ? '' : 'disabled'} aria-label="选择当前页商品"></th>
        <th>主图</th>
        <th>商品信息</th>
        <th>您的价格<br>最低价格</th>
        <th>当前促销价<br>基于您的价格的折扣</th>
        <th>本次促销活动在定价基础上的折扣</th>
        <th>该促销活动的价格</th>
        <th>促销提升</th>
        <th>提升价格<br>最小 / 最大</th>
        <th>促销活动中的商品数量</th>
        <th>还需要销售</th>
        <th></th>
      </tr></thead>
      <tbody>${rows || `<tr><td colspan="12"><div class="ozon-empty">暂无商品数据</div></td></tr>`}</tbody>
    </table>
  </div>`;
}

function renderOzonPromoProductTable(products = [], options = {}) {
  const mode = options.mode || 'detail';
  if (mode === 'detail') return renderOzonPromoDetailProductTable(products);
  const selectable = mode === 'candidate';
  const selectedSet = getOzonMarketingCandidateSelectionSet();
  const rows = products.map(item => normalizeOzonPromoProductRow(item)).map(product => {
    const rowId = getOzonPromoProductRowId(product);
    const checked = selectedSet.has(rowId);
    const displayProduct = selectable ? applyOzonMarketingCandidateEdit(product) : product;
    const suggestedActionPrice = getOzonPromoSuggestedActionPrice(displayProduct);
    const suggestedDiscount = getOzonPromoSuggestedDiscount(displayProduct);
    const hasSuggestedDiscount = suggestedDiscount !== null;
    const hasBoost = Number.isFinite(Number(displayProduct.boost));
    const boost = hasBoost ? Math.max(0, Math.min(100, Number(displayProduct.boost || 0))) : 0;
    const progressTone = boost >= 50 ? 'is-good' : boost >= 25 ? 'is-warn' : '';
    const discountValue = hasSuggestedDiscount ? suggestedDiscount : Number(displayProduct.discount || 0);
    const discountText = hasSuggestedDiscount || discountValue > 0 ? `${Number(discountValue).toFixed(discountValue % 1 ? 2 : 0)}%` : '--';
    const actionPrice = suggestedActionPrice || 0;
    const currentPromo = displayProduct.actionPrice || displayProduct.currentPrice || 0;
    const currencyCode = displayProduct.currencyCode || 'RUB';
    const editableDiscount = hasSuggestedDiscount || Number(discountValue || 0) > 0 ? formatOzonPromoInputNumber(discountValue) : '';
    const editableActionPrice = Number(actionPrice || 0) > 0 ? formatOzonPromoInputNumber(actionPrice) : '';
    const discountAmountText = getOzonPromoDiscountAmountText(displayProduct.currentPrice, actionPrice, currencyCode);
    const officialStock = getOzonPromoOfficialRawNumber(displayProduct, ['stock']);
    const officialMinStock = getOzonPromoOfficialRawNumber(displayProduct, ['min_stock']);
    const stockText = officialStock === null ? '--' : officialStock.toLocaleString();
    const minStockText = officialMinStock === null ? '' : `最低 ${officialMinStock.toLocaleString()}`;
    const rowText = [
      displayProduct.name,
      displayProduct.category,
      displayProduct.productId,
      displayProduct.sku,
      displayProduct.offerId,
      displayProduct.accountTitle
    ].map(value => normalizeOzonText(value).toLowerCase()).join(' ');
    return `<tr class="${checked ? 'is-selected' : ''}" data-ozon-candidate-row="${escapeHtml(rowId)}" data-ozon-promo-row-text="${escapeHtml(rowText)}">
      <td class="ozon-promo-check-cell" ${selectable && rowId ? `data-ozon-check-row="${escapeHtml(rowId)}"` : ''}>${selectable && rowId ? `<input type="checkbox" aria-label="选择商品" data-ozon-candidate-check="${escapeHtml(rowId)}" ${checked ? 'checked' : ''}>` : `<input type="checkbox" disabled>`}</td>
      ${renderOzonPromoProductPhotoCell(displayProduct, 'ozon-promo-photo')}
      ${renderOzonPromoProductTextCell(displayProduct, {
        showDisplayInfoNote: selectable,
        hydrating: Boolean(selectable && ozonState.marketingCandidateProducts?.hydrating)
      })}
      <td><div class="ozon-promo-money">${escapeHtml(getOzonPromoMoney(displayProduct.currentPrice, '--', currencyCode))}</div><div class="ozon-promo-sub">最低价 ${escapeHtml(getOzonPromoMoney(displayProduct.minPrice, '--', currencyCode))}</div></td>
      <td><div class="ozon-promo-money">${escapeHtml(getOzonPromoMoney(currentPromo, '--', currencyCode))}</div><div class="ozon-promo-sub">基于您的价格折扣 ${escapeHtml(discountText)}</div></td>
      <td>${selectable ? `<span class="ozon-promo-percent-field"><input class="ozon-promo-edit-input" type="number" min="0" max="99.99" step="0.01" value="${escapeHtml(editableDiscount)}" data-ozon-promo-discount="${escapeHtml(rowId)}" data-current-price="${escapeHtml(displayProduct.currentPrice || 0)}" data-currency-code="${escapeHtml(currencyCode)}"><span class="ozon-promo-percent-symbol">%</span></span>` : `<div class="${Number(discountValue || 0) >= 10 ? 'ozon-promo-blue' : ''}">${escapeHtml(discountText)}</div>`}<div class="ozon-promo-sub" data-ozon-promo-discount-amount="${escapeHtml(rowId)}" data-currency-code="${escapeHtml(currencyCode)}">${escapeHtml(discountAmountText)}</div></td>
      <td>${selectable ? `<input class="ozon-promo-edit-input" type="number" min="0" step="0.01" value="${escapeHtml(editableActionPrice)}" data-ozon-promo-action-price="${escapeHtml(rowId)}" data-current-price="${escapeHtml(displayProduct.currentPrice || 0)}" data-currency-code="${escapeHtml(currencyCode)}">` : `<div class="ozon-promo-money">${escapeHtml(getOzonPromoMoney(actionPrice, '--', currencyCode))}</div>`}${displayProduct.alertMaxActionPriceFailed ? '<div class="ozon-promo-sub ozon-trend-down">低于最低值</div>' : ''}</td>
      <td>${hasBoost ? `<div class="ozon-promo-progress ${progressTone}"><span><i style="--w:${escapeHtml(boost)}%"></i></span><div class="ozon-promo-sub">${escapeHtml(boost)}%</div></div>` : '<span class="ozon-promo-sub">--</span>'}</td>
      <td><div class="ozon-promo-money">${escapeHtml(getOzonPromoMoney(displayProduct.maxActionPrice || actionPrice || 0, '--', currencyCode))}</div><div class="ozon-promo-sub">最小 ${escapeHtml(getOzonPromoMoney(displayProduct.minActionPrice || actionPrice || 0, '--', currencyCode))}</div></td>
      <td><div class="ozon-promo-money">${escapeHtml(stockText)}</div>${minStockText ? `<div class="ozon-promo-sub">${escapeHtml(minStockText)}</div>` : ''}</td>
    </tr>`;
  }).join('');
  const bulkDiscountHead = selectable
    ? `<div class="ozon-promo-bulk-discount">
        <span class="ozon-promo-bulk-discount-title"><span>本次促销活动在您</span><span>定价基础上的折扣</span></span>
        <div class="ozon-promo-bulk-discount-actions">
          <label><input class="ozon-promo-bulk-discount-input" type="number" min="0" max="99.99" step="0.01" placeholder="比例" data-ozon-bulk-candidate-discount><b>%</b></label>
          <button class="btn btn-default btn-xs" type="button" data-ozon-apply-bulk-candidate-discount>批量填写</button>
        </div>
      </div>`
    : '本次促销活动在您定价基础上的折扣';
  return `<div class="ozon-promo-table-wrap ${mode === 'candidate' ? 'ozon-promo-modal-table-wrap' : ''}">
    <table class="ozon-promo-table">
      <thead><tr>
        <th><input type="checkbox" ${selectable ? 'data-ozon-candidate-check-all' : 'disabled'}></th>
        <th>主图</th>
        <th>商品信息</th>
        <th>您的价格<br>最低价格</th>
        <th>当前促销价<br>基于您的价格的折扣</th>
        <th>${bulkDiscountHead}</th>
        <th>该促销活动价格</th>
        <th>促销提升</th>
        <th>提升价格<br>最小 / 最大</th>
        <th>促销活动中的商品数量</th>
      </tr></thead>
      <tbody>${rows || `<tr><td colspan="10"><div class="ozon-empty">暂无商品数据</div></td></tr>`}</tbody>
    </table>
  </div>`;
}

function renderOzonMarketingActionCards(actions = []) {
  const cards = actions.map(action => {
    const displayTitle = getOzonActionDisplayTitle(action);
    const active = Boolean(action.isActive);
    const joined = Number(action.participantsCount || action.productCount || 0);
    const candidates = Number(action.candidatesCount || 0);
    const ended = action.daysToEnd !== null && action.daysToEnd !== undefined && Number(action.daysToEnd) < 0;
    const periodText = `从 ${escapeHtml(formatOzonActionDate(action.dateStart))} 至 ${escapeHtml(formatOzonActionEndDate(action.dateEnd))}`;
    const typeText = getOzonActionTypeLabel(action);
    const freezeText = getOzonActionFreezeText(action);
    const joinedText = getOzonActionJoinText(action);
    const autoAddText = getOzonActionAutoAddText(action);
    const accountTitle = action.accountTitle || getOzonAccountLabel(action.accountId || '');
    const footerText = /stock_discount|stock|inventory/i.test(normalizeOzonText(action.actionType || action.type || ''))
      ? '参与活动即可获得“WOW价格”徽章及特别精选商品集'
      : '';
    const cardClass = ended ? 'is-ended' : active ? 'is-active' : 'is-planned';
    const statusBadge = ended
      ? '<span class="ozon-action-badge is-ended">已结束</span>'
      : '<span class="ozon-action-badge">已自动添加</span>';
    return `<div class="ozon-action-card ${cardClass}" role="button" tabindex="0" data-ozon-open-action="${escapeHtml(action.id || '')}" data-account-id="${escapeHtml(action.accountId || '')}" data-action-title="${escapeHtml(displayTitle)}">
      <div class="ozon-action-main">
        <div class="ozon-action-info">
          <div class="ozon-action-title">${escapeHtml(displayTitle)}</div>
          <div class="ozon-action-period"><strong>${periodText}</strong>${typeText ? ` · ${escapeHtml(typeText)}` : ''}${accountTitle ? ` · ${escapeHtml(accountTitle)}` : ''}</div>
          <div class="ozon-action-subline"><b>${escapeHtml(joinedText)}</b>${autoAddText ? ` <span>${escapeHtml(autoAddText)}</span>` : (freezeText ? ` <span>${escapeHtml(freezeText)}</span>` : (footerText ? ` <span>${escapeHtml(footerText)}</span>` : ''))}</div>
        </div>
        <div class="ozon-action-side">
          <div class="ozon-action-counts">
            <div class="ozon-action-count"><span>已参加</span><strong>${escapeHtml(joined.toLocaleString())}</strong></div>
            <div class="ozon-action-count"><span>可添加</span><strong>${escapeHtml(candidates.toLocaleString())}</strong></div>
          </div>
          <div class="ozon-action-badges">
            ${statusBadge}
            ${active || action.isParticipating ? '<span class="ozon-action-badge is-joined">我正在参与</span>' : ''}
          </div>
        </div>
      </div>
      <div class="ozon-action-footer">
        <div class="ozon-action-tools">
          <button class="btn btn-primary btn-sm" type="button" data-ozon-open-action="${escapeHtml(action.id || '')}" data-account-id="${escapeHtml(action.accountId || '')}" data-action-title="${escapeHtml(displayTitle)}">进入活动</button>
          <button class="btn btn-default btn-sm" type="button" data-ozon-open-candidate-modal="${escapeHtml(action.id || '')}" data-account-id="${escapeHtml(action.accountId || '')}" data-action-title="${escapeHtml(displayTitle)}">添加商品</button>
        </div>
      </div>
    </div>`;
  }).join('');
  return `<div class="ozon-action-board-head">
    <div>
      <h4>全球促销活动</h4>
      <p>我们在这里集中展示 OZON 后台“来自 OZON 的促销活动”，可按店铺、状态和关键词筛选。</p>
    </div>
  </div>${cards}`;
}

function renderOzonMarketing() {
  syncOzonAccountSelects();
  const data = ozonState.marketing || {};
  const summary = data.summary || {};
  const { actions } = getOzonMarketingFilteredData();
  const noAccounts = !hasOzonAccounts();
  setOzonText('ozonMarketingTotal', String(summary.total || 0));
  setOzonText('ozonMarketingActive', String(summary.active || 0));
  setOzonText('ozonMarketingEndingSoon', String(summary.endingSoon || 0));
  setOzonText('ozonMarketingCandidates', String(summary.candidateProducts || 0));
  setOzonText('ozonMarketingJoined', String(summary.joinedProducts || 0));
  setOzonMarketingActiveTitle(ozonState.marketingView === 'products' ? getOzonActionDisplayTitle(ozonState.marketingSelectedAction || {}) : '');
  if (noAccounts) {
    const actionWrap = document.getElementById('ozonMarketingList');
    const productWrap = document.getElementById('ozonMarketingProductList');
    if (actionWrap) actionWrap.innerHTML = renderOzonNeedAccountEmpty('请先在首页添加 OZON 店铺授权。');
    if (productWrap) productWrap.innerHTML = renderOzonNeedAccountEmpty('请先在首页添加 OZON 店铺授权。');
    return;
  }
  const actionWrap = document.getElementById('ozonMarketingList');
  if (actionWrap) {
    if (ozonState.marketingLoading && !Array.isArray(data.actions)) {
      actionWrap.innerHTML = renderOzonLoadingEmpty('正在读取 OZON 促销活动...');
    } else
    if (!actions.length) {
      const errors = Array.isArray(data.errors) ? data.errors : [];
      actionWrap.innerHTML = `<div class="ozon-empty">${escapeHtml(data.sourceStatusText || '暂无营销活动')}${errors.length ? `<br>${escapeHtml(errors.map(item => `${item.accountTitle || item.accountId}: ${item.message}`).join('；'))}` : ''}</div>`;
    } else {
      actionWrap.innerHTML = renderOzonMarketingActionCards(actions);
    }
  }
  renderOzonMarketingProducts();
  renderOzonDiscountTasks();
  setOzonMarketingView(ozonState.marketingView || 'actions');
}

function renderOzonMarketingProducts() {
  const wrap = document.getElementById('ozonMarketingProductList');
  const data = ozonState.marketingProducts || {};
  const selected = ozonState.marketingSelectedAction || {};
  const selectedTitle = getOzonActionDisplayTitle(selected);
  const selectedAutoAddDate = normalizeOzonText(ozonState.marketingDetailAutoAddDate || '');
  const dataSource = normalizeOzonMarketingProductSource(data.source || ozonState.marketingProductSource || 'joined');
  const detailTab = selectedAutoAddDate || dataSource === 'auto'
    ? 'autoAdd'
    : (dataSource === 'candidate' ? 'candidate' : 'joined');
  const keyword = normalizeOzonText(ozonState.marketingProductKeyword || document.getElementById('ozonMarketingDetailSearchInput')?.value || '');
  const allProducts = Array.isArray(data.products) ? data.products.map(product => normalizeOzonPromoProductRow(product)) : [];
  const autoAddDates = getOzonMarketingAutoAddDates(selected);
  const selectedAutoAddDateKey = normalizeOzonPromoDateKey(selectedAutoAddDate);
  const autoAddDateKeys = autoAddDates.map(dateValue => normalizeOzonPromoDateKey(dateValue)).filter(Boolean);
  const baseProducts = selectedAutoAddDate
    ? (dataSource === 'auto' ? allProducts : [])
    : allProducts;
  const products = baseProducts.filter(product => matchesOzonPromoProductKeyword(product, keyword));
  pruneOzonMarketingDetailSelection(products);
  const detailSelectedCount = getOzonMarketingDetailSelectedProducts().length;
  const counts = getOzonMarketingActionCounts(selected);
  const page = Math.max(1, Number(data.page || ozonState.marketingProductPage || 1));
  const limit = Math.max(1, Number(data.limit || ozonState.marketingProductLimit || 50));
  const total = Math.max(0, Number(data.total !== undefined ? data.total : (products.length || 0)));
  const loaded = Math.max(0, Number(data.loaded !== undefined ? data.loaded : (products.length || 0)));
  const from = total && loaded ? ((page - 1) * limit + 1) : 0;
  const to = total && loaded ? Math.min((page - 1) * limit + loaded, total) : 0;
  const totalPages = total ? Math.max(1, Math.ceil(total / limit)) : page;
  const pageText = total ? `${from}-${to} / ${total.toLocaleString()} 条 · 第 ${page}/${totalPages} 页` : `${loaded.toLocaleString()} 条`;
  const pageNumbers = renderOzonPromoPageNumbers(page, totalPages, 'data-ozon-product-page');
  const autoAddDateTabs = autoAddDates.map(dateValue => {
    const formattedDate = formatOzonActionFullDate(dateValue);
    if (!formattedDate || formattedDate === '--') return '';
    const active = normalizeOzonPromoDateKey(dateValue) === normalizeOzonPromoDateKey(selectedAutoAddDate);
    const countValue = getOzonMarketingAutoAddCount(selected, dateValue);
    const count = countValue > 0
      ? countValue.toLocaleString()
      : '';
    return `<button type="button" class="ozon-promo-switch-chip ${active ? 'active' : ''}" data-ozon-detail-auto-date="${escapeHtml(dateValue)}">从${escapeHtml(formattedDate)}起参与${count ? ` <span class="ozon-promo-count">${escapeHtml(count)}</span>` : ''}</button>`;
  }).filter(Boolean).join('');
  if (!wrap) return;
  if (!data.actionId) {
    wrap.innerHTML = '<div class="ozon-empty">先在活动列表里点击活动卡片，进入活动详情。</div>';
    return;
  }
  setOzonMarketingActiveTitle(selectedTitle || '');
  wrap.innerHTML = `<div class="ozon-promo-detail">
    <div class="ozon-promo-detail-head">
      <div class="ozon-promo-switch">
        <button type="button" class="${detailTab === 'joined' ? 'active' : ''}" data-ozon-detail-source="joined">正在参与 <span class="ozon-promo-count">${escapeHtml(counts.joined.toLocaleString())}</span></button>
        ${autoAddDateTabs}
      </div>
      <div class="ozon-promo-toolbar">
        <input id="ozonMarketingDetailSearchInput" value="${escapeHtml(keyword)}" placeholder="名称、货号、SKU">
        <select disabled><option>商品</option></select>
        <div class="ozon-actions">
          <button class="btn btn-primary btn-sm" type="button" data-ozon-open-candidate-modal="${escapeHtml(selected.id || '')}" data-account-id="${escapeHtml(selected.accountId || '')}" data-action-title="${escapeHtml(selectedTitle || '')}">添加商品</button>
          <button class="btn btn-default btn-sm" type="button" data-ozon-refresh-action-products>刷新</button>
          <button class="btn btn-default btn-sm" type="button" data-ozon-deactivate-selected-products ${detailSelectedCount > 0 ? '' : 'disabled'}>移出勾选链接</button>
        </div>
      </div>
    </div>
    <div class="ozon-promo-detail-table-slot">
      ${data.loading ? renderOzonLoadingEmpty('正在读取当前页活动商品...') : (products.length ? renderOzonPromoProductTable(products, { mode: 'detail' }) : '<div class="ozon-empty">当前活动暂无商品明细。</div>')}
    </div>
    ${!data.loading && data.actionId ? `<div class="ozon-promo-modal-footer">
      <span class="ozon-promo-page-info">${escapeHtml(pageText)}</span>
      <div class="ozon-promo-page-center">${pageNumbers}</div>
      <div class="ozon-promo-page-actions">
        <button class="btn btn-default btn-sm" type="button" data-ozon-product-page="${escapeHtml(page - 1)}" ${page <= 1 ? 'disabled' : ''}>上一页</button>
        <button class="btn btn-default btn-sm" type="button" data-ozon-product-page="${escapeHtml(page + 1)}" ${page >= totalPages ? 'disabled' : ''}>下一页</button>
      </div>
    </div>` : ''}
  </div>`;
  syncOzonMarketingDetailSelectionUi(wrap);
}

function renderOzonDiscountTasks() {
  const wrap = document.getElementById('ozonDiscountTaskList');
  const meta = document.getElementById('ozonDiscountTaskMeta');
  const data = ozonState.discountTasks || {};
  const tasks = Array.isArray(data.tasks) ? data.tasks : [];
  if (meta) {
    meta.innerHTML = renderOzonMetaItems([
      `状态：<strong>${escapeHtml(data.status || ozonState.discountTaskStatus || 'NEW')}</strong>`,
      data.total !== undefined ? `申请数：<strong>${escapeHtml(data.total || 0)}</strong>` : '',
      data.sourceStatusText ? escapeHtml(data.sourceStatusText) : '',
      data.fetchedAt ? `读取时间：${escapeHtml(formatOzonDateTime(data.fetchedAt))}` : ''
    ].filter(Boolean).map(item => item.replace(/^([^：]+)：/, '<b>$1：</b>')));
  }
  if (!wrap) return;
  if (!data.fetchedAt && !tasks.length) {
    wrap.innerHTML = '<div class="ozon-empty">请点击“刷新折扣申请”。</div>';
    return;
  }
  if (!tasks.length) {
    const errors = Array.isArray(data.errors) ? data.errors : [];
    wrap.innerHTML = `<div class="ozon-empty">${escapeHtml(data.sourceStatusText || '暂无折扣申请')}${errors.length ? `<br>${escapeHtml(errors.map(item => `${item.accountTitle || item.accountId}: ${item.message}`).join('；'))}` : ''}</div>`;
    return;
  }
  wrap.innerHTML = `<table class="ozon-table">
    <thead><tr><th>申请</th><th>店铺</th><th>商品</th><th>价格</th><th>数量</th><th>时间</th><th>备注</th><th>操作</th></tr></thead>
    <tbody>${tasks.map(task => `<tr>
      <td><div class="ozon-list-title">#${escapeHtml(task.id || '--')}</div><div class="ozon-list-meta">${escapeHtml(task.status || '--')}${task.customerName ? `<br>${escapeHtml(task.customerName)}` : ''}</div></td>
      <td>${escapeHtml(task.accountTitle || 'OZON 店铺')}</td>
      <td><div class="ozon-list-meta">SKU: <span class="ozon-mono">${escapeHtml(task.sku || '--')}</span><br>Offer: <span class="ozon-mono">${escapeHtml(task.offerId || '--')}</span></div></td>
      <td><div class="ozon-list-meta">原价：${escapeHtml(task.originalPrice ? formatOzonAmount(task.originalPrice, 'RUB') : '--')}<br>申请：${escapeHtml(task.requestedPrice ? formatOzonAmount(task.requestedPrice, 'RUB') : '--')}<br><span class="${Number(task.priceGap || 0) >= 20 ? 'ozon-trend-down' : 'ozon-trend-flat'}">差异：${escapeHtml(task.priceGap ? `${task.priceGap}%` : '--')}</span></div></td>
      <td><div class="ozon-list-meta">申请：${escapeHtml(task.requestedQuantityMin || '--')} - ${escapeHtml(task.requestedQuantityMax || '--')}<br>批准：${escapeHtml(task.approvedQuantityMin || '--')} - ${escapeHtml(task.approvedQuantityMax || '--')}</div></td>
      <td><div class="ozon-list-meta">创建：${escapeHtml(formatOzonDateTime(task.createdAt) || '--')}<br>截止：${escapeHtml(formatOzonDateTime(task.endAt) || '--')}</div></td>
      <td><div class="ozon-list-meta">${escapeHtml(task.userComment || task.sellerComment || '--')}</div></td>
      <td><div class="ozon-marketing-task-actions">
        <button class="btn btn-primary btn-sm" type="button" data-ozon-approve-discount="${escapeHtml(task.id || '')}">同意</button>
        <button class="btn btn-default btn-sm" type="button" data-ozon-decline-discount="${escapeHtml(task.id || '')}">拒绝</button>
      </div></td>
    </tr>`).join('')}</tbody>
  </table>`;
}
async function loadOzonMarketing(options = {}) {
  restoreOzonAccountsFromLocalCache();
  const hasPrevious = Boolean(ozonState.marketing && Array.isArray(ozonState.marketing.actions));
  const list = document.getElementById('ozonMarketingList');
  ozonState.marketingLoading = true;
  if (!options.silent && !hasPrevious) renderOzonMarketing();
  if (!options.silent && (!hasPrevious || options.forceRefresh)) {
    if (list && !hasPrevious) list.innerHTML = '<div class="ozon-empty">正在读取 OZON 促销活动...</div>';
  }
  try {
    const accountId = getOzonActiveAccountId(document.getElementById('ozonMarketingAccountFilter')?.value || ozonState.marketingAccountId || '');
    ozonState.marketingAccountId = accountId;
    const qs = new URLSearchParams();
    const shouldIncludeProducts = options.includeProducts === true
      || options.sync === true
      || ozonState.marketingView === 'products'
      || Boolean(ozonState.marketingSelectedAction?.id);
    if (accountId) qs.set('ozonAccountId', accountId);
    qs.set('includeProducts', shouldIncludeProducts ? '1' : '0');
    if (options.sync) qs.set('sync', '1');
    const path = `/marketing/actions?${qs.toString()}`;
    await loadOzonCached(path, (result) => {
      ozonState.marketing = result;
      ozonState.marketingLoading = false;
      renderOzonMarketing();
    }, {
      allowStale: true,
      keepStaleOnError: true,
      requestTimeoutMs: options.sync ? 35000 : OZON_MARKETING_LIST_READ_TIMEOUT_MS,
      ...options
    });
    ozonState.marketingLoading = false;
    renderOzonMarketing();
  } catch (err) {
    ozonState.marketingLoading = false;
    if (hasPrevious) {
      renderOzonMarketing();
      setOzonResult(`显示上次活动数据，最新刷新失败：${err.message || '营销活动读取失败'}`, 'warning');
      return;
    }
    const message = err.message || '营销活动读取失败';
    const timeoutMessage = /超时|timeout|abort/i.test(message)
      ? `${message}。没有生成临时活动数据；请稍后重试，或点击“同步”重新从 OZON 官方接口同步。`
      : message;
    if (list) list.innerHTML = renderOzonErrorEmpty(timeoutMessage);
    setOzonResult(timeoutMessage, 'error');
  }
}

async function loadOzonMarketingProducts(action = {}, source = 'joined', options = {}) {
  const actionId = action.id || action.actionId || action.action_id;
  const accountId = action.accountId || action.ozonAccountId || ozonState.marketingAccountId || '';
  if (!actionId || !accountId) {
    setOzonResult('请先选择一个活动和店铺', 'error');
    return;
  }
  const knownAction = findOzonMarketingActionById(actionId, accountId) || {};
  ozonState.marketingSelectedAction = { ...knownAction, ...action, id: actionId, accountId };
  ozonState.marketingProductSource = normalizeOzonMarketingProductSource(source);
  ozonState.marketingDetailTab = ozonState.marketingProductSource;
  if (!options.keepAutoAddDate) ozonState.marketingDetailAutoAddDate = '';
  if (!options.keepDetailSelection) ozonState.marketingDetailSelectedIds = new Set();
  const limit = Math.max(1, Number(options.limit || ozonState.marketingProductLimit || 50));
  const page = Math.max(1, Number(options.page || ozonState.marketingProductPage || 1));
  const offset = (page - 1) * limit;
  ozonState.marketingProductPage = page;
  ozonState.marketingProductLimit = limit;
  ozonState.marketingView = 'products';
  ozonState.marketingProducts = {
    ok: true,
    actionId,
    source: ozonState.marketingProductSource,
    page,
    limit,
    offset,
    total: 0,
    loaded: 0,
    products: [],
    loading: true
  };
  setOzonMarketingView('products');
  renderOzonMarketingProducts();
  try {
    const qs = new URLSearchParams({
      ozonAccountId: accountId,
      source: ozonState.marketingProductSource,
      limit: String(limit),
      offset: String(offset),
      maxPages: '1',
      hydrateDetails: options.hydrateDetails ? '1' : '0'
    });
    if (ozonState.marketingProductSource === 'auto') {
      const autoAddDates = getOzonMarketingAutoAddDates(ozonState.marketingSelectedAction || action);
      if (ozonState.marketingDetailAutoAddDate) qs.set('autoAddDate', ozonState.marketingDetailAutoAddDate);
      qs.set('autoAddDateCount', String(autoAddDates.length));
      qs.set('maxScanPages', '60');
    }
    const displayTitle = getOzonActionDisplayTitle(ozonState.marketingSelectedAction || action);
    if (displayTitle) qs.set('actionTitle', displayTitle);
    const path = `/marketing/actions/${encodeURIComponent(actionId)}/products?${qs.toString()}`;
    await loadOzonCached(path, (result) => {
      ozonState.marketingProducts = result;
      ozonState.marketingProducts.hydrating = false;
      ozonState.marketingProductPage = Math.max(1, Number(result.page || page));
      ozonState.marketingProductLimit = Math.max(1, Number(result.limit || limit));
      ozonState.marketingSelectedAction = {
        ...ozonState.marketingSelectedAction,
        ...(result.account ? { accountTitle: result.account.title || ozonState.marketingSelectedAction.accountTitle } : {})
      };
      updateOzonMarketingSelectedActionProductCount(result.source || ozonState.marketingProductSource, result.total || 0);
      renderOzonMarketingProducts();
      maybeHydrateOzonMarketingProductDetails(actionId, accountId, ozonState.marketingProductSource, {
        page: ozonState.marketingProductPage,
        limit: ozonState.marketingProductLimit,
        keepAutoAddDate: Boolean(options.keepAutoAddDate || ozonState.marketingDetailAutoAddDate),
        actionTitle: displayTitle,
        autoAddDate: ozonState.marketingDetailAutoAddDate,
        skip: Boolean(options.hydrateDetails)
      });
    }, {
      ...options,
      requestTimeoutMs: options.requestTimeoutMs || (ozonState.marketingProductSource === 'auto' ? 65000 : undefined),
      cache: false
    });
  } catch (err) {
    const wrap = document.getElementById('ozonMarketingProductList');
    if (wrap) wrap.innerHTML = `<div class="ozon-empty">${escapeHtml(err.message || '活动商品读取失败')}</div>`;
    setOzonResult(err.message || '活动商品读取失败', 'error');
  }
}

async function maybeHydrateOzonMarketingProductDetails(actionId = '', accountId = '', source = 'joined', options = {}) {
  const data = ozonState.marketingProducts || {};
  if (options.skip || data.loading || data.hydrating || !data.actionId) return;
  if (!shouldHydrateOzonMarketingProducts(data)) return;
  const page = Math.max(1, Number(options.page || data.page || ozonState.marketingProductPage || 1));
  const limit = Math.max(1, Number(options.limit || data.limit || ozonState.marketingProductLimit || 50));
  const hydrateKey = getOzonMarketingProductHydrateKey(actionId || data.actionId, source || data.source, page, limit, options.autoAddDate || '');
  if (!hydrateKey || ozonState.marketingProductHydrateKey === hydrateKey) return;
  ozonState.marketingProductHydrateKey = hydrateKey;
  ozonState.marketingProductHydrating = true;
  ozonState.marketingProducts = { ...data, hydrating: true };
  renderOzonMarketingProducts();
  try {
    const offset = (page - 1) * limit;
    const normalizedSource = normalizeOzonMarketingProductSource(source || data.source || ozonState.marketingProductSource || 'joined');
    const qs = new URLSearchParams({
      ozonAccountId: accountId,
      source: normalizedSource,
      limit: String(limit),
      offset: String(offset),
      maxPages: '1',
      hydrateDetails: '1'
    });
    if (normalizedSource === 'auto') {
      const autoAddDates = getOzonMarketingAutoAddDates(ozonState.marketingSelectedAction || {});
      if (options.autoAddDate) qs.set('autoAddDate', options.autoAddDate);
      qs.set('autoAddDateCount', String(autoAddDates.length));
      qs.set('maxScanPages', '60');
    }
    if (options.actionTitle) qs.set('actionTitle', options.actionTitle);
    const result = await ozonRequest(`/marketing/actions/${encodeURIComponent(actionId || data.actionId)}/products?${qs.toString()}`, {
      cache: false,
      requestTimeoutMs: normalizedSource === 'auto' ? 65000 : 45000
    });
    const stillSamePage = String(ozonState.marketingProducts?.actionId || '') === String(result.actionId || actionId || data.actionId)
      && String(ozonState.marketingProducts?.source || '') === String(result.source || source)
      && Number(ozonState.marketingProducts?.page || page) === Number(result.page || page);
    if (!stillSamePage) return;
    ozonState.marketingProducts = { ...result, hydrating: false };
    ozonState.marketingProductPage = Math.max(1, Number(result.page || page));
    ozonState.marketingProductLimit = Math.max(1, Number(result.limit || limit));
    updateOzonMarketingSelectedActionProductCount(result.source || normalizedSource, result.total || 0);
    renderOzonMarketingProducts();
  } catch (err) {
    ozonState.marketingProducts = {
      ...(ozonState.marketingProducts || data),
      hydrating: false,
      detailErrors: [{ message: err.message || '当前页商品详情补齐失败' }]
    };
    renderOzonMarketingProducts();
  } finally {
    ozonState.marketingProductHydrating = false;
  }
}

async function openOzonMarketingDetail(action = {}, source = 'joined') {
  ozonState.marketingProductKeyword = '';
  ozonState.marketingProductPage = 1;
  ozonState.marketingProductHydrateKey = '';
  await loadOzonMarketingProducts(action, source);
}

async function openOzonMarketingCandidateModal(action = {}) {
  const actionId = action.id || action.actionId || action.action_id;
  const accountId = action.accountId || action.ozonAccountId || ozonState.marketingAccountId || '';
  if (!actionId || !accountId) {
    setOzonResult('请先选择一个活动和店铺', 'error');
    return;
  }
  const knownAction = findOzonMarketingActionById(actionId, accountId) || {};
  ozonState.marketingSelectedAction = { ...knownAction, ...action, id: actionId, accountId };
  const displayTitle = getOzonActionDisplayTitle(ozonState.marketingSelectedAction);
  ozonState.marketingCandidateSelectedIds = new Set();
  ozonState.marketingCandidateSelectedProducts = {};
  ozonState.marketingCandidatePriceEdits = {};
  ozonState.marketingCandidateKeyword = '';
  ozonState.marketingCandidateProducts = null;
  ozonState.marketingCandidateRequestSeq += 1;
  ozonState.marketingCandidateHydrateKey = '';
  ozonState.marketingCandidatePage = 1;
  ozonState.marketingCandidateLimit = 100;
  openOzonMarketingActionModal({
    title: '将商品添加到促销活动中',
    panelClass: 'is-marketing-products',
    confirmText: '添加选择的商品',
    confirmMode: 'candidate-selected',
    body: `<div class="ozon-promo-modal-subtitle">${escapeHtml(displayTitle || 'OZON 活动')}</div>
      <div class="ozon-promo-modal-toolbar">
        <input id="ozonMarketingCandidateSearchInput" placeholder="名称、货号、SKU">
        <div></div>
        <button class="btn btn-default btn-sm" type="button" data-ozon-add-by-template>通过模板添加</button>
      </div>
      <div id="ozonMarketingCandidateModalList"><div class="ozon-empty">正在读取可添加商品...</div></div>`,
    onConfirm: async () => {
      await submitOzonCandidateProducts();
    }
  });
  await loadOzonMarketingCandidateProducts(ozonState.marketingSelectedAction);
}

async function loadOzonMarketingCandidateProducts(action = ozonState.marketingSelectedAction || {}, options = {}) {
  const actionId = action.id || action.actionId || action.action_id;
  const accountId = action.accountId || action.ozonAccountId || ozonState.marketingAccountId || '';
  const list = document.getElementById('ozonMarketingCandidateModalList');
  if (!actionId || !accountId) {
    if (list) list.innerHTML = renderOzonErrorEmpty('请先选择一个活动和店铺');
    return;
  }
  if (list) list.innerHTML = '<div class="ozon-empty">正在读取可添加商品...</div>';
  const requestSeq = ++ozonState.marketingCandidateRequestSeq;
  const requestActionId = String(actionId || '');
  const requestAccountId = String(accountId || '');
  try {
    const limit = Number(options.limit || ozonState.marketingCandidateLimit || 100);
    const page = Math.max(1, Number(options.page || ozonState.marketingCandidatePage || 1));
    const offset = (page - 1) * limit;
    const qs = new URLSearchParams({
      ozonAccountId: accountId,
      source: 'candidate',
      limit: String(limit),
      offset: String(offset),
      maxPages: '1',
      hydrateDetails: options.hydrateDetails ? '1' : '0'
    });
    const displayTitle = getOzonActionDisplayTitle(action);
    if (displayTitle) qs.set('actionTitle', displayTitle);
    const result = await ozonRequest(`/marketing/actions/${encodeURIComponent(actionId)}/products?${qs.toString()}`, {
      cache: false,
      requestTimeoutMs: 45000
    });
    const currentActionId = String(ozonState.marketingSelectedAction?.id || ozonState.marketingSelectedAction?.actionId || '');
    const currentAccountId = String(ozonState.marketingSelectedAction?.accountId || ozonState.marketingAccountId || '');
    if (requestSeq !== ozonState.marketingCandidateRequestSeq || requestActionId !== currentActionId || requestAccountId !== currentAccountId) {
      return;
    }
    ozonState.marketingCandidateProducts = {
      ...result,
      actionId: requestActionId,
      accountId: requestAccountId
    };
    ozonState.marketingCandidatePage = page;
    ozonState.marketingCandidateLimit = Math.max(1, Number(result.limit || limit));
    renderOzonMarketingCandidateModal();
    if (!options.hydrateDetails) {
      maybeHydrateOzonMarketingCandidateDetails(actionId, accountId, {
        page,
        limit: ozonState.marketingCandidateLimit,
        actionTitle: displayTitle
      });
    }
  } catch (err) {
    if (list) list.innerHTML = renderOzonErrorEmpty(err.message || '可添加商品读取失败');
  }
}

async function maybeHydrateOzonMarketingCandidateDetails(actionId = '', accountId = '', options = {}) {
  const data = ozonState.marketingCandidateProducts || {};
  if (!data.actionId || data.hydrating || !shouldHydrateOzonMarketingProducts(data)) return;
  const page = Math.max(1, Number(options.page || data.page || ozonState.marketingCandidatePage || 1));
  const limit = Math.max(1, Number(options.limit || data.limit || ozonState.marketingCandidateLimit || 100));
  const hydrateKey = [
    normalizeOzonText(accountId || data.accountId || ''),
    normalizeOzonText(actionId || data.actionId || ''),
    'candidate',
    String(page),
    String(limit)
  ].join(':');
  if (!hydrateKey || ozonState.marketingCandidateHydrateKey === hydrateKey) return;
  ozonState.marketingCandidateHydrateKey = hydrateKey;
  ozonState.marketingCandidateProducts = { ...data, hydrating: true };
  renderOzonMarketingCandidateModal();
  try {
    const offset = (page - 1) * limit;
    const qs = new URLSearchParams({
      ozonAccountId: accountId || data.accountId || '',
      source: 'candidate',
      limit: String(limit),
      offset: String(offset),
      maxPages: '1',
      hydrateDetails: '1'
    });
    if (options.actionTitle) qs.set('actionTitle', options.actionTitle);
    const result = await ozonRequest(`/marketing/actions/${encodeURIComponent(actionId || data.actionId)}/products?${qs.toString()}`, {
      cache: false,
      requestTimeoutMs: 45000
    });
    const current = ozonState.marketingCandidateProducts || {};
    const stillSamePage = String(current.actionId || '') === String(result.actionId || actionId || data.actionId)
      && String(current.accountId || '') === String(accountId || data.accountId || '')
      && Number(current.page || page) === Number(result.page || page);
    if (!stillSamePage) return;
    ozonState.marketingCandidateProducts = {
      ...result,
      accountId: accountId || data.accountId || '',
      hydrating: false
    };
    ozonState.marketingCandidatePage = Math.max(1, Number(result.page || page));
    ozonState.marketingCandidateLimit = Math.max(1, Number(result.limit || limit));
    renderOzonMarketingCandidateModal();
  } catch (err) {
    ozonState.marketingCandidateProducts = {
      ...(ozonState.marketingCandidateProducts || data),
      hydrating: false,
      detailErrors: [{ message: err.message || '当前页商品标题/图片补齐失败' }]
    };
    renderOzonMarketingCandidateModal();
  }
}

function renderOzonMarketingCandidateModal() {
  const list = document.getElementById('ozonMarketingCandidateModalList');
  if (!list) return;
  const data = ozonState.marketingCandidateProducts || {};
  const keyword = normalizeOzonText(ozonState.marketingCandidateKeyword || document.getElementById('ozonMarketingCandidateSearchInput')?.value || '');
  const products = (Array.isArray(data.products) ? data.products : [])
    .map(product => normalizeOzonPromoProductRow(product))
    .filter(product => matchesOzonPromoProductKeyword(product, keyword));
  const page = Math.max(1, Number(data.page || ozonState.marketingCandidatePage || 1));
  const limit = Math.max(1, Number(data.limit || ozonState.marketingCandidateLimit || 100));
  const total = Math.max(0, Number(data.total || products.length || 0));
  const loaded = Math.max(0, Number(data.loaded || products.length || 0));
  const missingDisplayCount = Math.max(0, Number(data.missingDisplayAfterHydration ?? data.missingDisplayBeforeHydration ?? 0));
  const detailErrors = Array.isArray(data.detailErrors) ? data.detailErrors : [];
  const detailStatusText = data.hydrating
    ? '正在补齐标题/图片...'
    : (missingDisplayCount > 0
      ? `${missingDisplayCount.toLocaleString()} 条缺少标题或图片`
      : (data.detailStatus === 'hydrated' || data.detailStatus === 'complete' ? '标题/图片已补齐' : '候选商品已加载'));
  const detailErrorText = detailErrors.length
    ? `；补齐失败：${detailErrors.map(item => item.message || item.error || String(item)).filter(Boolean).slice(0, 2).join('；')}`
    : '';
  const from = total && loaded ? ((page - 1) * limit + 1) : 0;
  const to = total && loaded ? Math.min((page - 1) * limit + loaded, total) : 0;
  const totalPages = total ? Math.max(1, Math.ceil(total / limit)) : page;
  const pageText = total ? `${from}-${to} / ${total.toLocaleString()} 条 · 第 ${page}/${totalPages} 页` : `${loaded.toLocaleString()} 条`;
  const pageNumbers = renderOzonPromoPageNumbers(page, totalPages);
  list.innerHTML = `${renderOzonPromoProductTable(products, { mode: 'candidate' })}
    <div class="ozon-promo-modal-footer">
      <span>已选择: <strong id="ozonMarketingCandidateSelectedCount">${escapeHtml((ozonState.marketingCandidateSelectedIds || new Set()).size)}</strong></span>
      <div class="ozon-promo-page-center">
        <span class="ozon-promo-page-info">${escapeHtml(pageText)} · ${escapeHtml(detailStatusText + detailErrorText)}</span>
        ${pageNumbers}
      </div>
      <div class="ozon-promo-page-actions">
        <button class="btn btn-default btn-sm" type="button" data-ozon-candidate-page="${escapeHtml(page - 1)}" ${page <= 1 ? 'disabled' : ''}>上一页</button>
        <button class="btn btn-default btn-sm" type="button" data-ozon-candidate-page="${escapeHtml(page + 1)}" ${page >= totalPages ? 'disabled' : ''}>下一页</button>
        <button class="btn btn-primary btn-sm" type="button" data-ozon-submit-selected-candidates>添加选择的商品</button>
        <button class="btn btn-default btn-sm" type="button" data-ozon-close-marketing-action>取消</button>
      </div>
    </div>`;
  syncOzonMarketingCandidateSelectionUi();
}

function getOzonMarketingCandidateSelectionSet() {
  if (!(ozonState.marketingCandidateSelectedIds instanceof Set)) {
    ozonState.marketingCandidateSelectedIds = new Set(Array.isArray(ozonState.marketingCandidateSelectedIds)
      ? ozonState.marketingCandidateSelectedIds.map(id => String(id || '')).filter(Boolean)
      : []);
  }
  return ozonState.marketingCandidateSelectedIds;
}

function getOzonMarketingCandidateSelectedProductMap() {
  if (!ozonState.marketingCandidateSelectedProducts || typeof ozonState.marketingCandidateSelectedProducts !== 'object' || Array.isArray(ozonState.marketingCandidateSelectedProducts)) {
    ozonState.marketingCandidateSelectedProducts = {};
  }
  return ozonState.marketingCandidateSelectedProducts;
}

function rememberOzonMarketingCandidateSelectedProduct(rowId = '') {
  const id = String(rowId || '').trim();
  if (!id) return;
  const product = getOzonMarketingCandidateProductByRowId(id);
  if (!product) return;
  getOzonMarketingCandidateSelectedProductMap()[id] = product;
}

function forgetOzonMarketingCandidateSelectedProduct(rowId = '') {
  const id = String(rowId || '').trim();
  if (!id) return;
  delete getOzonMarketingCandidateSelectedProductMap()[id];
}

function syncOzonMarketingCandidateSelectionUi(modal = document.getElementById('ozonMarketingActionModal')) {
  const selectedSet = getOzonMarketingCandidateSelectionSet();
  if (!modal) return;
  const checks = Array.from(modal.querySelectorAll('[data-ozon-candidate-check]'));
  checks.forEach((input) => {
    const id = input.dataset.ozonCandidateCheck || '';
    const checked = Boolean(id && selectedSet.has(id));
    if (input.checked !== checked) input.checked = checked;
    const row = input.closest('tr');
    if (row) {
      row.classList.toggle('is-selected', checked);
      row.dataset.ozonCandidateSelected = checked ? '1' : '0';
    }
  });
  const visibleChecks = checks.filter((input) => input.closest('tr')?.style.display !== 'none');
  const selectedVisibleCount = visibleChecks.filter(input => input.checked).length;
  const allCheck = modal.querySelector('[data-ozon-candidate-check-all]');
  if (allCheck) {
    allCheck.checked = visibleChecks.length > 0 && selectedVisibleCount === visibleChecks.length;
    allCheck.indeterminate = selectedVisibleCount > 0 && selectedVisibleCount < visibleChecks.length;
  }
  const countEl = document.getElementById('ozonMarketingCandidateSelectedCount');
  if (countEl) countEl.textContent = String(selectedSet.size);
  const selectedCount = selectedSet.size;
  const submitButtons = Array.from(modal.querySelectorAll('[data-ozon-submit-selected-candidates]'));
  const primaryConfirm = document.getElementById('ozonMarketingActionModalConfirmBtn');
  if (primaryConfirm?.dataset.ozonConfirmMode === 'candidate-selected' && !primaryConfirm.hidden) submitButtons.push(primaryConfirm);
  submitButtons.forEach((button) => {
    button.disabled = selectedCount <= 0;
    button.title = selectedCount > 0 ? `添加已勾选的 ${selectedCount} 个商品` : '请先勾选商品';
  });
}

function setOzonMarketingCandidateSelected(id, checked, modal = document.getElementById('ozonMarketingActionModal')) {
  const rowId = String(id || '');
  if (!rowId) return;
  const selectedSet = getOzonMarketingCandidateSelectionSet();
  if (checked) {
    selectedSet.add(rowId);
    rememberOzonMarketingCandidateSelectedProduct(rowId);
  } else {
    selectedSet.delete(rowId);
    forgetOzonMarketingCandidateSelectedProduct(rowId);
  }
  if (modal) {
    const input = modal.querySelector(`[data-ozon-candidate-check="${CSS.escape(rowId)}"]`);
    if (input && input.checked !== Boolean(checked)) input.checked = Boolean(checked);
    const row = input?.closest('tr') || modal.querySelector(`[data-ozon-candidate-row="${CSS.escape(rowId)}"]`);
    if (row) {
      row.classList.toggle('is-selected', Boolean(checked));
      row.dataset.ozonCandidateSelected = checked ? '1' : '0';
    }
    const checks = Array.from(modal.querySelectorAll('[data-ozon-candidate-check]'));
    const visibleChecks = checks.filter((item) => item.closest('tr')?.style.display !== 'none');
    const selectedVisibleCount = visibleChecks.filter(item => item.checked).length;
    const allCheck = modal.querySelector('[data-ozon-candidate-check-all]');
    if (allCheck) {
      allCheck.checked = visibleChecks.length > 0 && selectedVisibleCount === visibleChecks.length;
      allCheck.indeterminate = selectedVisibleCount > 0 && selectedVisibleCount < visibleChecks.length;
    }
    const countEl = document.getElementById('ozonMarketingCandidateSelectedCount');
    if (countEl) countEl.textContent = String(selectedSet.size);
    syncOzonMarketingCandidateSelectionUi(modal);
    return;
  }
  syncOzonMarketingCandidateSelectionUi(modal);
}

function setOzonMarketingCandidatePageSelected(checked, modal = document.getElementById('ozonMarketingActionModal')) {
  if (!modal) return;
  const selectedSet = getOzonMarketingCandidateSelectionSet();
  Array.from(modal.querySelectorAll('[data-ozon-candidate-check]')).forEach((input) => {
    const row = input.closest('tr');
    if (row?.style.display === 'none') return;
    const id = input.dataset.ozonCandidateCheck || '';
    if (!id) return;
    if (checked) {
      selectedSet.add(id);
      rememberOzonMarketingCandidateSelectedProduct(id);
    } else {
      selectedSet.delete(id);
      forgetOzonMarketingCandidateSelectedProduct(id);
    }
  });
  syncOzonMarketingCandidateSelectionUi(modal);
}

function handleOzonMarketingCandidateCheckInput(input, modal = document.getElementById('ozonMarketingActionModal')) {
  if (!input) return false;
  if (input.matches?.('[data-ozon-candidate-check-all]')) {
    setOzonMarketingCandidatePageSelected(Boolean(input.checked), modal);
    return true;
  }
  if (input.matches?.('[data-ozon-candidate-check]')) {
    setOzonMarketingCandidateSelected(input.dataset.ozonCandidateCheck || '', Boolean(input.checked), modal);
    return true;
  }
  return false;
}

function getOzonMarketingDetailSelectionSet() {
  if (!(ozonState.marketingDetailSelectedIds instanceof Set)) {
    ozonState.marketingDetailSelectedIds = new Set(Array.isArray(ozonState.marketingDetailSelectedIds)
      ? ozonState.marketingDetailSelectedIds.map(id => String(id || '')).filter(Boolean)
      : []);
  }
  return ozonState.marketingDetailSelectedIds;
}

function getOzonMarketingDetailVisibleProducts() {
  return (Array.isArray(ozonState.marketingProducts?.products) ? ozonState.marketingProducts.products : [])
    .map(product => normalizeOzonPromoProductRow(product));
}

function getOzonMarketingDetailSelectedProducts() {
  const selectedSet = getOzonMarketingDetailSelectionSet();
  return getOzonMarketingDetailVisibleProducts().filter(product => {
    const rowId = getOzonPromoProductRowId(product);
    return rowId && selectedSet.has(rowId) && product.productId;
  });
}

function pruneOzonMarketingDetailSelection(products = getOzonMarketingDetailVisibleProducts()) {
  const selectedSet = getOzonMarketingDetailSelectionSet();
  const validIds = new Set((Array.isArray(products) ? products : [])
    .map(product => normalizeOzonPromoProductRow(product))
    .filter(product => product.productId)
    .map(product => getOzonPromoProductRowId(product))
    .filter(Boolean));
  Array.from(selectedSet).forEach((id) => {
    if (!validIds.has(id)) selectedSet.delete(id);
  });
}

function syncOzonMarketingDetailSelectionUi(root = document.getElementById('ozonMarketing')) {
  if (!root) return;
  const selectedSet = getOzonMarketingDetailSelectionSet();
  const checks = Array.from(root.querySelectorAll('[data-ozon-detail-check]'));
  checks.forEach((input) => {
    const id = input.dataset.ozonDetailCheck || '';
    const checked = Boolean(id && selectedSet.has(id));
    if (input.checked !== checked) input.checked = checked;
    const row = input.closest('tr');
    if (row) row.classList.toggle('is-selected', checked);
  });
  const enabledChecks = checks.filter(input => !input.disabled);
  const selectedVisibleCount = enabledChecks.filter(input => input.checked).length;
  const allCheck = root.querySelector('[data-ozon-detail-check-all]');
  if (allCheck) {
    allCheck.checked = enabledChecks.length > 0 && selectedVisibleCount === enabledChecks.length;
    allCheck.indeterminate = selectedVisibleCount > 0 && selectedVisibleCount < enabledChecks.length;
  }
  const bulkBtn = root.querySelector('[data-ozon-deactivate-selected-products]');
  if (bulkBtn) {
    const count = getOzonMarketingDetailSelectedProducts().length;
    bulkBtn.disabled = count <= 0;
    bulkBtn.title = count > 0 ? `移出已勾选的 ${count} 条链接` : '请先勾选链接';
  }
}

function setOzonMarketingDetailSelected(id, checked, root = document.getElementById('ozonMarketing')) {
  const rowId = String(id || '').trim();
  if (!rowId) return;
  const selectedSet = getOzonMarketingDetailSelectionSet();
  if (checked) selectedSet.add(rowId);
  else selectedSet.delete(rowId);
  syncOzonMarketingDetailSelectionUi(root);
}

function setOzonMarketingDetailPageSelected(checked, root = document.getElementById('ozonMarketing')) {
  if (!root) return;
  const selectedSet = getOzonMarketingDetailSelectionSet();
  Array.from(root.querySelectorAll('[data-ozon-detail-check]')).forEach((input) => {
    if (input.disabled) return;
    const id = input.dataset.ozonDetailCheck || '';
    if (!id) return;
    if (checked) selectedSet.add(id);
    else selectedSet.delete(id);
  });
  syncOzonMarketingDetailSelectionUi(root);
}

function handleOzonMarketingDetailCheckInput(input, root = document.getElementById('ozonMarketing')) {
  if (!input) return false;
  if (input.matches?.('[data-ozon-detail-check-all]')) {
    setOzonMarketingDetailPageSelected(Boolean(input.checked), root);
    return true;
  }
  if (input.matches?.('[data-ozon-detail-check]')) {
    setOzonMarketingDetailSelected(input.dataset.ozonDetailCheck || '', Boolean(input.checked), root);
    return true;
  }
  return false;
}

function toggleOzonMarketingDetailRow(rowOrCell, root = document.getElementById('ozonMarketing')) {
  const input = rowOrCell?.querySelector?.('[data-ozon-detail-check]');
  if (!input || input.disabled) return false;
  input.checked = !input.checked;
  handleOzonMarketingDetailCheckInput(input, root);
  return true;
}

function toggleOzonMarketingCandidateRow(rowOrCell, modal = document.getElementById('ozonMarketingActionModal')) {
  const input = rowOrCell?.querySelector?.('[data-ozon-candidate-check]');
  if (!input || input.disabled) return false;
  input.checked = !input.checked;
  handleOzonMarketingCandidateCheckInput(input, modal);
  return true;
}

function getOzonMarketingCandidateProductByRowId(rowId) {
  const target = String(rowId || '');
  const products = Array.isArray(ozonState.marketingCandidateProducts?.products)
    ? ozonState.marketingCandidateProducts.products
    : [];
  return products
    .map(product => normalizeOzonPromoProductRow(product))
    .find(product => getOzonPromoProductRowId(product) === target) || null;
}

function buildOzonCandidateSubmissionProducts(products = []) {
  const skipped = [];
  const payloadProducts = [];
  const seen = new Set();
  for (const row of Array.isArray(products) ? products : []) {
    const product = applyOzonMarketingCandidateEdit(normalizeOzonPromoProductRow(row));
    const rowId = getOzonPromoProductRowId(product);
    if (!rowId || seen.has(rowId)) continue;
    seen.add(rowId);
    const productId = Number(product.productId || 0);
    const actionPrice = getOzonPromoSuggestedActionPrice(product);
    const stock = getOzonPromoSubmissionStock(product);
    if (!(productId > 0)) {
      skipped.push(`${rowId || product.name || '未知商品'}：缺少官方商品 ID`);
      continue;
    }
    if (!(actionPrice > 0)) {
      skipped.push(`${product.offerId || productId}：缺少 OZON 返回的活动价/建议价`);
      continue;
    }
    if (!Number.isFinite(stock) || stock < 0) {
      skipped.push(`${product.offerId || productId}：OZON 返回的活动数量无效`);
      continue;
    }
    payloadProducts.push({
      product_id: productId,
      action_price: roundOzonPromoNumber(actionPrice, 2),
      stock: roundOzonPromoNumber(stock, 2)
    });
  }
  return { payloadProducts, skipped };
}

async function loadAllOzonMarketingCandidateProductsForAction(action = {}) {
  const actionId = action.id || action.actionId || action.action_id;
  const accountId = action.accountId || action.ozonAccountId || ozonState.marketingAccountId || '';
  if (!actionId || !accountId) throw new Error('请先选择一个活动和店铺');
  const resultEl = document.getElementById('ozonMarketingActionModalResult');
  const limit = 1000;
  let offset = 0;
  let total = 0;
  const products = [];
  const seen = new Set();
  let finished = false;
  for (let pageIndex = 0; pageIndex < 200; pageIndex += 1) {
    if (resultEl) resultEl.textContent = `正在读取全部可添加商品：第 ${pageIndex + 1} 页...`;
    const qs = new URLSearchParams({
      ozonAccountId: accountId,
      source: 'candidate',
      limit: String(limit),
      offset: String(offset),
      maxPages: '1'
    });
    const displayTitle = getOzonActionDisplayTitle(action);
    if (displayTitle) qs.set('actionTitle', displayTitle);
    const result = await ozonRequest(`/marketing/actions/${encodeURIComponent(actionId)}/products?${qs.toString()}`);
    const pageProducts = Array.isArray(result.products) ? result.products : [];
    const normalizedPageProducts = pageProducts.map(product => normalizeOzonPromoProductRow(product));
    normalizedPageProducts.forEach(product => {
      const rowId = getOzonPromoProductRowId(product);
      if (!rowId || seen.has(rowId)) return;
      seen.add(rowId);
      products.push(product);
    });
    total = Math.max(total, Number(result.total || 0));
    const serverLimit = Math.max(1, Number(result.limit || limit));
    const loaded = Number(result.loaded || pageProducts.length || 0);
    if (!loaded || loaded < serverLimit) {
      finished = true;
      break;
    }
    offset += loaded;
    if (total > 0 && offset >= total) {
      finished = true;
      break;
    }
  }
  if (!finished && products.length) {
    throw new Error(`OZON 候选商品分页超过保护上限，已读取 ${products.length.toLocaleString()} 个但无法确认已完整读取，请稍后重试`);
  }
  if (total > 0 && products.length < total) {
    throw new Error(`OZON 候选商品未读取完整：已读取 ${products.length.toLocaleString()} / ${total.toLocaleString()}，请稍后重试`);
  }
  return products;
}

function mergeOzonMarketingResultPayload(results = []) {
  const acceptedProductIds = [];
  const rejected = [];
  const failDetails = [];
  results.forEach(result => {
    if (Array.isArray(result.acceptedProductIds)) acceptedProductIds.push(...result.acceptedProductIds);
    if (Array.isArray(result.rejected)) rejected.push(...result.rejected);
    if (Array.isArray(result.failDetails)) failDetails.push(...result.failDetails);
  });
  return {
    acceptedProductIds,
    rejected,
    failDetails,
    successCount: acceptedProductIds.length || results.reduce((sum, item) => sum + Number(item.successCount || 0), 0),
    failCount: rejected.length || failDetails.length || results.reduce((sum, item) => sum + Number(item.failCount || 0), 0)
  };
}

async function submitOzonCandidateProducts() {
  const action = ozonState.marketingSelectedAction || {};
  const displayTitle = getOzonActionDisplayTitle(action);
  const resultEl = document.getElementById('ozonMarketingActionModalResult');
  if (resultEl) resultEl.textContent = '正在准备添加选择的商品...';
  const products = (Array.isArray(ozonState.marketingCandidateProducts?.products) ? ozonState.marketingCandidateProducts.products : [])
    .map(product => normalizeOzonPromoProductRow(product));
  const selectedIds = getOzonMarketingCandidateSelectionSet();
  const selectedIdList = Array.from(selectedIds).filter(Boolean);
  const selectedProductMap = getOzonMarketingCandidateSelectedProductMap();
  const currentProductMap = new Map(products.map(product => [getOzonPromoProductRowId(product), product]).filter(([rowId]) => Boolean(rowId)));
  const missingSelectedIds = [];
  const targetProducts = selectedIdList.map((rowId) => {
    const product = currentProductMap.get(rowId) || selectedProductMap[rowId] || null;
    if (!product) missingSelectedIds.push(rowId);
    return product;
  }).filter(Boolean);
  if (missingSelectedIds.length) {
    throw new Error(`有 ${missingSelectedIds.length} 个跨页勾选商品缺少页面快照，请回到对应页重新勾选后再提交`);
  }
  if (!targetProducts.length) throw new Error('请先勾选要添加的商品');
  const { payloadProducts, skipped } = buildOzonCandidateSubmissionProducts(targetProducts);
  if (!payloadProducts.length) {
    const reasonText = skipped.length ? `：${skipped.slice(0, 5).join('；')}` : '';
    throw new Error(`所选商品缺少官方活动价或活动数量，无法提交${reasonText}`);
  }
  if (resultEl) {
    resultEl.textContent = `已准备 ${payloadProducts.length} 个商品，等待确认提交${skipped.length ? `；${skipped.length} 个商品因缺少官方活动价不会提交` : ''}。`;
  }
  const confirmed = await showCenterConfirm({
    title: '确认添加活动商品',
    message: `确认把 ${payloadProducts.length} 个商品添加到活动「${displayTitle || action.id}」？${skipped.length ? `\n${skipped.length} 个商品因缺少官方活动价不会提交。` : ''}`,
    confirmText: '确认添加'
  });
  if (!confirmed) throw new Error('已取消操作');
  const chunkSize = 100;
  const results = [];
  for (let index = 0; index < payloadProducts.length; index += chunkSize) {
    const chunk = payloadProducts.slice(index, index + chunkSize);
    if (resultEl) resultEl.textContent = `正在提交 ${index + 1}-${index + chunk.length} / ${payloadProducts.length} 个商品...`;
    const chunkResult = await ozonRequest(`/marketing/actions/${encodeURIComponent(action.id)}/products/activate`, {
      method: 'POST',
      body: JSON.stringify({
        ozonAccountId: action.accountId,
        products: chunk
      })
    });
    results.push(chunkResult);
  }
  const result = mergeOzonMarketingResultPayload(results);
  const successCount = result.acceptedProductIds?.length || result.successCount || 0;
  const failCount = result.rejected?.length || result.failCount || 0;
  const rejectText = Array.isArray(result.rejected) && result.rejected.length
    ? `；失败明细：${result.rejected.slice(0, 3).map(item => item.error || item.reason || item.message || item.product_id || JSON.stringify(item)).join('；')}`
    : '';
  const message = `添加完成：成功 ${successCount}，失败 ${failCount}${rejectText}`;
  if (failCount > 0 && successCount <= 0) {
    if (resultEl) resultEl.textContent = message;
    setOzonResult(message, 'warn');
    return;
  }
  setOzonResult(message, failCount > 0 ? 'warn' : 'success');
  if (resultEl) resultEl.textContent = message;
  setOzonMarketingActionModalVisible(false);
  await loadOzonMarketingProducts(action, 'joined');
}

async function loadOzonDiscountTasks(options = {}) {
  setOzonMarketingView('discounts');
  setOzonText('ozonDiscountTaskMeta', '正在读取折扣申请...');
  try {
    const accountId = document.getElementById('ozonMarketingAccountFilter')?.value || ozonState.marketingAccountId || '';
    const status = document.getElementById('ozonDiscountTaskStatusFilter')?.value || ozonState.discountTaskStatus || 'NEW';
    ozonState.marketingAccountId = accountId;
    ozonState.discountTaskStatus = status;
    const qs = new URLSearchParams({ status, limit: '50', page: '1' });
    if (accountId) qs.set('ozonAccountId', accountId);
    const path = `/marketing/discount-tasks?${qs.toString()}`;
    await loadOzonCached(path, (result) => {
      ozonState.discountTasks = result;
      renderOzonDiscountTasks();
    }, options);
  } catch (err) {
    const wrap = document.getElementById('ozonDiscountTaskList');
    if (wrap) wrap.innerHTML = `<div class="ozon-empty">${escapeHtml(err.message || '折扣申请读取失败')}</div>`;
    setOzonText('ozonDiscountTaskMeta', err.message || '折扣申请读取失败');
  }
}
function setOzonMarketingActionModalVisible(visible) {
  const modal = document.getElementById('ozonMarketingActionModal');
  if (!modal) return;
  modal.classList.toggle('is-open', Boolean(visible));
  modal.setAttribute('aria-hidden', visible ? 'false' : 'true');
  if (!visible) {
    const panel = document.getElementById('ozonMarketingActionModalPanel');
    if (panel) panel.className = 'ozon-modal-panel is-wide';
  }
}

function openOzonMarketingActionModal({ title = '营销活动操作', body = '', confirmText = '确认', confirmMode = '', onConfirm, panelClass = '' } = {}) {
  const titleEl = document.getElementById('ozonMarketingActionModalTitle');
  const bodyEl = document.getElementById('ozonMarketingActionModalBody');
  const resultEl = document.getElementById('ozonMarketingActionModalResult');
  const confirmBtn = document.getElementById('ozonMarketingActionModalConfirmBtn');
  const footerActions = document.getElementById('ozonMarketingActionModalFooterActions');
  const panel = document.getElementById('ozonMarketingActionModalPanel');
  if (panel) panel.className = `ozon-modal-panel is-wide ${panelClass}`.trim();
  if (titleEl) titleEl.textContent = title;
  if (bodyEl) bodyEl.innerHTML = body;
  if (resultEl) resultEl.textContent = '';
  if (footerActions) footerActions.style.display = confirmMode === 'candidate-selected' ? 'none' : '';
  if (confirmBtn) {
    confirmBtn.textContent = confirmText;
    confirmBtn.dataset.ozonConfirmMode = confirmMode || '';
    confirmBtn.hidden = confirmMode === 'candidate-selected';
    if (confirmMode === 'candidate-selected') {
      confirmBtn.disabled = true;
      confirmBtn.title = '请先勾选商品';
    } else {
      confirmBtn.disabled = false;
      confirmBtn.title = '';
    }
    confirmBtn.onclick = async () => {
      try {
        confirmBtn.disabled = true;
        if (resultEl) resultEl.textContent = '正在提交...';
        await onConfirm?.();
        setOzonMarketingActionModalVisible(false);
      } catch (err) {
        if (resultEl) resultEl.textContent = err.message || '操作失败';
      } finally {
        if (confirmMode === 'candidate-selected') syncOzonMarketingCandidateSelectionUi();
        else confirmBtn.disabled = false;
      }
    };
  }
  setOzonMarketingActionModalVisible(true);
}

function getLoadedMarketingProduct(productId) {
  const products = Array.isArray(ozonState.marketingProducts?.products) ? ozonState.marketingProducts.products : [];
  return products.find(item => String(item.productId || '') === String(productId || '')) || null;
}

function getLoadedDiscountTask(taskId) {
  const tasks = Array.isArray(ozonState.discountTasks?.tasks) ? ozonState.discountTasks.tasks : [];
  return tasks.find(item => String(item.id || '') === String(taskId || '')) || null;
}

function openOzonActivateProductModal(productId) {
  const product = getLoadedMarketingProduct(productId);
  const action = ozonState.marketingSelectedAction || {};
  const displayTitle = getOzonActionDisplayTitle(action);
  if (!product || !action.id) {
    setOzonResult('未找到要报名的商品或活动', 'error');
    return;
  }
  const normalizedProduct = normalizeOzonPromoProductRow(product);
  const suggestedActionPrice = getOzonPromoSuggestedActionPrice(normalizedProduct);
  const suggestedStock = getOzonPromoSubmissionStock(normalizedProduct);
  openOzonMarketingActionModal({
    title: '报名 OZON 活动商品',
    confirmText: '确认报名',
    body: `<div class="ozon-muted">将商品加入活动「${escapeHtml(displayTitle || action.id)}」。请按 OZON 后台建议确认活动价和库存。</div>
      <label>商品 ID<input id="ozonMarketingModalProductId" value="${escapeHtml(normalizedProduct.productId || '')}" readonly></label>
      <label>活动价<input id="ozonMarketingModalActionPrice" type="number" min="0" step="0.01" value="${escapeHtml(suggestedActionPrice ? formatOzonPromoInputNumber(suggestedActionPrice) : '')}"></label>
      <label>活动库存<input id="ozonMarketingModalStock" type="number" min="1" step="1" value="${escapeHtml(suggestedStock || '')}"></label>`,
    onConfirm: async () => {
      const actionPrice = Number(document.getElementById('ozonMarketingModalActionPrice')?.value || 0);
      const stock = Number(document.getElementById('ozonMarketingModalStock')?.value || 0);
      if (!actionPrice || !stock) throw new Error('活动价和库存必须大于 0');
      const confirmed = await showCenterConfirm({ title: '确认报名活动', message: `确认把商品 ${normalizedProduct.productId} 加入活动 ${action.id}？`, confirmText: '确认报名' });
      if (!confirmed) throw new Error('已取消操作');
      const result = await ozonRequest(`/marketing/actions/${encodeURIComponent(action.id)}/products/activate`, {
        method: 'POST',
        body: JSON.stringify({
          ozonAccountId: action.accountId,
          products: [{ product_id: normalizedProduct.productId, action_price: actionPrice, stock }]
        })
      });
      setOzonResult(`报名完成：成功 ${result.acceptedProductIds?.length || result.successCount || 0}，失败 ${result.rejected?.length || result.failCount || 0}`, 'success');
      await loadOzonMarketingProducts(action, ozonState.marketingProductSource || 'candidate');
    }
  });
}

function openOzonDeactivateProductModal(productId) {
  const product = getLoadedMarketingProduct(productId);
  const action = ozonState.marketingSelectedAction || {};
  const displayTitle = getOzonActionDisplayTitle(action);
  if (!product || !action.id) {
    setOzonResult('未找到要移出的商品或活动', 'error');
    return;
  }
  openOzonMarketingActionModal({
    title: '移出 OZON 活动商品',
    confirmText: '确认移出',
    body: `<div class="ozon-muted">将商品从活动「${escapeHtml(displayTitle || action.id)}」移出。</div>
      <div class="ozon-table-wrap"><table class="ozon-table"><tbody>
        <tr><th>商品 ID</th><td>${escapeHtml(product.productId || '--')}</td></tr>
        <tr><th>SKU / Offer</th><td>${escapeHtml(product.sku || product.offerId || '--')}</td></tr>
        <tr><th>活动价</th><td>${escapeHtml(product.actionPrice ? formatOzonAmount(product.actionPrice, product.currencyCode || 'RUB') : '--')}</td></tr>
      </tbody></table></div>`,
    onConfirm: async () => {
      const confirmed = await showCenterConfirm({ title: '确认移出活动', message: `确认把商品 ${product.productId} 从活动 ${action.id} 移出？`, confirmText: '确认移出' });
      if (!confirmed) throw new Error('已取消操作');
      const result = await ozonRequest(`/marketing/actions/${encodeURIComponent(action.id)}/products/deactivate`, {
        method: 'POST',
        body: JSON.stringify({
          ozonAccountId: action.accountId,
          productIds: [product.productId]
        })
      });
      setOzonResult(`移出完成：成功 ${result.acceptedProductIds?.length || result.successCount || 0}，失败 ${result.rejected?.length || result.failCount || 0}`, 'success');
      await loadOzonMarketingProducts(action, normalizeOzonMarketingProductSource(ozonState.marketingProductSource || 'joined'), {
        keepAutoAddDate: Boolean(ozonState.marketingDetailAutoAddDate),
        hydrateDetails: normalizeOzonMarketingProductSource(ozonState.marketingProductSource || 'joined') === 'auto'
      });
    }
  });
}

async function submitOzonDeactivateSelectedProducts() {
  const action = ozonState.marketingSelectedAction || {};
  const displayTitle = getOzonActionDisplayTitle(action);
  if (!action.id || !action.accountId) {
    setOzonResult('请先选择活动和店铺', 'error');
    return;
  }
  const selectedProducts = getOzonMarketingDetailSelectedProducts();
  const productIds = [...new Set(selectedProducts
    .map(product => Number(product.productId || 0))
    .filter(value => Number.isFinite(value) && value > 0))];
  if (!productIds.length) {
    setOzonResult('请先勾选要移出的链接', 'warn');
    syncOzonMarketingDetailSelectionUi();
    return;
  }
  const confirmed = await showCenterConfirm({
    title: '确认移出勾选链接',
    message: `确认把已勾选的 ${productIds.length} 条链接从活动「${displayTitle || action.id}」移出？`,
    confirmText: '确认移出'
  });
  if (!confirmed) return;
  const chunkSize = 100;
  const results = [];
  for (let index = 0; index < productIds.length; index += chunkSize) {
    const chunk = productIds.slice(index, index + chunkSize);
    setOzonResult(`正在移出 ${index + 1}-${index + chunk.length} / ${productIds.length} 条链接...`, 'info');
    const result = await ozonRequest(`/marketing/actions/${encodeURIComponent(action.id)}/products/deactivate`, {
      method: 'POST',
      body: JSON.stringify({
        ozonAccountId: action.accountId,
        productIds: chunk
      })
    });
    results.push(result);
  }
  const result = mergeOzonMarketingResultPayload(results);
  const successCount = result.acceptedProductIds?.length || result.successCount || 0;
  const failCount = result.rejected?.length || result.failCount || 0;
  const failText = Array.isArray(result.rejected) && result.rejected.length
    ? `；失败明细：${result.rejected.slice(0, 3).map(item => item.error || item.reason || item.message || item.product_id || JSON.stringify(item)).join('；')}`
    : '';
  ozonState.marketingDetailSelectedIds = new Set();
  setOzonResult(`移出完成：成功 ${successCount}，失败 ${failCount}${failText}`, failCount > 0 ? 'warn' : 'success');
  const source = ozonState.marketingDetailAutoAddDate ? 'auto' : normalizeOzonMarketingProductSource(ozonState.marketingProductSource || 'joined');
  await loadOzonMarketingProducts(action, source, {
    forceRefresh: true,
    keepAutoAddDate: Boolean(ozonState.marketingDetailAutoAddDate),
    hydrateDetails: true
  });
}

function openOzonDiscountTaskModal(taskId, mode = 'approve') {
  const task = getLoadedDiscountTask(taskId);
  if (!task) {
    setOzonResult('未找到折扣申请', 'error');
    return;
  }
  const approving = mode === 'approve';
  openOzonMarketingActionModal({
    title: approving ? '同意折扣申请' : '拒绝折扣申请',
    confirmText: approving ? '确认同意' : '确认拒绝',
    body: `<div class="ozon-table-wrap" style="margin-bottom:12px;"><table class="ozon-table"><tbody>
        <tr><th>申请 ID</th><td>${escapeHtml(task.id || '--')}</td></tr>
        <tr><th>SKU / Offer</th><td>${escapeHtml(task.sku || task.offerId || '--')}</td></tr>
        <tr><th>原价</th><td>${escapeHtml(task.originalPrice ? formatOzonAmount(task.originalPrice, 'RUB') : '--')}</td></tr>
        <tr><th>申请价</th><td>${escapeHtml(task.requestedPrice ? formatOzonAmount(task.requestedPrice, 'RUB') : '--')}</td></tr>
      </tbody></table></div>
      ${approving ? `<label>批准价格<input id="ozonDiscountModalApprovedPrice" type="number" min="0" step="0.01" value="${escapeHtml(task.requestedPrice || task.approvedPrice || '')}"></label>
      <label>批准数量下限<input id="ozonDiscountModalQtyMin" type="number" min="0" step="1" value="${escapeHtml(task.requestedQuantityMin || task.approvedQuantityMin || 0)}"></label>
      <label>批准数量上限<input id="ozonDiscountModalQtyMax" type="number" min="0" step="1" value="${escapeHtml(task.requestedQuantityMax || task.approvedQuantityMax || 0)}"></label>` : ''}
      <label>卖家备注<input id="ozonDiscountModalComment" value="${approving ? 'ok' : '暂不参与该折扣'}"></label>`,
    onConfirm: async () => {
      const confirmed = await showCenterConfirm({ title: approving ? '确认同意折扣' : '确认拒绝折扣', message: `确认处理折扣申请 ${task.id}？`, confirmText: approving ? '确认同意' : '确认拒绝' });
      if (!confirmed) throw new Error('已取消操作');
      const payloadTask = {
        id: task.id,
        seller_comment: document.getElementById('ozonDiscountModalComment')?.value || ''
      };
      if (approving) {
        payloadTask.approved_price = Number(document.getElementById('ozonDiscountModalApprovedPrice')?.value || 0);
        payloadTask.approved_quantity_min = Number(document.getElementById('ozonDiscountModalQtyMin')?.value || 0);
        payloadTask.approved_quantity_max = Number(document.getElementById('ozonDiscountModalQtyMax')?.value || 0);
        if (!payloadTask.approved_price) throw new Error('批准价格必须大于 0');
      }
      const result = await ozonRequest(`/marketing/discount-tasks/${approving ? 'approve' : 'decline'}`, {
        method: 'POST',
        body: JSON.stringify({
          ozonAccountId: task.accountId,
          tasks: [payloadTask]
        })
      });
      setOzonResult(`${approving ? '同意' : '拒绝'}完成：成功 ${result.successCount || 0}，失败 ${result.failCount || 0}`, 'success');
      await loadOzonDiscountTasks();
    }
  });
}
function ensureOzonDiagnosisModal() {
  let modal = document.getElementById('ozonDiagnosisModal');
  if (modal) return modal;
  modal = document.createElement('div');
  modal.id = 'ozonDiagnosisModal';
  modal.style.cssText = 'position:fixed;inset:0;z-index:10000;display:none;align-items:center;justify-content:center;background:rgba(15,23,42,.45);padding:20px;';
  modal.setAttribute('aria-hidden', 'true');
  modal.innerHTML = `<div style="width:min(980px,96vw);max-height:86vh;overflow:auto;background:#fff;border-radius:8px;box-shadow:0 24px 70px rgba(15,23,42,.28);">
    <div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--border);gap:12px;">
      <h3 style="margin:0;font-size:16px;color:var(--text-primary);">OZON 商品整改方案</h3>
      <button class="btn btn-default btn-sm" type="button" data-ozon-diagnosis-close>关闭</button>
    </div>
    <div id="ozonDiagnosisModalBody" style="padding:16px;"></div>
  </div>`;
  modal.querySelector('[data-ozon-diagnosis-close]')?.addEventListener('click', () => setOzonOrderDetailModalVisible(modal, false));
  modal.addEventListener('click', event => {
    if (event.target === modal) setOzonOrderDetailModalVisible(modal, false);
  });
  document.body.appendChild(modal);
  return modal;
}

function showOzonDiagnosisDetail(item = {}) {
  const modal = ensureOzonDiagnosisModal();
  const body = document.getElementById('ozonDiagnosisModalBody');
  const ui = normalizeOzonProductForUi(item.product || {});
  const diag = item.diagnosis || {};
  const sales = item.sales || {};
  const titleState = getOzonAiTitleState(diag);
  const aiState = getOzonAiDiagnosisState(diag);
  if (!body) return;
  body.innerHTML = `<div class="ozon-card-grid" style="grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin-bottom:14px;">
    <div class="ozon-mini-card"><div class="ozon-mini-label">标题得分</div><div class="ozon-mini-value">${escapeHtml(diag.score ?? 0)}分</div></div>
    <div class="ozon-mini-card"><div class="ozon-mini-label">风险等级</div><div class="ozon-mini-value">${escapeHtml(diag.riskLevel || '低')}</div></div>
    <div class="ozon-mini-card"><div class="ozon-mini-label">近7日订单数</div><div class="ozon-mini-value">${escapeHtml((sales.orderCount7 ?? sales.qty7) || 0)}</div></div>
    <div class="ozon-mini-card"><div class="ozon-mini-label">近28日订单数</div><div class="ozon-mini-value">${escapeHtml((sales.orderCount28 ?? sales.qty28) || 0)}</div></div>
  </div>
  <div class="ozon-table-wrap" style="margin-bottom:14px;"><table class="ozon-table"><tbody>
    <tr><th>原始标题</th><td>${escapeHtml(ui.title)}</td></tr>
    <tr><th>AI建议标题</th><td><div class="ozon-copy-block">${escapeHtml(titleState.title)}</div></td></tr>
    <tr><th>诊断时间</th><td>${escapeHtml(formatOzonDateTime(getOzonAiDiagnosisTime(diag)))}</td></tr>
    <tr><th>AI标题依据</th><td><div class="ozon-copy-block">${escapeHtml(titleState.basis)}</div></td></tr>
    <tr><th>关键词依据</th><td><div class="ozon-copy-block">${escapeHtml(diag.aiKeywordBasis || titleState.basis || '--')}</div></td></tr>
  </tbody></table></div>
  <div class="ozon-card-grid" style="grid-template-columns:repeat(auto-fit,minmax(260px,1fr));">
    <div class="ozon-tool-card"><h4>现存问题</h4><div class="ozon-problem-list ${aiState.unavailable ? 'ozon-ai-unavailable' : ''}">${(aiState.problems || []).map(text => `<span>• ${escapeHtml(text)}</span>`).join('')}</div></div>
    <div class="ozon-tool-card"><h4>优化方案</h4><div class="ozon-problem-list ${aiState.unavailable ? 'ozon-ai-unavailable' : ''}">${(aiState.suggestions || []).map(text => `<span>• ${escapeHtml(text)}</span>`).join('')}</div></div>
    <div class="ozon-tool-card"><h4>优化依据</h4><div class="ozon-problem-list ${aiState.unavailable ? 'ozon-ai-unavailable' : ''}">${(aiState.basis || []).map(text => `<span>• ${escapeHtml(text)}</span>`).join('')}</div></div>
  </div>
  <div class="ozon-actions" style="margin-top:14px;">
    <button class="btn btn-primary btn-sm" type="button" id="ozonCopyDiagnosisTitleBtn" ${titleState.canCopy ? '' : 'disabled'}>复制新标题</button>
    <button class="btn btn-default btn-sm" type="button" id="ozonCopyDiagnosisKeywordsBtn" ${aiState.unavailable ? 'disabled' : ''}>复制关键词依据</button>
  </div>`;
  document.getElementById('ozonCopyDiagnosisTitleBtn')?.addEventListener('click', () => copyOzonText(titleState.canCopy ? titleState.title : ''));
  document.getElementById('ozonCopyDiagnosisKeywordsBtn')?.addEventListener('click', () => copyOzonText(diag.aiKeywordBasis || titleState.basis || ''));
  setOzonOrderDetailModalVisible(modal, true);
}

function getOzonFreightInputs() {
  const readNumberInput = (id) => {
    const raw = document.getElementById(id)?.value;
    const text = String(raw ?? '').trim();
    if (!text) return NaN;
    return Number(text);
  };
  return {
    currency: 'RUB',
    value: readNumberInput('ozonFreightValueInput'),
    weightGrams: readNumberInput('ozonFreightWeightInput'),
    length: readNumberInput('ozonFreightLengthInput'),
    width: readNumberInput('ozonFreightWidthInput'),
    height: readNumberInput('ozonFreightHeightInput')
  };
}

function hasCompleteOzonFreightInputs(input = getOzonFreightInputs()) {
  return ['value', 'weightGrams', 'length', 'width', 'height'].every(key => {
    const value = Number(input[key]);
    return Number.isFinite(value) && value > 0;
  });
}

function formatOzonFreightNumber(value, digits = 2) {
  if (typeof value !== 'number' || !Number.isFinite(value)) return escapeHtml(value || '--');
  return value.toLocaleString('zh-CN', { minimumFractionDigits: digits, maximumFractionDigits: digits });
}

function setOzonFreightCell(id, value, state = '') {
  const el = document.getElementById(id);
  if (!el) return;
  el.textContent = value;
  el.classList.remove('ozon-freight-status-ok', 'ozon-freight-status-bad');
  if (state === 'ok') el.classList.add('ozon-freight-status-ok');
  if (state === 'bad') el.classList.add('ozon-freight-status-bad');
}

function getOzonFreightServiceResult(services, serviceCode) {
  return (services || []).find(row => String(row.serviceCode || '').toLowerCase() === String(serviceCode).toLowerCase()) || null;
}

function renderOzonFreightServiceCells(serviceCode, weightId, feeId, services) {
  const row = getOzonFreightServiceResult(services, serviceCode);
  if (!row) {
    setOzonFreightCell(weightId, '--');
    setOzonFreightCell(feeId, '--');
    return;
  }
  const unavailable = row.statusText === '未上线' || !row.available;
  const invalidSize = row.statusText && row.statusText !== '可计算' && row.statusText !== '未上线';
  if (invalidSize) {
    setOzonFreightCell(weightId, '--', 'bad');
    setOzonFreightCell(feeId, row.statusText || '--', 'bad');
    return;
  }
  if (unavailable) {
    setOzonFreightCell(weightId, '未上线', 'bad');
    setOzonFreightCell(feeId, '未上线', 'bad');
    return;
  }
  setOzonFreightCell(weightId, formatOzonFreightNumber(Number(row.billWeightKg), 3), 'ok');
  setOzonFreightCell(feeId, formatOzonFreightNumber(Number(row.fee), 2), 'ok');
}

function renderOzonFreightCalculatorResult(result = {}) {
  const groupEl = document.getElementById('ozonFreightGroupResult');
  const sizeEl = document.getElementById('ozonFreightSizeResult');
  const volumeEl = document.getElementById('ozonFreightVolumetricResult');
  const meta = document.getElementById('ozonFreightMeta');
  const groupText = result.groupInfo?.groupCode
    ? result.groupInfo.groupCode
    : result.groupInfo?.status || '--';
  setOzonFreightCell('ozonFreightGroupResult', groupText, result.groupInfo?.groupCode ? 'ok' : 'bad');
  const sizeStatus = result.sizeInfo?.status || '--';
  if (sizeEl) {
    sizeEl.textContent = sizeStatus;
    sizeEl.classList.toggle('ozon-freight-status-ok', sizeStatus === '限定区间内');
    sizeEl.classList.toggle('ozon-freight-status-bad', Boolean(sizeStatus && sizeStatus !== '限定区间内' && sizeStatus !== '--'));
  }
  if (volumeEl) {
    volumeEl.textContent = sizeStatus === '限定区间内' ? (result.isVolumetric ? '是' : '否') : '--';
    volumeEl.classList.toggle('ozon-freight-status-ok', sizeStatus === '限定区间内');
    volumeEl.classList.toggle('ozon-freight-status-bad', false);
  }
  if (meta) {
    const ruleSetName = result.ruleSet?.name || '当前启用规则';
    const grams = result.input?.weightGrams || '';
    meta.textContent = `${ruleSetName}：重量向上取整为 ${grams || '--'} 克，规则来自管理后台。`;
  }
  const services = Array.isArray(result.services) ? result.services : [];
  if (!services.length) {
    if (meta && result.groupInfo?.status) meta.textContent = result.groupInfo.status;
    setOzonFreightCell('ozonFreightExpressWeight', '--');
    setOzonFreightCell('ozonFreightExpressFee', '--');
    setOzonFreightCell('ozonFreightStandardWeight', '--');
    setOzonFreightCell('ozonFreightStandardFee', '--');
    setOzonFreightCell('ozonFreightEconomyWeight', '--');
    setOzonFreightCell('ozonFreightEconomyFee', '--');
    return;
  }
  renderOzonFreightServiceCells('Express', 'ozonFreightExpressWeight', 'ozonFreightExpressFee', services);
  renderOzonFreightServiceCells('Standard', 'ozonFreightStandardWeight', 'ozonFreightStandardFee', services);
  renderOzonFreightServiceCells('Economy', 'ozonFreightEconomyWeight', 'ozonFreightEconomyFee', services);
}

function resetOzonFreightResult() {
  [
    'ozonFreightGroupResult',
    'ozonFreightSizeResult',
    'ozonFreightVolumetricResult',
    'ozonFreightExpressWeight',
    'ozonFreightExpressFee',
    'ozonFreightStandardWeight',
    'ozonFreightStandardFee',
    'ozonFreightEconomyWeight',
    'ozonFreightEconomyFee'
  ].forEach(id => {
    const el = document.getElementById(id);
    if (el) {
      el.textContent = '--';
      el.classList.remove('ozon-freight-status-ok', 'ozon-freight-status-bad');
    }
  });
  const meta = document.getElementById('ozonFreightMeta');
  if (meta) meta.textContent = '请输入货值、重量和三边尺寸后点击计算。';
}

async function renderOzonFreightCalculator() {
  const meta = document.getElementById('ozonFreightMeta');
  if (!hasCompleteOzonFreightInputs()) {
    resetOzonFreightResult();
    if (meta) meta.textContent = '请输入完整的货物价值、重量和三边尺寸后再计算。';
    setOzonFreightCell('ozonFreightExpressFee', '参数不完整', 'bad');
    return;
  }
  if (meta) meta.textContent = '正在读取管理后台运费规则并计算...';
  [
    'ozonFreightGroupResult',
    'ozonFreightSizeResult',
    'ozonFreightVolumetricResult',
    'ozonFreightExpressWeight',
    'ozonFreightExpressFee',
    'ozonFreightStandardWeight',
    'ozonFreightStandardFee',
    'ozonFreightEconomyWeight',
    'ozonFreightEconomyFee'
  ].forEach(id => setOzonFreightCell(id, id.endsWith('Fee') || id.endsWith('Weight') ? '计算中...' : '--'));
  try {
    const result = await ozonRequest('/freight/calculate', {
      method: 'POST',
      body: JSON.stringify(getOzonFreightInputs())
    });
    renderOzonFreightCalculatorResult(result);
  } catch (err) {
    resetOzonFreightResult();
    if (meta) meta.textContent = err.message || '运费计算失败';
    setOzonFreightCell('ozonFreightExpressFee', err.message || '运费计算失败', 'bad');
  }
}

function resetOzonFreightCalculator() {
  const values = {
    ozonFreightCurrencyInput: 'RUB',
    ozonFreightValueInput: '',
    ozonFreightWeightInput: '',
    ozonFreightLengthInput: '',
    ozonFreightWidthInput: '',
    ozonFreightHeightInput: ''
  };
  Object.entries(values).forEach(([id, value]) => {
    const el = document.getElementById(id);
    if (el) el.value = value;
  });
  resetOzonFreightResult();
}

let ozonFreightAutoCalcTimer = null;

function scheduleOzonFreightAutoCalculate() {
  window.clearTimeout(ozonFreightAutoCalcTimer);
  const input = getOzonFreightInputs();
  if (!hasCompleteOzonFreightInputs(input)) {
    resetOzonFreightResult();
    return;
  }
  ozonFreightAutoCalcTimer = window.setTimeout(() => {
    withOzonButtonLoading('ozonFreightCalcBtn', '计算中...', renderOzonFreightCalculator)
      .catch(err => setOzonResult(err.message || '运费计算失败', 'error'));
  }, 450);
}

function bindOzonFreightAutoCalculate() {
  [
    'ozonFreightValueInput',
    'ozonFreightWeightInput',
    'ozonFreightLengthInput',
    'ozonFreightWidthInput',
    'ozonFreightHeightInput'
  ].forEach(id => {
    const el = document.getElementById(id);
    if (!el || el.dataset.ozonFreightAutoBound === '1') return;
    el.dataset.ozonFreightAutoBound = '1';
    el.addEventListener('input', scheduleOzonFreightAutoCalculate);
    el.addEventListener('change', scheduleOzonFreightAutoCalculate);
  });
}

function renderOzonFinance() {
  syncOzonAccountSelects();
  const data = ozonState.finance || {};
  const summaryWrap = document.getElementById('ozonFinanceSummary');
  const payoutWrap = document.getElementById('ozonFinancePayoutList');
  const accounts = Array.isArray(data.accounts) ? data.accounts : [];
  const summaries = Array.isArray(data.summaries) ? data.summaries : [];
  if (!hasOzonAccounts()) {
    if (summaryWrap) summaryWrap.innerHTML = renderOzonNeedAccountEmpty('还没有绑定 OZON 店铺。绑定后可查看当前余额和付款明细。');
    if (payoutWrap) payoutWrap.innerHTML = renderOzonNeedAccountEmpty('还没有绑定 OZON 店铺，暂无付款明细。');
    return;
  }
  if (ozonState.financeLoading && !Array.isArray(data.payouts) && !Array.isArray(data.summaries)) {
    if (summaryWrap) summaryWrap.innerHTML = renderOzonLoadingEmpty('正在读取财务数据...');
    if (payoutWrap) payoutWrap.innerHTML = renderOzonLoadingEmpty('正在读取付款明细...');
    return;
  }
  const rows = summaries.length ? summaries : accounts.map(account => ({
    accountId: account.id,
    accountTitle: account.title || account.clientId || 'OZON 店铺',
    balance: null,
    currency: account.currencyCode || 'RUB',
    statusText: data.sourceStatusText || '等待财务数据'
  }));
  if (summaryWrap) {
    summaryWrap.innerHTML = rows.length
      ? rows.map(row => `<div class="ozon-finance-card">
          <span>${escapeHtml(row.accountTitle || 'OZON 店铺')}</span>
          <strong>${row.balance === null || row.balance === undefined ? '--' : escapeHtml(formatOzonAmount(row.balance, row.currency || 'RUB'))}</strong>
          <div class="ozon-list-meta">
            当前余额 · ${escapeHtml(row.currency || 'RUB')}<br>
            期初：${escapeHtml(row.beginBalance === null || row.beginBalance === undefined ? '--' : formatOzonAmount(row.beginBalance, row.currency || 'RUB'))}<br>
            应计：${escapeHtml(row.invoiceTransfer === null || row.invoiceTransfer === undefined ? '--' : formatOzonAmount(row.invoiceTransfer, row.currency || 'RUB'))}<br>
            已支付：${escapeHtml(row.paymentAmount === null || row.paymentAmount === undefined ? '--' : formatOzonAmount(row.paymentAmount, row.currency || 'RUB'))}<br>
            ${escapeHtml(row.statusText || data.sourceStatusText || '等待财务数据')}
          </div>
        </div>`).join('')
      : `<div class="ozon-empty">${escapeHtml(data.sourceStatusText || 'OZON 财务接口已连接，当前时间范围暂无余额数据。')}</div>`;
  }
  const errorRows = Array.isArray(data.errors) ? data.errors : [];
  const payouts = Array.isArray(data.payouts) ? data.payouts : [];
  if (payoutWrap) {
    payoutWrap.innerHTML = payouts.length
      ? `<table class="ozon-table"><thead><tr><th>店铺</th><th>付款类型</th><th>金额</th><th>付款状态</th><th>计划付款日期</th><th>付款发放日期</th><th>时期</th><th>付款文件编号</th><th>支付方式</th></tr></thead><tbody>${payouts.map(row => {
          const periodText = row.period?.begin || row.period?.end
            ? `${formatOzonDateTime(row.period.begin).split(' ')[0] || '--'} - ${formatOzonDateTime(row.period.end).split(' ')[0] || '--'}`
            : '--';
          return `<tr>
            <td>${escapeHtml(row.accountTitle || 'OZON')}</td>
            <td>${escapeHtml(row.type || '其他付款')}</td>
            <td>${escapeHtml(formatOzonAmount(row.amount || 0, row.currency || 'RUB'))}</td>
            <td>${escapeHtml(row.status || '--')}</td>
            <td>${escapeHtml(formatOzonDateTime(row.plannedAt) || '--')}</td>
            <td>${escapeHtml(formatOzonDateTime(row.paidAt) || '--')}</td>
            <td>${escapeHtml(periodText)}</td>
            <td>${escapeHtml(row.documentNumber || '--')}</td>
            <td>${escapeHtml(row.paymentMethod || '--')}</td>
          </tr>`;
        }).join('')}</tbody></table>`
      : `<div class="ozon-empty">${escapeHtml(data.sourceStatusText || 'OZON 官方财务接口当前未返回付款明细字段。')}${errorRows.length ? `<br>${escapeHtml(errorRows.map(item => `${item.accountTitle || item.accountId}: ${item.message}`).join('；'))}` : ''}</div>`;
  }
}

function getOzonMessageCacheKey(message = {}) {
  const chatId = String(message.chatId || message.id || '').trim();
  const accountId = String(message.accountId || '').trim();
  return accountId && chatId ? `${accountId}::${chatId}` : chatId;
}

function ensureOzonMessageHistoryState(message = {}) {
  const key = getOzonMessageCacheKey(message);
  if (!key) return null;
  if (!ozonState.messageHistoryById || typeof ozonState.messageHistoryById !== 'object') {
    ozonState.messageHistoryById = {};
  }
  if (!ozonState.messageHistoryById[key]) {
    ozonState.messageHistoryById[key] = {
      loaded: true,
      messages: [],
      sourceStatusText: ''
    };
  }
  if (!Array.isArray(ozonState.messageHistoryById[key].messages)) {
    ozonState.messageHistoryById[key].messages = [];
  }
  return ozonState.messageHistoryById[key];
}

function mergeOzonLocalChatMessages(apiMessages = [], localMessages = []) {
  const merged = [];
  const seen = new Set();
  const add = (row = {}) => {
    const id = String(row.localClientId || row.id || '').trim();
    const textKey = `${row.side || ''}|${row.text || ''}|${row.createdAt || ''}`;
    const key = id || textKey;
    if (key && seen.has(key)) return;
    if (key) seen.add(key);
    merged.push(row);
  };
  apiMessages.forEach(add);
  localMessages.forEach(add);
  return merged.sort((a, b) => {
    const at = new Date(a.createdAt || 0).getTime() || 0;
    const bt = new Date(b.createdAt || 0).getTime() || 0;
    if (at !== bt) return at - bt;
    return String(a.id || '').localeCompare(String(b.id || ''));
  });
}

function getOldestOfficialOzonChatMessageId(rows = []) {
  const officialRows = (Array.isArray(rows) ? rows : [])
    .filter(row => !row.localClientId && /^\d+$/.test(String(row.id || '').trim()) && String(row.id || '').trim() !== '0')
    .sort((a, b) => {
      const at = new Date(a.createdAt || 0).getTime() || 0;
      const bt = new Date(b.createdAt || 0).getTime() || 0;
      if (at !== bt) return at - bt;
      return Number(a.id || 0) - Number(b.id || 0);
    });
  return String(officialRows[0]?.id || '').trim();
}

function isOzonChatAttachmentImage(file = {}) {
  const value = String(file.url || file.title || '').toLowerCase();
  return /\.(png|jpe?g|webp|gif|bmp)(\?|#|$)/i.test(value) || /^image\//i.test(String(file.mimeType || file.type || ''));
}

function renderOzonChatAttachments(attachments = []) {
  const rows = Array.isArray(attachments) ? attachments.filter(file => file?.url) : [];
  if (!rows.length) return '';
  return `<div class="ozon-chat-files">${rows.map((file) => {
    const url = escapeHtml(file.url || '');
    const title = escapeHtml(file.title || file.url || '附件');
    if (isOzonChatAttachmentImage(file)) {
      return `<a class="ozon-chat-file is-image" href="${url}" target="_blank" rel="noopener" title="${title}"><img src="${url}" alt="${title}" loading="lazy"><span>${title}</span></a>`;
    }
    return `<a class="ozon-chat-file" href="${url}" target="_blank" rel="noopener" download title="${title}"><span>附件</span><strong>${title}</strong></a>`;
  }).join('')}</div>`;
}

function renderOzonChatLocalStatus(row = {}) {
  const status = normalizeOzonText(row.localStatus || '');
  const failed = normalizeOzonText(row.sendStatus || '').toLowerCase() === 'failed' || /失败/.test(status);
  if (!status && !failed) return '';
  return `<div class="ozon-chat-local-status${failed ? ' is-error' : ''}">
    <span>${escapeHtml(status || '发送失败')}</span>
    ${failed && normalizeOzonText(row.text || '') ? `<button type="button" data-ozon-retry-message-id="${escapeHtml(row.localClientId || row.id || '')}">重试</button>` : ''}
  </div>`;
}

function appendOzonLocalChatMessage(message = {}, row = {}) {
  const state = ensureOzonMessageHistoryState(message);
  if (!state) return null;
  const local = Array.isArray(state.localMessages) ? state.localMessages : [];
  const normalized = {
    id: row.id || `local-${Date.now()}`,
    localClientId: row.localClientId || row.id || `local-${Date.now()}`,
    chatId: message.chatId || message.id || '',
    accountId: message.accountId || '',
    side: row.side || 'seller',
    authorName: row.authorName || '店铺/客服',
    authorType: row.authorType || 'seller',
    text: row.text || '',
    attachments: Array.isArray(row.attachments) ? row.attachments : [],
    createdAt: row.createdAt || new Date().toISOString(),
    localStatus: row.localStatus || '',
    sendStatus: row.sendStatus || ''
  };
  state.localMessages = [...local, normalized];
  state.messages = mergeOzonLocalChatMessages(state.messages || [], state.localMessages);
  state.loaded = true;
  state.loading = false;
  state.error = '';
  return normalized;
}

const OZON_CHAT_QUICK_REPLIES = [
  'Здравствуйте! Спасибо за обращение. Сейчас проверим информацию и ответим вам.',
  'Здравствуйте! Пожалуйста, уточните номер заказа или пришлите фото, чтобы мы быстрее помогли.',
  'Спасибо за ожидание. Мы передали вопрос в обработку и вернемся с ответом как можно скорее.'
];

function setOzonChatSendStatus(message = {}, text = '', type = '') {
  const key = getOzonMessageCacheKey(message);
  if (!key) return;
  ozonState.messageSendingById[key] = {
    text,
    type,
    updatedAt: Date.now()
  };
  const statusEl = document.getElementById('ozonChatSendStatus');
  if (statusEl) {
    statusEl.textContent = text;
    statusEl.classList.toggle('is-error', type === 'error');
    statusEl.classList.toggle('is-success', type === 'success');
  }
}

function getOzonChatReplyInputState() {
  const input = document.getElementById('ozonChatReplyInput');
  if (!input) return { active: false, value: '', dirty: false };
  const value = String(input.value || '');
  const active = document.activeElement === input;
  return {
    active,
    value,
    dirty: Boolean(value.trim())
  };
}

function isOzonChatReplyEditing() {
  const state = getOzonChatReplyInputState();
  return state.active || state.dirty;
}

function persistOzonChatReplyDraft(message = {}) {
  const key = getOzonMessageCacheKey(message);
  if (!key) return;
  if (!ozonState.messageReplyDraftById || typeof ozonState.messageReplyDraftById !== 'object') {
    ozonState.messageReplyDraftById = {};
  }
  const state = getOzonChatReplyInputState();
  ozonState.messageReplyDraftById[key] = state.value;
}

function persistCurrentOzonChatReplyDraft() {
  const key = document.getElementById('ozonMessageDetail')?.dataset?.ozonMessageKey || '';
  if (!key) return;
  if (!ozonState.messageReplyDraftById || typeof ozonState.messageReplyDraftById !== 'object') {
    ozonState.messageReplyDraftById = {};
  }
  ozonState.messageReplyDraftById[key] = getOzonChatReplyInputState().value;
}

function getOzonChatReplyDraft(message = {}) {
  const key = getOzonMessageCacheKey(message);
  if (!key) return '';
  const state = getOzonChatReplyInputState();
  const renderedKey = document.getElementById('ozonMessageDetail')?.dataset?.ozonMessageKey || '';
  if (document.activeElement?.id === 'ozonChatReplyInput' && renderedKey === key) return state.value;
  return String(ozonState.messageReplyDraftById?.[key] || '');
}

function clearOzonChatReplyDraft(message = {}) {
  const key = getOzonMessageCacheKey(message);
  if (key && ozonState.messageReplyDraftById) {
    delete ozonState.messageReplyDraftById[key];
  }
}

function shouldSkipOzonMessageDetailRerender(message = {}, options = {}) {
  if (options.forceRender) return false;
  const key = getOzonMessageCacheKey(message);
  if (!key) return false;
  const detail = document.getElementById('ozonMessageDetail');
  const renderedKey = detail?.dataset?.ozonMessageKey || '';
  if (renderedKey !== key) return false;
  return isOzonChatReplyEditing();
}

function getSelectedOzonMessage() {
  const messages = Array.isArray(ozonState.messages?.messages) ? ozonState.messages.messages : [];
  return messages.find(item => String(item.id || '') === String(ozonState.selectedMessageId || '')) || null;
}

function normalizeOzonMessageCategory(value = '') {
  const category = normalizeOzonText(value).toLowerCase();
  return Object.prototype.hasOwnProperty.call(OZON_MESSAGE_CATEGORIES, category) ? category : '';
}

function getOzonMessageCategory(item = {}) {
  const category = normalizeOzonMessageCategory(item.category || item.messageCategory || item.message_category);
  const type = normalizeOzonText(item.type).toLowerCase();
  if (type === 'notification' && item.source !== 'push_notification') return 'notification';
  if (category) return category;
  if (type === 'buyer') return 'buyer';
  if (type === 'service') return 'service';
  return 'main';
}

function getOzonMessageCategoryLabel(item = {}) {
  const category = getOzonMessageCategory(item);
  return OZON_MESSAGE_CATEGORIES[category] || item.categoryLabel || item.typeLabel || '消息';
}

function getOzonEmptyCategoryCountMap() {
  return Object.keys(OZON_MESSAGE_CATEGORIES).reduce((acc, key) => {
    acc[key] = 0;
    return acc;
  }, {});
}

function getOzonMessageUnreadCount(item = {}) {
  return Math.max(0, Number(item?.unreadCount || 0));
}

function getOzonLocalCategoryUnreadCounts(messages = []) {
  return messages.reduce((acc, item) => {
    const unreadCount = getOzonMessageUnreadCount(item);
    if (!unreadCount) return acc;
    const category = getOzonMessageCategory(item);
    acc[category] = (acc[category] || 0) + unreadCount;
    return acc;
  }, getOzonEmptyCategoryCountMap());
}

function getOzonVisibleMessages() {
  return Array.isArray(ozonState.messages?.messages) ? ozonState.messages.messages : [];
}

function getOzonCurrentCategoryUnreadCounts() {
  const serverCounts = ozonState.messages?.categoryUnreadCounts;
  if (serverCounts && typeof serverCounts === 'object') {
    const counts = getOzonEmptyCategoryCountMap();
    Object.entries(serverCounts).forEach(([key, value]) => {
      const category = normalizeOzonMessageCategory(key);
      if (!category) return;
      counts[category] = Math.max(0, Number(value || 0));
    });
    const overrides = ozonState.messageReadOverrideByKey || {};
    Object.values(overrides).forEach(item => {
    const category = normalizeOzonMessageCategory(item?.category || '');
    const unreadCount = Math.max(1, Number(item?.unreadCount || 0));
    if (!category) return;
    counts[category] = Math.max(0, Number(counts[category] || 0) - unreadCount);
  });
  return counts;
}
  return getOzonLocalCategoryUnreadCounts(applyOzonLocalReadOverrides(getOzonVisibleMessages()));
}

function getOzonServerAccountUnreadCounts() {
  const source = ozonState.messages?.accountUnreadCounts || {};
  if (!source || typeof source !== 'object') return new Map();
  return new Map(Object.entries(source).map(([accountId, value]) => {
    const row = value && typeof value === 'object' ? value : {};
    return [String(accountId), {
      count: Number(row.count || row.unreadChat || 0),
      unreadChat: Number(row.unreadChat || row.count || 0),
      latestAt: row.latestAt || '',
      categoryCounts: row.categoryCounts || {}
    }];
  }));
}

function applyOzonReadOverridesToAccountBadgeMap(map = new Map()) {
  const overrides = ozonState.messageReadOverrideByKey || {};
  Object.values(overrides).forEach(item => {
    const accountId = normalizeOzonText(item?.accountId || '');
    if (!accountId || !map.has(accountId)) return;
    const badge = map.get(accountId) || {};
    const unreadCount = Math.max(1, Number(item?.unreadCount || 0));
    const nextUnread = Math.max(0, Number(badge.unreadChat || badge.count || 0) - unreadCount);
    const category = normalizeOzonMessageCategory(item?.category || '');
    const categoryCounts = { ...(badge.categoryCounts || {}) };
    if (category) categoryCounts[category] = Math.max(0, Number(categoryCounts[category] || 0) - unreadCount);
    map.set(accountId, {
      ...badge,
      count: nextUnread,
      unreadChat: nextUnread,
      categoryCounts
    });
  });
  return map;
}

function applyOzonLocalReadOverrides(messages = []) {
  const overrides = ozonState.messageReadOverrideByKey || {};
  const now = Date.now();
  Object.keys(overrides).forEach(key => {
    if (now - Number(overrides[key]?.readAt || 0) > 2 * 60 * 1000) delete overrides[key];
  });
  messages.forEach(item => {
    const key = getOzonMessageCacheKey(item);
    if (!key || !overrides[key]) return;
    item.unreadCount = 0;
    item.firstUnreadMessageId = '';
    item.notificationEventLabel = item.notificationEventLabel === '新聊天消息' ? '' : item.notificationEventLabel;
  });
  ozonState.messageReadOverrideByKey = overrides;
  return messages;
}

function reconcileOzonReadOverridesFromServer(messages = []) {
  const overrides = ozonState.messageReadOverrideByKey || {};
  messages.forEach(item => {
    const key = getOzonMessageCacheKey(item);
    if (key && overrides[key] && Number(item.unreadCount || 0) <= 0) delete overrides[key];
  });
  ozonState.messageReadOverrideByKey = overrides;
  return messages;
}

function markOzonMessageLocallyRead(message = {}) {
  const key = getOzonMessageCacheKey(message);
  if (!key) return false;
  const wasUnread = Number(message.unreadCount || 0) > 0;
  if (!ozonState.messageReadOverrideByKey || typeof ozonState.messageReadOverrideByKey !== 'object') {
    ozonState.messageReadOverrideByKey = {};
  }
  if (wasUnread) {
    ozonState.messageReadOverrideByKey[key] = {
      readAt: Date.now(),
      accountId: normalizeOzonText(message.accountId || ''),
      category: getOzonMessageCategory(message),
      unreadCount: Math.max(1, Number(message.unreadCount || 0))
    };
  }
  let changed = false;
  const rows = Array.isArray(ozonState.messages?.messages) ? ozonState.messages.messages : [];
  rows.forEach(row => {
    if (getOzonMessageCacheKey(row) === key && Number(row.unreadCount || 0) > 0) {
      row.unreadCount = 0;
      row.firstUnreadMessageId = '';
      row.notificationEventLabel = row.notificationEventLabel === '新聊天消息' ? '' : row.notificationEventLabel;
      changed = true;
    }
  });
  return changed;
}

function fileToBase64Content(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      const value = String(reader.result || '');
      resolve(value.includes(',') ? value.split(',').pop() : value);
    };
    reader.onerror = () => reject(reader.error || new Error('文件读取失败'));
    reader.readAsDataURL(file);
  });
}

function isGenericOzonChatTitle(value = '') {
  return /^OZON\s*会话\s+[a-z0-9-]+$/i.test(normalizeOzonText(value));
}

function getOzonDisplayMessageTitle(message = {}) {
  const title = normalizeOzonText(message.buyerName || message.title || '');
  if (title && !isGenericOzonChatTitle(title)) return title;
  if (message.chatId || message.id) {
    const category = getOzonMessageCategory(message);
    const label = category === 'buyer' ? '买家' : (OZON_MESSAGE_CATEGORIES[category] || message.typeLabel || '消息');
    return `${label} · ${String(message.chatId || message.id).slice(-8)}`;
  }
  return 'OZON 会话';
}

function inferOzonBuyerNameFromHistory(rows = []) {
  const buyer = rows.find(row => String(row.side || '') === 'buyer' && normalizeOzonText(row.authorName) && !['买家', '客户', '系统'].includes(normalizeOzonText(row.authorName)));
  return normalizeOzonText(buyer?.authorName || '');
}

function updateOzonMessageRowFromHistory(message = {}, historyRows = []) {
  const key = getOzonMessageCacheKey(message);
  if (!key || !Array.isArray(ozonState.messages?.messages)) return false;
  const buyerName = inferOzonBuyerNameFromHistory(historyRows);
  const lastRow = historyRows.length ? historyRows[historyRows.length - 1] : null;
  let changed = false;
  ozonState.messages.messages.forEach(row => {
    if (getOzonMessageCacheKey(row) !== key) return;
    if (buyerName && (!row.buyerName || isGenericOzonChatTitle(row.title))) {
      row.buyerName = buyerName;
      row.title = buyerName;
      changed = true;
    }
    if (lastRow?.text && row.preview !== lastRow.text) {
      row.preview = lastRow.text;
      row.content = lastRow.text;
      changed = true;
    }
    if (lastRow?.createdAt && row.updatedAt !== lastRow.createdAt) {
      row.updatedAt = lastRow.createdAt;
      changed = true;
    }
    if (lastRow?.side && row.lastSide !== lastRow.side) {
      row.lastSide = lastRow.side;
      changed = true;
    }
  });
  return changed;
}

function renderOzonChatTextContent(text = '') {
  const value = String(text || '');
  if (!value) return escapeHtml('[非文本消息]');
  const imagePattern = /!\[[^\]]*]\((https?:\/\/[^)\s]+)\)/g;
  let cursor = 0;
  let output = '';
  let match = null;
  while ((match = imagePattern.exec(value)) !== null) {
    const before = value.slice(cursor, match.index);
    if (before) output += escapeHtml(before).replace(/\n/g, '<br>');
    const url = match[1];
    output += `<a class="ozon-chat-image-link" href="${escapeHtml(url)}" target="_blank" rel="noopener"><img src="${escapeHtml(url)}" alt="OZON 聊天图片" loading="lazy" /></a>`;
    cursor = match.index + match[0].length;
  }
  const rest = value.slice(cursor);
  if (rest) output += escapeHtml(rest).replace(/\n/g, '<br>');
  return output || escapeHtml(value).replace(/\n/g, '<br>');
}

function isOzonMessagesTabActive() {
  return isOzonWorkspace() && document.querySelector('.section.active')?.id === 'ozonMessages' && !document.hidden;
}

function stopOzonMessageRealtimeSync() {
  if (ozonState.messagePollingTimer) {
    window.clearInterval(ozonState.messagePollingTimer);
    ozonState.messagePollingTimer = null;
  }
  if (ozonState.messageEventSource) {
    try {
      ozonState.messageEventSource.close();
    } catch (_) {}
    ozonState.messageEventSource = null;
  }
  ozonState.messagePollInFlight = false;
}

async function refreshOzonActiveMessageSilently() {
  if (!isOzonMessagesTabActive()) return;
  if (isOzonChatReplyEditing()) return;
  const current = getSelectedOzonMessage();
  if (!current) return;
  const key = getOzonMessageCacheKey(current);
  const state = key ? ozonState.messageHistoryById?.[key] : null;
  const lastHistorySyncAt = current.lastHistorySyncAt || current.last_history_sync_at || state?.loadedAt || '';
  const updatedAt = current.updatedAt || current.lastNotificationAt || current.createdAt || '';
  if (state?.loaded && lastHistorySyncAt && updatedAt) {
    const lastSyncMs = new Date(lastHistorySyncAt).getTime() || 0;
    const updatedMs = new Date(updatedAt).getTime() || 0;
    if (lastSyncMs >= updatedMs) return;
    if (state?.syncing && Date.now() - Number(state.syncingAt || 0) < 30 * 1000) return;
  }
  try {
    await loadOzonMessageHistory(current, { silent: true, keepScroll: true, force: !state?.loaded, sync: true });
  } catch (err) {
    console.warn('[小青蛙电商助手][OZON] 会话实时同步失败:', err?.message || err);
  }
}

function applyOzonMessageDeltaResult(result = {}) {
  ozonState.messageChangesSince = result.serverTime || new Date().toISOString();
  if (result.categoryCounts && ozonState.messages) ozonState.messages.categoryCounts = result.categoryCounts;
  if (result.categoryUnreadCounts && ozonState.messages) ozonState.messages.categoryUnreadCounts = result.categoryUnreadCounts;
  if (result.accountUnreadCounts && ozonState.messages) ozonState.messages.accountUnreadCounts = result.accountUnreadCounts;
  const activeAccountId = getOzonSelectedMessageAccountId();
  const activeCategory = normalizeOzonMessageCategory(ozonState.messageType || '') || 'buyer';
  let rowChanged = false;
  if (Array.isArray(result.messages) && result.messages.length && ozonState.messages) {
    reconcileOzonReadOverridesFromServer(result.messages);
    const existing = Array.isArray(ozonState.messages.messages) ? ozonState.messages.messages : [];
    const byKey = new Map(existing.map(item => [getOzonMessageCacheKey(item), item]));
    result.messages.forEach(item => {
      if (activeAccountId && String(item.accountId || '') !== String(activeAccountId)) return;
      if (getOzonMessageCategory(item) !== activeCategory) return;
      const key = getOzonMessageCacheKey(item);
      if (!key) return;
      const previous = byKey.get(key) || {};
      const next = { ...previous, ...item };
      if (JSON.stringify(previous) !== JSON.stringify(next)) rowChanged = true;
      byKey.set(key, next);
    });
    ozonState.messages.messages = Array.from(byKey.values()).sort((a, b) => {
      const bt = new Date(b.updatedAt || b.lastNotificationAt || b.createdAt || 0).getTime() || 0;
      const at = new Date(a.updatedAt || a.lastNotificationAt || a.createdAt || 0).getTime() || 0;
      return bt - at;
    });
    reconcileOzonReadOverridesFromServer(ozonState.messages.messages);
  }
  applyOzonLocalReadOverrides(getOzonVisibleMessages());
  updateOzonMessageControls();
  const shouldRender = !isOzonChatReplyEditing() && rowChanged;
  if (shouldRender) renderOzonMessages();
  return shouldRender;
}

async function refreshOzonMessageListSilently() {
  if (!isOzonMessagesTabActive()) return;
  const accountId = getOzonSelectedMessageAccountId();
  const since = ozonState.messageChangesSince || new Date(Date.now() - 60 * 1000).toISOString();
  const params = new URLSearchParams({ since });
  if (accountId) params.set('ozonAccountId', accountId);
  try {
    const result = await ozonRequest(`/messages/changes?${params.toString()}`, {
      cache: false,
      skipCache: true
    });
    applyOzonMessageDeltaResult(result);
  } catch (err) {
    console.warn('[小青蛙电商助手][OZON] 消息增量读取失败:', err?.message || err);
  }
}

function startOzonMessageEventStream() {
  if (!window.EventSource || !isOzonMessagesTabActive()) return false;
  if (ozonState.messageEventSource) return true;
  const token = localStorage.getItem('temuServerToken') || '';
  if (!token) return false;
  const accountId = getOzonSelectedMessageAccountId();
  const params = new URLSearchParams({ token });
  if (accountId) params.set('ozonAccountId', accountId);
  if (ozonState.messageChangesSince) params.set('since', ozonState.messageChangesSince);
  try {
    const source = new EventSource(`${SERVER_URL}/ozon/messages/stream?${params.toString()}`);
    ozonState.messageEventSource = source;
    source.__ozonInitialSyncSeen = false;
    source.addEventListener('message-sync', (event) => {
      try {
        const result = JSON.parse(event.data || '{}');
        if (result?.ok !== false) {
          if (!source.__ozonInitialSyncSeen) {
            source.__ozonInitialSyncSeen = true;
            ozonState.messageChangesSince = result.serverTime || new Date().toISOString();
            if (result.categoryCounts && ozonState.messages) ozonState.messages.categoryCounts = result.categoryCounts;
            if (result.categoryUnreadCounts && ozonState.messages) ozonState.messages.categoryUnreadCounts = result.categoryUnreadCounts;
            if (result.accountUnreadCounts && ozonState.messages) ozonState.messages.accountUnreadCounts = result.accountUnreadCounts;
            applyOzonLocalReadOverrides(getOzonVisibleMessages());
            updateOzonMessageControls();
            return;
          }
          applyOzonMessageDeltaResult(result);
        }
      } catch (err) {
        console.warn('[小青蛙电商助手][OZON] 消息实时数据解析失败:', err?.message || err);
      }
    });
    source.addEventListener('error', () => {
      if (ozonState.messageEventSource === source) {
        try {
          source.close();
        } catch (_) {}
        ozonState.messageEventSource = null;
        window.setTimeout(() => {
          if (isOzonMessagesTabActive() && !ozonState.messageEventSource) startOzonMessageEventStream();
        }, 5000);
      }
    });
    return true;
  } catch (err) {
    console.warn('[小青蛙电商助手][OZON] 消息实时通道打开失败:', err?.message || err);
    return false;
  }
}

function startOzonMessageRealtimeSync() {
  if (!isOzonMessagesTabActive()) {
    stopOzonMessageRealtimeSync();
    return;
  }
  startOzonMessageEventStream();
  if (ozonState.messagePollingTimer) return;
  ozonState.messagePollingTimer = window.setInterval(async () => {
    if (!isOzonMessagesTabActive()) {
      stopOzonMessageRealtimeSync();
      return;
    }
    if (ozonState.messagePollInFlight) return;
    ozonState.messagePollInFlight = true;
    try {
      const now = Date.now();
      if (now - Number(ozonState.messageLastAutoSyncAt || 0) > 15000) {
        ozonState.messageLastAutoSyncAt = now;
        await Promise.allSettled([
          ozonState.messageEventSource ? Promise.resolve() : refreshOzonMessageListSilently(),
          refreshOzonActiveMessageSilently()
        ]);
      } else {
        await refreshOzonActiveMessageSilently();
      }
    } catch (err) {
      console.warn('[小青蛙电商助手][OZON] 消息实时同步失败:', err?.message || err);
    } finally {
      ozonState.messagePollInFlight = false;
    }
  }, 8000);
}

function getOzonChatReadFromMessageId(message = {}) {
  const candidates = [
    message.lastMessageId,
    message.last_message_id,
    message.messageId,
    message.message_id,
    message.raw?.last_message_id,
    message.raw?.lastMessageId,
    message.raw?.last_message?.message_id,
    message.raw?.lastMessage?.messageId,
    message.raw?.last_message?.id,
    message.raw?.lastMessage?.id,
    message.firstUnreadMessageId,
    message.first_unread_message_id,
    message.raw?.first_unread_message_id,
    message.raw?.firstUnreadMessageId
  ].map(value => String(value || '').trim()).filter(value => /^\d+$/.test(value) && value !== '0');
  const direct = candidates.find(Boolean);
  if (direct) return direct;
  const key = getOzonMessageCacheKey(message);
  const historyRows = key && Array.isArray(ozonState.messageHistoryById?.[key]?.messages)
    ? ozonState.messageHistoryById[key].messages
    : [];
  const latestBuyerOrAny = [...historyRows]
    .reverse()
    .find(row => !row.localClientId && /^\d+$/.test(String(row.id || '').trim()) && String(row.id || '').trim() !== '0');
  return String(latestBuyerOrAny?.id || '').trim();
}

async function markOzonChatRead(message = {}) {
  const chatId = String(message.chatId || message.id || '').trim();
  const accountId = String(message.accountId || '').trim();
  if (!chatId || !accountId) return null;
  const key = getOzonMessageCacheKey(message);
  if (!key) return null;
  if (Number(message.unreadCount || 0) <= 0 && !message.firstUnreadMessageId) return null;
  const fromMessageId = getOzonChatReadFromMessageId(message);
  if (!fromMessageId) return null;
  try {
    const result = await ozonRequest(`/messages/${encodeURIComponent(chatId)}/read`, {
      method: 'POST',
      body: JSON.stringify({ ozonAccountId: accountId, fromMessageId })
    });
    markOzonMessageLocallyRead(message);
    renderOzonMessageAccountCards({ forceRender: true });
    if (!isOzonChatReplyEditing()) renderOzonMessages({ forceRender: true });
    return result;
  } catch (err) {
    console.warn('[小青蛙电商助手][OZON] 已读同步失败:', err?.message || err);
    return null;
  }
}

function getOzonNotificationTitle(event = {}) {
  const type = String(event.eventType || '').trim();
  const label = String(event.eventLabel || '').trim();
  if (label) return label;
  if (!type) return 'OZON 推送事件';
  if (type === 'TYPE_PING') return 'URL 验证 Ping';
  if (type === 'TYPE_NEW_POSTING') return '新货件';
  if (type === 'TYPE_POSTING_CANCELLED') return '货件取消';
  if (type === 'TYPE_STATE_CHANGED') return '货件状态变更';
  if (type === 'TYPE_CUTOFF_DATE_CHANGED') return '发运截止变更';
  if (type === 'TYPE_DELIVERY_DATE_CHANGED') return '配送日期变更';
  if (type === 'TYPE_CREATE_OR_UPDATE_ITEM') return '商品创建/更新';
  if (type === 'TYPE_CREATE_ITEM') return '商品创建';
  if (type === 'TYPE_UPDATE_ITEM') return '商品更新';
  if (type === 'TYPE_STOCKS_CHANGED') return '库存变化';
  if (type === 'TYPE_NEW_MESSAGE') return '新聊天消息';
  if (type === 'TYPE_UPDATE_MESSAGE') return '聊天消息更新';
  if (type === 'TYPE_MESSAGE_READ') return '聊天消息已读';
  if (type === 'TYPE_CHAT_CLOSED') return '聊天会话关闭';
  if (/chat|message/i.test(type)) return '聊天推送';
  return type;
}

function isOzonChatNotificationEvent(event = {}) {
  const type = String(event.eventType || '').trim().toUpperCase();
  return ['TYPE_NEW_MESSAGE', 'TYPE_UPDATE_MESSAGE', 'TYPE_MESSAGE_READ', 'TYPE_CHAT_CLOSED'].includes(type);
}

function getOzonNotificationKindTitle(kind = '') {
  const value = String(kind || '').trim();
  if (value === 'chat') return '消息';
  if (value === 'order') return '订单';
  if (value === 'fulfillment') return '履约';
  if (value === 'product') return '商品';
  if (value === 'stock') return '库存';
  if (value === 'system') return '系统';
  return '其他';
}

function getOzonBusinessNotificationEvents() {
  const data = ozonState.notifications || {};
  const events = Array.isArray(data.events) ? data.events : [];
  return events.filter(event => String(event.eventType || '').toUpperCase() !== 'TYPE_PING');
}

function updateOzonNotificationRailBadge() {
  applyOzonLocalReadOverrides(getOzonVisibleMessages());
  const unreadCount = Number(getOzonCurrentCategoryUnreadCounts().notification || 0);
  return setOzonBadgeCount('ozonNotificationRailCount', unreadCount);
}

function renderOzonNotifications(options = {}) {
  const urlEl = document.getElementById('ozonNotificationUrl');
  const statusEl = document.getElementById('ozonNotificationStatus');
  const eventsEl = document.getElementById('ozonNotificationEvents');
  const kindEl = document.getElementById('ozonNotificationKindSummary');
  updateOzonNotificationRailBadge();
  if (options.silent) return;
  const data = ozonState.notifications || {};
  const webhookUrl = data.webhookUrl || 'https://www.xiaoqingwa.vip/api/ozon/notifications/7f3b0d6c-0e8f-4db8-9c90-8d4b90c8b713';
  if (urlEl) urlEl.textContent = webhookUrl;
  if (statusEl) {
    const pingText = data.hasPing
      ? `OZON 已验证过该地址，最近 Ping：${formatOzonDateTime(data.lastPingAt)}`
      : '地址可用，等待 OZON 后台添加 URL 后发送 Ping 验证。';
    statusEl.textContent = `${pingText} 已打开全部通知后，系统会记录消息、订单、履约、商品和库存事件。`;
  }
  if (kindEl) {
    const byKind = Array.isArray(data.stats?.byKind) ? data.stats.byKind : [];
    kindEl.innerHTML = byKind.length
      ? byKind.map(item => `<span class="ozon-notification-kind-pill ${Number(item.count || 0) > 0 ? 'is-hot' : ''}">${escapeHtml(item.label || getOzonNotificationKindTitle(item.kind))} ${escapeHtml(item.count || 0)}</span>`).join('')
      : '<span class="ozon-notification-kind-pill">暂无业务通知</span>';
  }
  if (eventsEl) {
    const businessEvents = getOzonBusinessNotificationEvents();
    eventsEl.innerHTML = businessEvents.length
      ? businessEvents.slice(0, 40).map(event => `<div class="ozon-notification-event">
          <strong>${escapeHtml(getOzonNotificationTitle(event))} · ${escapeHtml(event.eventKindLabel || getOzonNotificationKindTitle(event.eventKind))}</strong>
          <span>${escapeHtml(event.accountTitle || 'OZON 店铺')} · ${escapeHtml(formatOzonDateTime(event.receivedAt))} · ${escapeHtml(event.status || 'received')}</span>
          <span>${[
            event.summary || '',
            event.processNote ? `处理：${event.processNote}` : '',
            event.eventId ? `ID ${event.eventId}` : '',
            event.postingNumber ? `货件 ${event.postingNumber}` : '',
            event.sku ? `SKU ${event.sku}` : '',
            event.chatId ? `Chat ${event.chatId}` : ''
          ].filter(Boolean).map(escapeHtml).join(' · ') || '已收到原始推送数据'}</span>
        </div>`).join('')
      : '<div class="ozon-empty">暂无业务推送记录。OZON 会先发送 TYPE_PING 验证，后续订单、商品、库存和聊天事件会显示在这里。</div>';
  }
  renderOzonMessageAccountCards({ forceRender: options.forceRender === true });
}

function getOzonMessageAccountBadgeMap() {
  const map = getOzonServerAccountUnreadCounts();
  const hasServerCounts = map.size > 0;
  const add = (accountId, count = 1, latestAt = '', options = {}) => {
    const id = normalizeOzonText(accountId);
    if (!id) return;
    const existing = map.get(id) || { count: 0, unreadChat: 0, pushCount: 0, latestAt: '' };
    const safeCount = Math.max(0, Number(count || 0));
    if (options.chat) existing.unreadChat += safeCount;
    else existing.pushCount += safeCount;
    existing.count = existing.unreadChat || existing.pushCount;
    if (latestAt && (!existing.latestAt || new Date(latestAt) > new Date(existing.latestAt))) {
      existing.latestAt = latestAt;
    }
    map.set(id, existing);
  };
  const messages = applyOzonLocalReadOverrides(getOzonVisibleMessages());
  if (!hasServerCounts) {
    messages.forEach(item => {
    const count = Number(item.unreadCount || 0);
    if (count > 0) add(item.accountId, count, item.updatedAt || item.lastNotificationAt || item.createdAt || '', { chat: true });
    });
    return map;
  }
  messages.forEach(item => {
    const count = Number(item.unreadCount || 0);
    if (count <= 0) return;
    const accountId = normalizeOzonText(item.accountId || '');
    if (!accountId || map.has(accountId)) return;
    add(accountId, count, item.updatedAt || item.lastNotificationAt || item.createdAt || '', { chat: true });
  });
  return applyOzonReadOverridesToAccountBadgeMap(map);
}

function renderOzonMessageAccountCards(options = {}) {
  const wrap = document.getElementById('ozonMessageAccountCards');
  if (!wrap) return;
  const accounts = getAuthorizedOzonAccounts();
  const activeId = getOzonActiveAccountId(ozonState.messageAccountId || getDefaultOzonAccountId());
  const badgeMap = getOzonMessageAccountBadgeMap();
  const signature = JSON.stringify({
    activeId,
    accounts: accounts.map(account => {
      const id = String(account.id || '');
      const badge = badgeMap.get(id) || { count: 0, unreadChat: 0, pushCount: 0, latestAt: '' };
      return {
        id,
        title: account.title || account.clientId || 'OZON 店铺',
        count: Number(badge.count || 0),
        unreadChat: Number(badge.unreadChat || 0),
        pushCount: Number(badge.pushCount || 0),
        latestAt: badge.latestAt || ''
      };
    })
  });
  if (!options.forceRender && wrap.dataset.ozonRenderSignature === signature) return;
  wrap.dataset.ozonRenderSignature = signature;
  ozonState.messageAccountCardsRenderSignature = signature;
  if (!accounts.length) {
    wrap.innerHTML = '<div class="ozon-empty">还没有绑定 OZON 店铺。</div>';
    return;
  }
  wrap.innerHTML = accounts.map(account => {
    const id = String(account.id || '');
    const badge = badgeMap.get(id) || { count: 0, unreadChat: 0, pushCount: 0, latestAt: '' };
    const count = Number(badge.count || 0);
    const unreadChat = Number(badge.unreadChat || 0);
    const meta = count > 0
      ? `全部未读 ${unreadChat}`
      : '暂无新通知';
    return `<button class="ozon-message-account-card ${id === activeId ? 'is-active' : ''}" type="button" data-ozon-message-account-card="${escapeHtml(id)}">
      <div class="ozon-message-account-name">${escapeHtml(account.title || account.clientId || 'OZON 店铺')}</div>
      ${count > 0 ? `<span class="ozon-message-account-badge">${escapeHtml(count > 99 ? '99+' : count)}</span>` : ''}
      <div class="ozon-message-account-meta">${escapeHtml(meta)}${badge.latestAt ? ` · ${formatOzonDateTime(badge.latestAt)}` : ''}</div>
    </button>`;
  }).join('');
  wrap.querySelectorAll('[data-ozon-message-account-card]').forEach(button => {
    button.addEventListener('click', () => {
      const previousAccountId = getOzonSelectedMessageAccountId();
      const accountId = setOzonSelectedMessageAccountId(button.dataset.ozonMessageAccountCard || '');
      if (!accountId || accountId === previousAccountId) return;
      ozonState.selectedMessageId = '';
      stopOzonMessageRealtimeSync();
      updateOzonTabSyncStamps('ozonMessages');
      loadOzonMessages({ forceRefresh: true, sync: false })
        .then(() => startOzonMessageRealtimeSync())
        .catch(err => setOzonResult(err.message || '消息读取失败', 'error'));
    });
  });
}

async function loadOzonNotifications(options = {}) {
  if (!options.silent) renderOzonNotifications(options);
  const accountId = options.allAccounts === false
    ? getOzonSelectedMessageAccountId()
    : '';
  const query = `?limit=160${accountId ? `&ozonAccountId=${encodeURIComponent(accountId)}` : ''}`;
  await loadOzonCached(`/notification-events${query}`, (result) => {
    ozonState.notifications = result;
    renderOzonNotifications(options);
  }, options);
}

function getOzonMessageHistoryRenderSignature(current = {}, state = {}, rows = [], sendState = {}) {
  return JSON.stringify({
    key: getOzonMessageCacheKey(current),
    title: getOzonDisplayMessageTitle(current),
    accountTitle: current.accountTitle || '',
    category: getOzonMessageCategory(current),
    categoryLabel: getOzonMessageCategoryLabel(current),
    typeLabel: current.typeLabel || current.type || '',
    time: current.lastNotificationAt || current.createdAt || current.updatedAt || '',
    preview: current.content || current.preview || '',
    source: current.source || '',
    loading: Boolean(state?.loading),
    error: state?.error || '',
    sourceStatusText: state?.sourceStatusText || '',
    sendText: sendState?.text || '',
    sendType: sendState?.type || '',
    canReply: canReplyToOzonMessage(current),
    rows: rows.map(row => ({
      id: row.localClientId || row.id || '',
      side: row.side || '',
      authorName: row.authorName || row.authorType || '',
      text: row.text || '',
      createdAt: row.createdAt || '',
      localStatus: row.localStatus || '',
      sendStatus: row.sendStatus || '',
      attachments: Array.isArray(row.attachments)
        ? row.attachments.map(file => `${file.title || ''}|${file.url || ''}`)
        : []
    })),
    pageState: state?.pageState || null
  });
}

function renderOzonMessageHistory(current = null, options = {}) {
  const detail = document.getElementById('ozonMessageDetail');
  if (!detail) return;
  if (!current) {
    const data = ozonState.messages || {};
    detail.dataset.ozonMessageKey = '';
    detail.innerHTML = `<div class="ozon-empty">${escapeHtml(data.sourceStatusText || '选择一条消息开始查看')}</div>`;
    return;
  }
  const key = getOzonMessageCacheKey(current);
  if (shouldSkipOzonMessageDetailRerender(current, options)) return;
  persistCurrentOzonChatReplyDraft();
  const state = ozonState.messageHistoryById?.[key] || null;
  const hasRenderedMessages = Array.isArray(state?.messages) && state.messages.length > 0;
  if (state?.loading && !hasRenderedMessages) {
    detail.dataset.ozonMessageKey = key;
    detail.innerHTML = `<div class="ozon-tool-card">
      <h4>${escapeHtml(getOzonDisplayMessageTitle(current))}</h4>
      <div class="ozon-list-meta">店铺：${escapeHtml(current.accountTitle || '全部店铺')} · 类型：${escapeHtml(getOzonMessageCategoryLabel(current))} · 时间：${escapeHtml(formatOzonDateTime(current.createdAt || current.updatedAt))}</div>
      <div class="ozon-empty">正在读取 OZON 会话正文...</div>
    </div>`;
    return;
  }
  if (state?.error && !hasRenderedMessages) {
    detail.dataset.ozonMessageKey = key;
    detail.innerHTML = `<div class="ozon-tool-card">
      <h4>${escapeHtml(getOzonDisplayMessageTitle(current))}</h4>
      <div class="ozon-list-meta">店铺：${escapeHtml(current.accountTitle || '全部店铺')} · 类型：${escapeHtml(getOzonMessageCategoryLabel(current))} · 来源：${escapeHtml(current.sourceLabel || 'Chat API')} · 时间：${escapeHtml(formatOzonDateTime(current.lastNotificationAt || current.createdAt || current.updatedAt))}</div>
      ${current.source === 'push_notification' ? `<div class="ozon-empty">已收到聊天 Push：${escapeHtml(current.content || current.preview || current.notificationEventLabel || '聊天通知')}<br>但 Chat API 暂时没有返回会话正文，请确认该 Api-Key 已开通 Chat API 权限。</div>` : ''}
      ${renderOzonErrorEmpty(state.error)}
    </div>`;
    return;
  }
  const rows = Array.isArray(state?.messages) ? state.messages : [];
  const canReply = canReplyToOzonMessage(current);
  const sendState = ozonState.messageSendingById?.[key] || {};
  const renderSignature = getOzonMessageHistoryRenderSignature(current, state, rows, sendState);
  if (!options.forceRender && detail.dataset.ozonRenderSignature === renderSignature) return;
  detail.dataset.ozonRenderSignature = renderSignature;
  const sendStatusClass = sendState.type ? ` is-${escapeHtml(sendState.type)}` : '';
  const lastMessage = rows.length ? rows[rows.length - 1] : null;
  const lastMessageSide = String(lastMessage?.side || '').trim();
  const historyStatus = state?.loading
    ? '正在同步 OZON 历史...'
    : state?.error
      ? `历史同步失败：${state.error}`
      : (state?.sourceStatusText || '');
  const draftText = getOzonChatReplyDraft(current);
  const hasMoreHistory = Boolean(state?.pageState?.hasMore || state?.hasMore);
  const oldestMessageId = getOldestOfficialOzonChatMessageId(rows);
  detail.dataset.ozonMessageKey = key;
  detail.innerHTML = `<div class="ozon-message-detail-head">
      <div>
        <h4>${escapeHtml(getOzonDisplayMessageTitle(current))}</h4>
        <div class="ozon-list-meta"><span class="ozon-message-buyer-tag">${escapeHtml(getOzonMessageCategoryLabel(current))}</span> ${escapeHtml(current.accountTitle || '全部店铺')} · ${escapeHtml(formatOzonDateTime(current.lastNotificationAt || current.createdAt || current.updatedAt))}${historyStatus ? ` · ${escapeHtml(historyStatus)}` : ''}</div>
      </div>
      <div class="ozon-chat-header-actions">
        <button class="ozon-chat-icon-btn" type="button" id="ozonRefreshMessageHistoryBtn" title="刷新详情">↻</button>
        <button class="ozon-chat-icon-btn" type="button" id="ozonChatFileHeadBtn" title="发送附件">📎</button>
        <button class="ozon-chat-icon-btn" type="button" id="ozonStartChatHeadBtn" title="按订单号新建会话">＋</button>
      </div>
    </div>
    <div class="ozon-message-thread">
      ${rows.length && (hasMoreHistory || oldestMessageId) ? `<div class="ozon-chat-history-more">
        <button class="btn btn-default btn-sm" type="button" id="ozonLoadOlderHistoryBtn" ${state?.loading ? 'disabled' : ''}>加载更早消息</button>
        <span>${escapeHtml(hasMoreHistory ? 'OZON 仍有更早记录' : '继续按最早消息向前读取')}</span>
      </div>` : ''}
      ${rows.length ? rows.map(row => `<div class="ozon-chat-row is-${escapeHtml(row.side || 'system')}">
        <div class="ozon-chat-bubble">
          <div class="ozon-chat-meta">${escapeHtml(row.authorName || row.authorType || 'OZON')} · ${escapeHtml(formatOzonDateTime(row.createdAt))}</div>
          <div class="ozon-chat-text">${renderOzonChatTextContent(row.text || '[非文本消息]')}</div>
          ${renderOzonChatAttachments(row.attachments)}
          ${renderOzonChatLocalStatus(row)}
        </div>
      </div>`).join('') : `<div class="ozon-empty">${escapeHtml(current.content || current.preview || '该会话暂无可展示正文')}${current.source === 'push_notification' ? '<br>这是 Push 收到的会话，点击“刷新详情”会继续尝试读取 Chat API 历史。' : ''}</div>`}
    </div>
    ${canReply ? `<div class="ozon-chat-reply">
      <div class="ozon-chat-reply-actions">
        <span class="ozon-chat-send-status${sendStatusClass}" id="ozonChatSendStatus">${escapeHtml(sendState.text || (lastMessageSide === 'buyer' ? '客户等待回复' : '通过 OZON Chat API 发送'))}</span>
        <div class="ozon-chat-quick-row">
          ${OZON_CHAT_QUICK_REPLIES.map((text, index) => `<button type="button" data-ozon-quick-reply="${index}">快捷回复 ${index + 1}</button>`).join('')}
        </div>
      </div>
      <div class="ozon-chat-reply-box">
        <button class="ozon-chat-icon-btn" type="button" id="ozonChatFileReplyBtn" title="发送附件">📎</button>
        <textarea id="ozonChatReplyInput" maxlength="1000" placeholder="您给买家写短信">${escapeHtml(draftText)}</textarea>
        <button class="ozon-chat-send-btn" type="button" id="ozonSendChatReplyBtn" title="发送">➤</button>
      </div>
    </div>` : '<div class="ozon-chat-reply-disabled">该会话不是 OZON 买家聊天类型，官方 Chat API 不允许直接回复。</div>'}`;
  document.getElementById('ozonRefreshMessageHistoryBtn')?.addEventListener('click', () => {
    loadOzonMessageHistory(current, { force: true, sync: true, markRead: true }).catch(err => setOzonResult(err.message || '消息详情刷新失败', 'error'));
  });
  document.getElementById('ozonLoadOlderHistoryBtn')?.addEventListener('click', () => {
    loadOlderOzonMessageHistory(current).catch(err => setOzonResult(err.message || '加载更早消息失败', 'error'));
  });
  document.getElementById('ozonSendChatReplyBtn')?.addEventListener('click', () => {
    sendOzonChatReply(current).catch(err => setOzonResult(err.message || '发送回复失败', 'error'));
  });
  detail.querySelectorAll('[data-ozon-retry-message-id]').forEach(button => {
    button.addEventListener('click', () => {
      retryOzonFailedChatMessage(current, button.dataset.ozonRetryMessageId || '').catch(err => setOzonResult(err.message || '重试发送失败', 'error'));
    });
  });
  ['ozonChatFileHeadBtn', 'ozonChatFileReplyBtn'].forEach((id) => {
    document.getElementById(id)?.addEventListener('click', () => {
      const fileInput = document.getElementById('ozonChatFileInput');
      if (fileInput) fileInput.click();
    });
  });
  document.getElementById('ozonStartChatHeadBtn')?.addEventListener('click', () => {
    toggleOzonStartChatModal(true);
  });
  document.querySelectorAll('[data-ozon-quick-reply]').forEach(button => {
    button.addEventListener('click', () => {
      const input = document.getElementById('ozonChatReplyInput');
      if (!input) return;
      const index = Number(button.dataset.ozonQuickReply || 0);
      input.value = OZON_CHAT_QUICK_REPLIES[index] || '';
      persistOzonChatReplyDraft(current);
      input.focus();
    });
  });
  const replyInput = document.getElementById('ozonChatReplyInput');
  if (replyInput) {
    replyInput.addEventListener('input', () => persistOzonChatReplyDraft(current));
  }
  if (!options.keepScroll) {
    window.setTimeout(() => {
      const thread = detail.querySelector('.ozon-message-thread');
      if (thread) thread.scrollTop = thread.scrollHeight;
    }, 0);
  }
}

function getOzonNotificationWebhookUrl() {
  return ozonState.notifications?.webhookUrl || 'https://www.xiaoqingwa.vip/api/ozon/notifications/7f3b0d6c-0e8f-4db8-9c90-8d4b90c8b713';
}

function toggleOzonNotificationGuideModal(force, accountId = '') {
  const modal = document.getElementById('ozonNotificationGuideModal');
  if (!modal) return;
  if (modal.parentElement !== document.body) document.body.appendChild(modal);
  const shouldOpen = force === undefined ? modal.getAttribute('aria-hidden') !== 'false' : Boolean(force);
  modal.setAttribute('aria-hidden', shouldOpen ? 'false' : 'true');
  modal.classList.toggle('is-open', shouldOpen);
  if (!shouldOpen) return;
  const account = ozonState.accounts.find(item => String(item.id) === String(accountId)) || {};
  setOzonText('ozonNotificationGuideAccountName', account.title || getOzonAccountLabel(accountId) || '当前店铺');
  const url = getOzonNotificationWebhookUrl();
  setOzonText('ozonNotificationGuideUrl', url);
  loadOzonNotifications({ silent: true, forceRefresh: true }).then(() => {
    setOzonText('ozonNotificationGuideUrl', getOzonNotificationWebhookUrl());
  }).catch(() => {});
}

function ensureOzonStartChatModal() {
  let modal = document.getElementById('ozonStartChatModal');
  if (modal) return modal;
  modal = document.createElement('div');
  modal.id = 'ozonStartChatModal';
  modal.className = 'ozon-modal';
  modal.setAttribute('aria-hidden', 'true');
  modal.innerHTML = `<div class="ozon-modal-panel">
    <div class="ozon-modal-head">
      <h3>按订单号新建 OZON 会话</h3>
      <button class="ozon-icon-btn" type="button" data-ozon-start-chat-close title="关闭">×</button>
    </div>
    <div class="ozon-muted" style="margin-bottom:12px;">官方 Chat API 只需要 posting_number。请填写 OZON 订单/货件号，系统不会补造会话内容。</div>
    <div class="ozon-form">
      <label>posting_number<input id="ozonStartChatPostingInput" placeholder="例如 00000000-0000-1"></label>
      <div class="ozon-actions">
        <button class="btn btn-primary" type="button" id="ozonStartChatConfirmBtn">新建会话</button>
        <button class="btn btn-default" type="button" data-ozon-start-chat-close>取消</button>
      </div>
    </div>
    <div id="ozonStartChatModalResult" class="ozon-modal-result"></div>
  </div>`;
  modal.querySelectorAll('[data-ozon-start-chat-close]').forEach(button => {
    button.addEventListener('click', () => toggleOzonStartChatModal(false));
  });
  modal.addEventListener('click', event => {
    if (event.target === modal) toggleOzonStartChatModal(false);
  });
  modal.querySelector('#ozonStartChatConfirmBtn')?.addEventListener('click', () => {
    submitOzonStartChatModal().catch(err => {
      const result = document.getElementById('ozonStartChatModalResult');
      if (result) {
        result.textContent = err.message || '新建会话失败';
        result.className = 'ozon-modal-result is-error';
      }
    });
  });
  modal.querySelector('#ozonStartChatPostingInput')?.addEventListener('keydown', event => {
    if (event.key === 'Enter') {
      event.preventDefault();
      modal.querySelector('#ozonStartChatConfirmBtn')?.click();
    }
  });
  document.body.appendChild(modal);
  return modal;
}

function toggleOzonStartChatModal(force) {
  const modal = ensureOzonStartChatModal();
  const shouldOpen = force === undefined ? modal.getAttribute('aria-hidden') !== 'false' : Boolean(force);
  modal.classList.toggle('is-open', shouldOpen);
  modal.setAttribute('aria-hidden', shouldOpen ? 'false' : 'true');
  if (shouldOpen) {
    const result = modal.querySelector('#ozonStartChatModalResult');
    if (result) {
      result.textContent = '';
      result.className = 'ozon-modal-result';
    }
    window.setTimeout(() => modal.querySelector('#ozonStartChatPostingInput')?.focus(), 0);
  }
}

async function submitOzonStartChatModal() {
  const modal = ensureOzonStartChatModal();
  const input = modal.querySelector('#ozonStartChatPostingInput');
  const result = modal.querySelector('#ozonStartChatModalResult');
  const postingNumber = normalizeOzonText(input?.value || '');
  if (!postingNumber) throw new Error('请填写 OZON posting_number');
  if (result) {
    result.textContent = '正在调用 OZON Chat API...';
    result.className = 'ozon-modal-result';
  }
  const response = await startOzonChatByPostingNumber(postingNumber);
  if (result) {
    result.textContent = response?.sourceStatusText || 'OZON 会话已创建或已返回现有会话';
    result.className = 'ozon-modal-result is-success';
  }
  if (input) input.value = '';
  window.setTimeout(() => toggleOzonStartChatModal(false), 500);
  return response;
}

async function loadOzonMessageHistory(message = {}, options = {}) {
  const chatId = String(message.chatId || message.id || '').trim();
  const accountId = String(message.accountId || '').trim();
  const key = getOzonMessageCacheKey(message);
  if (!chatId || !accountId || !key) {
    if (key) {
      ozonState.messageHistoryById[key] = { error: '该会话缺少 chat_id 或店铺 ID，无法读取 OZON Chat history。' };
    }
    renderOzonMessageHistory(message, options);
    return null;
  }
  if (!options.force && ozonState.messageHistoryById?.[key]?.loaded) {
    renderOzonMessageHistory(message, options);
    return ozonState.messageHistoryById[key];
  }
  const previousState = ozonState.messageHistoryById?.[key] || {};
  const localMessages = Array.isArray(previousState.localMessages) ? previousState.localMessages : [];
  ozonState.messageHistoryById[key] = {
    loading: true,
    syncing: true,
    syncingAt: Date.now(),
    loaded: Boolean(previousState.loaded),
    messages: mergeOzonLocalChatMessages(previousState.messages || [], localMessages),
    localMessages
  };
  if (!options.silent) renderOzonMessageHistory(message, options);
  try {
    const params = new URLSearchParams({ ozonAccountId: accountId });
    if (options.force || options.sync) params.set('sync', '1');
    if (options.pages) params.set('pages', String(options.pages));
    if (options.limit) params.set('limit', String(options.limit));
    if (options.direction) params.set('direction', String(options.direction));
    if (options.fromMessageId) params.set('fromMessageId', String(options.fromMessageId));
    const result = await ozonRequest(`/messages/${encodeURIComponent(chatId)}/history?${params.toString()}`, {
      cache: false,
      skipCache: true
    });
    const latestState = ozonState.messageHistoryById[key] || {};
    const latestLocalMessages = Array.isArray(latestState.localMessages) ? latestState.localMessages : localMessages;
    const apiMessages = Array.isArray(result.messages) ? result.messages : [];
    const mergedMessages = mergeOzonLocalChatMessages(apiMessages, latestLocalMessages);
    ozonState.messageHistoryById[key] = {
      loaded: true,
      loading: false,
      syncing: false,
      loadedAt: new Date().toISOString(),
      messages: mergedMessages,
      localMessages: latestLocalMessages,
      sourceStatusText: result.sourceStatusText || '',
      pageState: result.pageState || latestState.pageState || {},
      hasMore: Boolean(result.pageState?.hasMore)
    };
    const changed = updateOzonMessageRowFromHistory(message, mergedMessages);
    renderOzonMessageHistory(message, options);
    if (changed) renderOzonMessages();
    if (options.markRead === true) markOzonChatRead(message).catch(() => {});
    return ozonState.messageHistoryById[key];
  } catch (err) {
    const fallbackMessages = mergeOzonLocalChatMessages(previousState.messages || [], localMessages);
    ozonState.messageHistoryById[key] = {
      loaded: Boolean(previousState.loaded || fallbackMessages.length),
      loading: false,
      syncing: false,
      messages: fallbackMessages,
      localMessages,
      error: err.message || '消息详情读取失败'
    };
    renderOzonMessageHistory(message, options);
    throw err;
  }
}

async function loadOlderOzonMessageHistory(message = {}) {
  const key = getOzonMessageCacheKey(message);
  const state = key ? ozonState.messageHistoryById?.[key] : null;
  const rows = Array.isArray(state?.messages) ? state.messages : [];
  const fromMessageId = state?.pageState?.nextFromMessageId || getOldestOfficialOzonChatMessageId(rows);
  if (!fromMessageId) throw new Error('当前会话没有可用于继续分页的官方 message_id');
  const thread = document.querySelector('#ozonMessageDetail .ozon-message-thread');
  const previousHeight = thread?.scrollHeight || 0;
  const previousTop = thread?.scrollTop || 0;
  const result = await loadOzonMessageHistory(message, {
    force: true,
    sync: true,
    keepScroll: true,
    pages: 2,
    limit: Math.min(300, Math.max(160, rows.length + 80)),
    direction: 'Backward',
    fromMessageId
  });
  window.setTimeout(() => {
    const nextThread = document.querySelector('#ozonMessageDetail .ozon-message-thread');
    if (!nextThread) return;
    const delta = Math.max(0, nextThread.scrollHeight - previousHeight);
    nextThread.scrollTop = previousTop + delta;
  }, 0);
  return result;
}

async function retryOzonFailedChatMessage(message = {}, localMessageId = '') {
  const key = getOzonMessageCacheKey(message);
  const state = key ? ozonState.messageHistoryById?.[key] : null;
  const rows = Array.isArray(state?.messages) ? state.messages : [];
  const failed = rows.find(row => String(row.localClientId || row.id || '') === String(localMessageId || ''));
  const text = normalizeOzonText(failed?.text || '').replace(/^\[附件发送失败]\s*/, '').trim();
  if (!failed || !text) throw new Error('找不到可重试的失败文本消息');
  const input = document.getElementById('ozonChatReplyInput');
  if (input) input.value = text;
  persistOzonChatReplyDraft(message);
  return sendOzonChatReply(message);
}

async function sendOzonChatReply(message = {}) {
  const chatId = String(message.chatId || message.id || '').trim();
  const accountId = String(message.accountId || '').trim();
  const input = document.getElementById('ozonChatReplyInput');
  persistOzonChatReplyDraft(message);
  const text = String(input?.value || '').trim();
  if (!chatId || !accountId) throw new Error('该会话缺少 chat_id 或店铺 ID，无法发送回复');
  if (!canReplyToOzonMessage(message)) throw new Error('该会话不是 OZON 买家聊天类型，官方 Chat API 不允许直接回复');
  if (!text) throw new Error('回复内容不能为空');
  if (text.length > 1000) throw new Error('OZON 官方 Chat API 要求回复内容不超过 1000 个字符');
  const key = getOzonMessageCacheKey(message);
  const button = document.getElementById('ozonSendChatReplyBtn');
  const oldText = button?.textContent || '发送回复';
  if (button) {
    button.disabled = true;
    button.textContent = '发送中...';
  }
  setOzonChatSendStatus(message, '正在发送到 OZON...', '');
  try {
    await ozonRequest(`/messages/${encodeURIComponent(chatId)}/send`, {
      method: 'POST',
      body: JSON.stringify({ ozonAccountId: accountId, text })
    });
    if (input) input.value = '';
    clearOzonChatReplyDraft(message);
    appendOzonLocalChatMessage(message, {
      id: `local-sent-${Date.now()}`,
      text,
      localStatus: '已发送，等待 OZON 历史同步',
      createdAt: new Date().toISOString()
    });
    setOzonChatSendStatus(message, '已发送到 OZON 会话', 'success');
    setOzonResult('OZON 聊天回复已发送', 'success');
    renderOzonMessageHistory(message, { forceRender: true });
    loadOzonMessageHistory(message, { force: true, sync: true }).catch(err => {
      setOzonChatSendStatus(message, `已发送；历史同步失败：${err.message || '请稍后刷新'}`, 'error');
    });
  } catch (err) {
    setOzonChatSendStatus(message, `发送失败：${err.message || 'OZON Chat 接口返回异常'}`, 'error');
    throw err;
  } finally {
    if (button) {
      button.disabled = false;
      button.textContent = oldText;
    }
    if (key && ozonState.messageSendingById?.[key]?.type === 'success') {
      window.setTimeout(() => {
        if (ozonState.messageSendingById?.[key]?.type === 'success') {
          ozonState.messageSendingById[key].text = '通过 OZON Chat API 发送';
          ozonState.messageSendingById[key].type = '';
          const current = getCurrentOzonMessage(Array.isArray(ozonState.messages?.messages) ? ozonState.messages.messages : []);
          if (current && getOzonMessageCacheKey(current) === key) renderOzonMessageHistory(current);
        }
      }, 2500);
    }
  }
}

async function sendOzonChatFile(message = {}, file = null) {
  const chatId = String(message.chatId || message.id || '').trim();
  const accountId = String(message.accountId || '').trim();
  if (!chatId || !accountId) throw new Error('该会话缺少 chat_id 或店铺 ID，无法发送附件');
  if (!canReplyToOzonMessage(message)) throw new Error('该会话不是 OZON 买家聊天类型，官方 Chat API 不允许发送附件');
  if (!file) throw new Error('请选择需要发送的文件');
  const maxBytes = 8 * 1024 * 1024;
  if (file.size > maxBytes) throw new Error('文件超过 8MB，请压缩后再发送');
  const base64Content = await fileToBase64Content(file);
  setOzonChatSendStatus(message, '正在发送附件到 OZON...', '');
  try {
    await ozonRequest(`/messages/${encodeURIComponent(chatId)}/send-file`, {
      method: 'POST',
      body: JSON.stringify({
        ozonAccountId: accountId,
        base64Content,
        name: file.name || 'attachment'
      })
    });
    appendOzonLocalChatMessage(message, {
      id: `local-file-${Date.now()}`,
      text: `[附件] ${file.name || 'attachment'}`,
      localStatus: '附件已发送，等待 OZON 历史同步',
      createdAt: new Date().toISOString()
    });
    setOzonChatSendStatus(message, '附件已发送到 OZON 会话', 'success');
    renderOzonMessageHistory(message, { forceRender: true });
    loadOzonMessageHistory(message, { force: true, sync: true }).catch(err => {
      setOzonChatSendStatus(message, `附件已发送；历史同步失败：${err.message || '请稍后刷新'}`, 'error');
    });
  } catch (err) {
    setOzonChatSendStatus(message, `附件发送失败：${err.message || 'OZON Chat 接口返回异常'}`, 'error');
    throw err;
  }
}

async function startOzonChatByPostingNumber(postingNumberInput = '') {
  const accountId = getOzonSelectedMessageAccountId();
  if (!accountId) throw new Error('请先在消息中心选择一个店铺卡片，再按订单号新建会话');
  const postingNumber = postingNumberInput || '';
  const normalized = normalizeOzonText(postingNumber);
  if (!normalized) return null;
  const result = await ozonRequest('/messages/start', {
    method: 'POST',
    body: JSON.stringify({ ozonAccountId: accountId, postingNumber: normalized })
  });
  setOzonResult(result.sourceStatusText || 'OZON 会话已创建或已返回现有会话', 'success');
  await loadOzonMessages({ forceRefresh: true, sync: true });
  startOzonMessageRealtimeSync();
  return result;
}

function getCurrentOzonMessage(messages = []) {
  return messages.find(item => String(item.id || '') === String(ozonState.selectedMessageId || '')) || messages[0] || null;
}

function getOzonMessageStatusText(item = {}) {
  const parts = [];
  if (item.notificationEventLabel) parts.push(item.notificationEventLabel);
  if (item.status === 'closed') parts.push('已关闭');
  if (Number(item.unreadCount || 0) > 0) parts.push(`未读 ${item.unreadCount}`);
  if (item.source === 'push_notification') parts.push('Push 通知');
  else if (item.source === 'chat_api') parts.push('官方 Chat');
  return parts.join(' · ');
}

function getOzonMessageLastSide(item = {}) {
  const key = getOzonMessageCacheKey(item);
  const rows = key && Array.isArray(ozonState.messageHistoryById?.[key]?.messages)
    ? ozonState.messageHistoryById[key].messages
    : [];
  if (!rows.length) return '';
  const latestBusiness = [...rows].reverse().find(row => ['buyer', 'seller'].includes(String(row?.side || '').trim()));
  return String(latestBusiness?.side || rows[rows.length - 1]?.side || '').trim();
}

function getOzonMessageLastSideFromRow(item = {}) {
  if (item.lastSide) return String(item.lastSide || '').trim();
  const historySide = getOzonMessageLastSide(item);
  if (historySide) return historySide;
  const raw = item.raw || {};
  const candidates = [
    raw.last_message?.user_type,
    raw.last_message?.userType,
    raw.last_message?.sender_type,
    raw.last_message?.senderType,
    raw.lastMessage?.userType,
    raw.lastMessage?.senderType,
    raw.last_message?.author?.type,
    raw.last_message?.sender?.type,
    raw.lastMessage?.author?.type,
    raw.lastMessage?.sender?.type,
    raw.last_message?.is_seller === true ? 'seller' : '',
    raw.last_message?.is_buyer === true ? 'buyer' : '',
    raw.lastMessage?.isSeller === true ? 'seller' : '',
    raw.lastMessage?.isBuyer === true ? 'buyer' : ''
  ].map(value => normalizeOzonText(value).toLowerCase()).filter(Boolean);
  const value = candidates.find(Boolean) || '';
  if (/seller|operator|support|service|admin|merchant|store|shop/.test(value)) return 'seller';
  if (/buyer|customer|client|user/.test(value)) return 'buyer';
  return '';
}

function doesOzonMessageMatchSearch(item = {}, keyword = '') {
  const text = normalizeOzonText(keyword).toLowerCase();
  if (!text) return true;
  const key = getOzonMessageCacheKey(item);
  const historyRows = key && Array.isArray(ozonState.messageHistoryById?.[key]?.messages)
    ? ozonState.messageHistoryById[key].messages
    : [];
  const haystack = [
    item.title,
    item.preview,
    item.content,
    item.accountTitle,
    item.categoryLabel,
    item.typeLabel,
    item.status,
    item.chatId,
    item.id,
    item.raw?.posting_number,
    item.raw?.postingNumber,
    item.raw?.sku,
    item.raw?.product_id,
    item.raw?.productId,
    ...historyRows.flatMap(row => [row.text, row.authorName, row.authorType])
  ].map(value => normalizeOzonText(value).toLowerCase()).join(' ');
  return haystack.includes(text);
}

function doesOzonMessageMatchQuickFilter(item = {}, filter = '') {
  const value = normalizeOzonText(filter);
  if (!value) return true;
  const raw = item.raw || {};
  const combined = [
    item.title,
    item.preview,
    item.content,
    raw.posting_number,
    raw.postingNumber,
    raw.order_number,
    raw.orderNumber,
    raw.product_id,
    raw.productId,
    raw.offer_id,
    raw.offerId,
    raw.sku,
    raw.item?.sku,
    raw.product?.sku
  ].map(value => normalizeOzonText(value).toLowerCase()).join(' ');
  if (value === 'new') return Number(item.unreadCount || 0) > 0 || item.notificationEventType === 'TYPE_NEW_MESSAGE';
  if (value === 'product') return /sku|offer|product|товар|商品|goods|item/i.test(combined);
  if (value === 'order') return /posting|order|заказ|订单/i.test(combined);
  if (value === 'need_seller') return getOzonMessageLastSideFromRow(item) === 'buyer';
  if (value === 'need_buyer') return getOzonMessageLastSideFromRow(item) === 'seller';
  return true;
}

function canReplyToOzonMessage(message = {}) {
  return Boolean(message.chatId || message.id) && Boolean(message.accountId) && message.canReply === true;
}

function getFilteredOzonMessages(messages = []) {
  const type = normalizeOzonMessageCategory(ozonState.messageType);
  const quickFilter = normalizeOzonText(ozonState.messageQuickFilter);
  const keyword = normalizeOzonText(ozonState.messageSearch || document.getElementById('ozonMessageSearchInput')?.value || '');
  return messages.filter(item => {
    if (type) {
      const itemCategory = getOzonMessageCategory(item);
      if (itemCategory !== type) return false;
    }
    if (!doesOzonMessageMatchQuickFilter(item, quickFilter)) return false;
    if (!doesOzonMessageMatchSearch(item, keyword)) return false;
    return true;
  });
}

function getOzonMessageListRenderSignature(data = {}, filtered = [], selectedId = '') {
  const errors = Array.isArray(data.errors) ? data.errors : [];
  return JSON.stringify({
    selectedId: String(selectedId || ''),
    sourceStatusText: data.sourceStatusText || '',
    type: ozonState.messageType || '',
    quickFilter: ozonState.messageQuickFilter || '',
    search: ozonState.messageSearch || '',
    errors: errors.map(item => `${item.accountId || ''}:${item.message || ''}`),
    rows: filtered.map(item => ({
      id: String(item.id || ''),
      title: getOzonDisplayMessageTitle(item),
      category: getOzonMessageCategory(item),
      categoryLabel: getOzonMessageCategoryLabel(item),
      typeLabel: item.typeLabel || item.type || '',
      accountTitle: item.accountTitle || '',
      statusText: getOzonMessageStatusText(item),
      preview: item.preview || item.content || '',
      unreadCount: Number(item.unreadCount || 0),
      time: item.lastNotificationAt || item.updatedAt || item.createdAt || ''
    }))
  });
}

function updateOzonMessageControls(options = {}) {
  syncOzonAccountSelects();
  renderOzonMessageAccountCards({ forceRender: options.forceRender === true });
  const currentCategory = normalizeOzonMessageCategory(ozonState.messageType || '') || 'buyer';
  ozonState.messageType = currentCategory;
  const messages = getOzonVisibleMessages();
  const categoryScopedMessages = messages.filter(item => getOzonMessageCategory(item) === currentCategory);
  const scopedUnreadCount = categoryScopedMessages.reduce((sum, item) => sum + Math.max(0, Number(item.unreadCount || 0)), 0);
  setOzonText('ozonMessageAllCount', String(categoryScopedMessages.length || 0));
  setOzonText('ozonMessageUnreadCount', String(scopedUnreadCount || 0));
  const unreadCountsByCategory = getOzonCurrentCategoryUnreadCounts();
  Object.entries(OZON_MESSAGE_CATEGORY_BADGE_IDS).forEach(([category, id]) => {
    if (category === 'all') {
      setOzonBadgeCount(id, 0);
      return;
    }
    setOzonBadgeCount(id, unreadCountsByCategory[category] || 0);
  });
  document.querySelectorAll('[data-ozon-message-tab]').forEach(button => {
    const isRail = button.classList.contains('ozon-chat-rail-item');
    button.classList.toggle(isRail ? 'is-active' : 'active', (button.dataset.ozonMessageTab || '') === currentCategory);
    button.classList.toggle('is-loading', options.loading === true && (button.dataset.ozonMessageTab || '') === currentCategory);
  });
  document.querySelectorAll('[data-ozon-message-quick-filter]').forEach(button => {
    button.classList.toggle('active', (button.dataset.ozonMessageQuickFilter || '') === (ozonState.messageQuickFilter || ''));
  });
  const drawer = document.getElementById('ozonNotificationDrawer');
  if (drawer) drawer.classList.toggle('is-open', Boolean(ozonState.notificationDrawerOpen));
}

function renderOzonMessages(options = {}) {
  updateOzonMessageControls({ forceRender: options.forceRender === true });
  const data = ozonState.messages || {};
  const list = document.getElementById('ozonMessageList');
  const messages = getOzonVisibleMessages();
  const filtered = getFilteredOzonMessages(messages);
  if (!hasOzonAccounts()) {
    setOzonText('ozonMessageAllCount', '0');
    setOzonText('ozonMessageUnreadCount', '0');
    Object.entries(OZON_MESSAGE_CATEGORY_BADGE_IDS)
      .filter(([category]) => category !== 'all')
      .forEach(([, id]) => setOzonBadgeCount(id, 0));
    if (list) list.innerHTML = renderOzonNeedAccountEmpty('还没有绑定 OZON 店铺。绑定后可读取客服、买家和系统消息。');
    const detail = document.getElementById('ozonMessageDetail');
    if (detail) detail.innerHTML = renderOzonNeedAccountEmpty('请先在首页添加 OZON 店铺授权。');
    renderOzonMessageAccountCards();
    return;
  }
  if (ozonState.messagesLoading && !Array.isArray(data.messages)) {
    setOzonText('ozonMessageAllCount', '0');
    setOzonText('ozonMessageUnreadCount', '0');
    Object.entries(OZON_MESSAGE_CATEGORY_BADGE_IDS)
      .filter(([category]) => category !== 'all')
      .forEach(([, id]) => setOzonBadgeCount(id, 0));
    if (list) list.innerHTML = renderOzonLoadingEmpty('正在读取消息...');
    const detail = document.getElementById('ozonMessageDetail');
    if (detail) detail.innerHTML = renderOzonLoadingEmpty('正在读取消息详情...');
    return;
  }
  applyOzonLocalReadOverrides(messages);
  const counts = messages.reduce((acc, item) => {
    const category = getOzonMessageCategory(item);
    acc[category] = (acc[category] || 0) + 1;
    acc.all += 1;
    return acc;
  }, { all: 0, buyer: 0, service: 0, main: 0, notification: 0, promotion: 0, training: 0, finance: 0 });
  Object.entries(data.categoryCounts || {}).forEach(([key, value]) => {
    const category = normalizeOzonMessageCategory(key);
    if (category) counts[category] = Math.max(Number(counts[category] || 0), Number(value || 0));
  });
  const currentCategory = normalizeOzonMessageCategory(ozonState.messageType || '') || 'buyer';
  ozonState.messageType = currentCategory;
  const categoryScopedMessages = currentCategory
    ? messages.filter(item => getOzonMessageCategory(item) === currentCategory)
    : messages;
  const scopedUnreadCount = categoryScopedMessages.reduce((sum, item) => sum + Math.max(0, Number(item.unreadCount || 0)), 0);
  setOzonText('ozonMessageAllCount', String(categoryScopedMessages.length || 0));
  setOzonText('ozonMessageUnreadCount', String(scopedUnreadCount || 0));
  const filteredIds = new Set(filtered.map(item => String(item.id || '')).filter(Boolean));
  const keepSelectedOnSilent = options.silent === true && ozonState.selectedMessageId && document.getElementById('ozonMessageDetail')?.dataset?.ozonMessageKey;
  if (!keepSelectedOnSilent && ozonState.selectedMessageId && !filteredIds.has(String(ozonState.selectedMessageId))) {
    ozonState.selectedMessageId = '';
  }
  if (!ozonState.selectedMessageId && filtered[0]?.id) {
    ozonState.selectedMessageId = String(filtered[0].id);
  }
  const listSignature = getOzonMessageListRenderSignature(data, filtered, ozonState.selectedMessageId);
  if (list) {
    const shouldRenderList = options.forceRender || list.dataset.ozonRenderSignature !== listSignature;
    if (shouldRenderList) {
      const errorRows = Array.isArray(data.errors) ? data.errors : [];
      list.dataset.ozonRenderSignature = listSignature;
      ozonState.messageListRenderSignature = listSignature;
      list.innerHTML = filtered.length
        ? filtered.map(item => {
          const title = getOzonDisplayMessageTitle(item);
          const unreadCount = Number(item.unreadCount || 0);
          return `<div class="ozon-message-item ${String(item.id || '') === String(ozonState.selectedMessageId || '') ? 'is-active' : ''}" data-ozon-message-id="${escapeHtml(item.id || '')}">
          <div class="ozon-message-buyer-tag">${escapeHtml(getOzonMessageCategoryLabel(item))}</div>
          <div class="ozon-message-item-head">
            <div class="ozon-list-title ozon-message-title-line" title="${escapeHtml(title)}">
              <span class="ozon-message-title-text">${escapeHtml(title)}</span>
              ${unreadCount ? `<span class="ozon-tab-count">${escapeHtml(unreadCount)}</span>` : ''}
            </div>
            <span class="ozon-message-time">${escapeHtml(formatOzonMessageListTime(item.lastNotificationAt || item.updatedAt || item.createdAt))}</span>
          </div>
          <div class="ozon-list-meta">${escapeHtml(item.accountTitle || '全部店铺')}</div>
          ${getOzonMessageStatusText(item) ? `<div class="ozon-list-meta">${escapeHtml(getOzonMessageStatusText(item))}</div>` : ''}
          <div class="ozon-message-preview">${escapeHtml(item.preview || item.content || '暂无预览')}</div>
        </div>`;
        }).join('')
        : `<div class="ozon-empty">${escapeHtml(data.sourceStatusText || '暂无客服、买家或系统消息')}${errorRows.length ? `<br>${escapeHtml(errorRows.map(item => `${item.accountTitle || item.accountId}: ${item.message}`).join('；'))}` : ''}</div>`;
      list.querySelectorAll('[data-ozon-message-id]').forEach(item => {
        item.addEventListener('click', () => {
          persistCurrentOzonChatReplyDraft();
          ozonState.selectedMessageId = item.dataset.ozonMessageId || '';
          renderOzonMessages({ forceRender: true });
          const current = getCurrentOzonMessage(filtered);
          if (current) {
            loadOzonMessageHistory(current, { forceRender: true, markRead: true }).catch(err => setOzonResult(err.message || '消息详情读取失败', 'error'));
          }
        });
      });
    }
  }
  const current = getCurrentOzonMessage(filtered);
  if (current) {
    renderOzonMessageHistory(current, options);
  } else {
    const detail = document.getElementById('ozonMessageDetail');
    if (detail) {
      detail.dataset.ozonMessageKey = '';
      const hasFilters = Boolean(ozonState.messageType || ozonState.messageQuickFilter || ozonState.messageSearch);
      detail.innerHTML = `<div class="ozon-empty">${hasFilters ? '当前筛选条件下没有会话，请切换左侧栏目或清空筛选。' : (data.sourceStatusText || '暂无 OZON 会话')}</div>`;
    }
  }
}

async function loadOzonFinance(options = {}) {
  const accountId = getOzonActiveAccountId(document.getElementById('ozonFinanceAccountFilter')?.value || ozonState.financeAccountId || '');
  ozonState.financeAccountId = accountId;
  const params = new URLSearchParams();
  if (accountId) params.set('ozonAccountId', accountId);
  if (options.sync) params.set('sync', '1');
  const query = params.toString() ? `?${params.toString()}` : '';
  const summaryWrap = document.getElementById('ozonFinanceSummary');
  const payoutWrap = document.getElementById('ozonFinancePayoutList');
  const hasPreviousFinance = Boolean(ozonState.finance && (Array.isArray(ozonState.finance.payouts) || Array.isArray(ozonState.finance.summaries)));
  ozonState.financeLoading = true;
  if (summaryWrap && !hasPreviousFinance) summaryWrap.innerHTML = renderOzonLoadingEmpty('正在读取财务数据...');
  if (payoutWrap && !hasPreviousFinance) payoutWrap.innerHTML = renderOzonLoadingEmpty('正在读取付款明细...');
  try {
    await loadOzonCached(`/finance${query}`, (result) => {
      ozonState.finance = result;
      ozonState.financeLoading = false;
      renderOzonFinance();
    }, { allowStale: true, keepStaleOnError: true, ...options });
    ozonState.financeLoading = false;
    renderOzonFinance();
  } catch (err) {
    ozonState.financeLoading = false;
    const message = err.message || '财务数据读取失败';
    if (summaryWrap) summaryWrap.innerHTML = renderOzonErrorEmpty(message);
    if (payoutWrap) payoutWrap.innerHTML = renderOzonErrorEmpty(message);
    throw err;
  }
}

async function loadOzonMessages(options = {}) {
  const accountId = setOzonSelectedMessageAccountId();
  const messageType = normalizeOzonMessageCategory(ozonState.messageType || '') || 'buyer';
  ozonState.messageType = messageType;
  const chatStatus = ozonState.messageQuickFilter === 'closed' ? 'Closed' : (['buyer', 'service'].includes(messageType) ? 'Opened' : 'All');
  const unreadOnly = ozonState.messageQuickFilter === 'new';
  const needsOfficialLastSide = ['need_seller', 'need_buyer'].includes(normalizeOzonText(ozonState.messageQuickFilter || ''));
  const params = new URLSearchParams({
    includeHistory: (options.includeHistory || needsOfficialLastSide) ? '1' : '0',
    limit: String(options.limit || 100),
    pages: String(options.pages || 2),
    chatStatus,
    unreadOnly: unreadOnly ? '1' : '0',
    messageType,
    quickFilter: normalizeOzonText(ozonState.messageQuickFilter || '')
  });
  if (options.sync) params.set('sync', '1');
  if (accountId) params.set('ozonAccountId', accountId);
  const query = `?${params.toString()}`;
  const list = document.getElementById('ozonMessageList');
  const detail = document.getElementById('ozonMessageDetail');
  if (!options.skipNotifications) {
    loadOzonNotifications({ ...options, allAccounts: false }).catch(err => {
      console.warn('[小青蛙电商助手][OZON] 通知记录读取失败:', err?.message || err);
      const statusEl = document.getElementById('ozonNotificationStatus');
      if (!options.silent && statusEl) statusEl.textContent = err?.message || '通知记录读取失败';
    });
  }
  const hasPreviousMessages = Boolean(ozonState.messages && Array.isArray(ozonState.messages.messages));
  ozonState.messagesLoading = true;
  if (!options.silent && !hasPreviousMessages && list) list.innerHTML = renderOzonLoadingEmpty('正在读取消息...');
  if (!options.silent && !hasPreviousMessages && detail) detail.innerHTML = renderOzonLoadingEmpty('正在读取消息详情...');
  try {
    await loadOzonCached(`/messages${query}`, (result) => {
      const previousSelectedId = String(ozonState.selectedMessageId || '');
      if (Array.isArray(result.messages)) reconcileOzonReadOverridesFromServer(result.messages);
      ozonState.messages = result;
      if (Array.isArray(ozonState.messages.messages)) applyOzonLocalReadOverrides(ozonState.messages.messages);
      ozonState.messagesLoading = false;
      if (Array.isArray(result.accounts)) {
        const knownIds = new Set((ozonState.accounts || []).map(account => String(account.id || '')));
        result.accounts.forEach(account => {
          if (account?.id && !knownIds.has(String(account.id))) {
            ozonState.accounts.push(account);
            knownIds.add(String(account.id));
          }
        });
      }
      const ids = new Set((Array.isArray(result.messages) ? result.messages : []).map(item => String(item.id || '')).filter(Boolean));
      if (ozonState.selectedMessageId && !ids.has(String(ozonState.selectedMessageId))) {
        ozonState.selectedMessageId = '';
      }
      if (options.keepSelection && previousSelectedId && ids.has(previousSelectedId)) {
        ozonState.selectedMessageId = previousSelectedId;
      }
      if (!ozonState.selectedMessageId && Array.isArray(result.messages) && result.messages[0]?.id) {
        ozonState.selectedMessageId = String(result.messages[0].id);
      }
      if (!options.deferRender && !(options.silent && isOzonChatReplyEditing())) renderOzonMessages({ forceRender: options.forceRender === true, silent: options.silent === true });
      const current = getCurrentOzonMessage(Array.isArray(result.messages) ? result.messages : []);
      if (current && !result.__fromOzonCache && !options.deferRender && !(options.silent && isOzonChatReplyEditing())) {
        loadOzonMessageHistory(current, { silent: options.silent, forceRender: options.forceRender === true }).catch(err => {
          if (!options.silent) setOzonResult(err.message || '消息详情读取失败', 'error');
        });
      }
      if (options.silent && !options.deferRender && !isOzonChatReplyEditing()) refreshOzonActiveMessageSilently().catch(() => {});
    }, { allowStale: false, keepStaleOnError: true, cache: false, ...options });
    ozonState.messagesLoading = false;
    if (!options.deferRender && !(options.silent && isOzonChatReplyEditing())) renderOzonMessages({ forceRender: options.forceRender === true, silent: options.silent === true });
  } catch (err) {
    ozonState.messagesLoading = false;
    const message = err.message || '消息读取失败';
    if (list) list.innerHTML = renderOzonErrorEmpty(message);
    if (detail) detail.innerHTML = renderOzonErrorEmpty(message);
    throw err;
  }
}

function analyzeOzonCollectionLink(link = {}) {
  const title = normalizeOzonText(link.title || link.payload?.title || '');
  const url = normalizeOzonText(link.url || '');
  const image = normalizeOzonText(link.imageUrl || link.payload?.image || link.payload?.imageUrl || '');
  const price = normalizeOzonText(link.priceText || link.payload?.price || link.payload?.priceText || '');
  const problems = [];
  if (!/^https?:\/\/([^/]+\.)?ozon\./i.test(url)) problems.push('链接不是标准 OZON 地址');
  if (!title || title.length < 15) problems.push('标题过短或未采集到标题');
  if (!image) problems.push('未采集到主图');
  if (!price) problems.push('未采集到价格');
  if (link.status === 'archived') problems.push('当前记录已归档');
  return {
    score: Math.max(0, 100 - problems.length * 20),
    problems: problems.length ? problems : ['链接基础信息完整'],
    suggestion: problems.length ? '重新打开商品详情页采集，补齐标题、图片和价格后再进入选品池。' : '可进入选品池或准备上架，后续结合商品诊断继续优化标题和关键词。'
  };
}

function renderOzonLinkDiagnosis() {
  const list = document.getElementById('ozonLinkDiagnosisList');
  const summaryEl = document.getElementById('ozonLinkDiagnosisSummary');
  const rows = (ozonState.collectionLinks || []).map(link => ({ link, diagnosis: analyzeOzonCollectionLink(link) }));
  const risky = rows.filter(row => row.diagnosis.score < 80);
  if (summaryEl) {
    summaryEl.innerHTML = `采集记录：<strong>${rows.length}</strong><br>需要整改：<strong>${risky.length}</strong><br>主要问题：${escapeHtml(risky[0]?.diagnosis.problems?.[0] || '暂无明显问题')}`;
  }
  if (!list) return;
  if (!rows.length) {
    list.classList.add('ozon-empty');
    list.innerHTML = '<div class="ozon-empty">暂无采集链接。</div>';
    return;
  }
  list.classList.remove('ozon-empty');
  list.innerHTML = `<table class="ozon-table"><thead><tr><th>链接商品</th><th>质量分</th><th>问题</th><th>建议</th></tr></thead><tbody>${rows.slice(0, 80).map(row => {
    const link = row.link || {};
    const diag = row.diagnosis || {};
    const tone = diag.score >= 85 ? 'ozon-score-good' : diag.score >= 65 ? 'ozon-score-warn' : 'ozon-score-bad';
    return `<tr>
      <td><div class="ozon-list-title">${escapeHtml(link.title || 'OZON 商品链接')}</div><div class="ozon-list-meta"><a href="${escapeHtml(link.url || '#')}" target="_blank" rel="noopener">${escapeHtml(link.url || '')}</a></div></td>
      <td><span class="ozon-score-badge ${tone}">${escapeHtml(diag.score)}分</span></td>
      <td><div class="ozon-problem-list">${diag.problems.map(text => `<span>• ${escapeHtml(text)}</span>`).join('')}</div></td>
      <td><div class="ozon-suggestion-text">${escapeHtml(diag.suggestion)}</div></td>
    </tr>`;
  }).join('')}</tbody></table>`;
}

function renderOzonOrders() {
  const wrap = document.getElementById('ozonOrderListTable') || document.getElementById('ozonOrderList');
  syncOzonAccountSelects();
  updateOzonOrderStatusTabs();
  if (!wrap) return;
  bindOzonOrderTableDelegates();
  if (!ozonState.orders.length) {
    updateOzonPagination('order');
    wrap.innerHTML = '<div class="ozon-empty">服务器暂无订单缓存。请确认 OZON 通知已开启；未开通知的店铺可在订单列表右上角手动拉取订单。</div>';
    return;
  }
  wrap.classList.add('ozon-order-table-wrap');
  const productMap = getOzonCachedProductMap();
  wrap.innerHTML = `<table class="ozon-table ozon-order-table">
  ${renderOzonColumnGroup(OZON_ORDER_LIST_COLUMN_DEFAULTS, 'orderListFull1080')}
  <thead><tr>
    <th>序号</th>
    <th>销售单号 / 货号</th><th>查看详情</th><th>图片</th><th>店铺</th><th>采购信息</th><th>金额</th><th>手续费</th><th>运费</th><th>成本</th><th>利润</th><th>利润率</th><th>状态</th><th>采购单</th><th>运单</th><th>下单时间</th>
  </tr></thead><tbody>${ozonState.orders.map((order, index) => {
    const key = getOzonOrderKey(order);
    const products = getOzonOrderProducts(order);
    const rowProducts = products.length ? products : [null];
    const orderMoney = getOzonOrderMoney(order);
    const delivery = getOzonOrderDelivery(order);
    const bucket = normalizeOzonOrderBucket(order.bucket || order.bucketLabel || 'all');
    const officialStatusLabel = getOzonOrderStatusLabel(order.status || '', order);
    const statusTone = getOzonOrderStatusTone(order.status || '');
    const statusTitle = normalizeOzonText(order.status || '');
    const purchaseNo = normalizeOzonText(order.purchaseOrderNumber || order.rawData?.purchase_order_number || order.rawData?.purchaseOrderNumber || '');
    const orderNumberText = normalizeOzonText(order.postingNumber || order.orderId || '');
    const orderKeyText = getOzonOrderKey(order);
    const accountId = getOzonOrderAccountId(order);
    const accountTitle = normalizeOzonText(order.accountTitle || order.shopTitle || getOzonAccountLabel(accountId) || 'OZON 店铺');
    const orderTimeText = getOzonOfficialOrderDateText(order);
    const serialNumber = index + 1;
    const waybillMethod = formatOzonWaybillMethod(delivery.method || '');
    const waybillHtml = escapeHtml(waybillMethod || '--');
    const rowSpan = Math.max(1, rowProducts.length);
    const groupClass = index % 2 === 1 ? 'is-group-even' : 'is-group-odd';
    const offerIds = [...new Set(products.map(product => normalizeOzonText(
      product?.offerId ||
      product?.offer_id ||
      product?.offer ||
      product?.raw?.offer_id ||
      product?.raw?.offerId ||
      ''
    )).filter(Boolean))];
    const offerText = offerIds.length ? (offerIds.length > 1 ? `${offerIds[0]} +${offerIds.length - 1}` : offerIds[0]) : '';
    const offerCopyText = offerIds.join('\n');
    return rowProducts.map((product, productIndex) => {
      const isFirst = productIndex === 0;
      const money = product ? getOzonOrderProductMoney(order, product, orderMoney, productIndex) : orderMoney;
      const hasProfit = !(money.profit === null || money.profit === undefined);
      const profitText = hasProfit ? formatOzonAmount(money.profit, money.profitCurrency || 'CNY') : (money.profitBlockReason || '待财务');
      const profitRateText = money.profitRate === null || money.profitRate === undefined ? '--' : `${Number(money.profitRate || 0).toFixed(2)}%`;
      const profitClass = !hasProfit ? 'ozon-muted-inline' : Number(money.profit || 0) >= 0 ? 'ozon-profit-good' : 'ozon-profit-bad';
      const costText = money.cost === null || money.cost === undefined ? '' : formatOzonCnyAmount(money.cost);
      const orderCostText = orderMoney.cost === null || orderMoney.cost === undefined ? '' : formatOzonCnyAmount(orderMoney.cost);
      const costMutedClass = orderMoney.cost === null || orderMoney.cost === undefined ? ' is-empty' : '';
      const commissionFeeText = getOzonOfficialCommissionText(money);
      const deliveryFeeText = getOzonOfficialDeliveryFeeText(money);
      const quantity = product ? (getOzonOrderProductQuantity(product) || 1) : 0;
      const detailsHtml = product ? (() => {
      const cached = getOzonOrderCachedProduct(product, productMap, order);
      const image = pickOzonImage(product) || cached?.ui?.image || '';
      const productTitle = product.name || cached?.ui?.title || product.financial?.name || 'OZON 商品';
      return `<div class="ozon-order-product-mini">${renderOzonClickableImageThumb(image, 'ozon-order-product-thumb', productTitle)}${quantity > 1 ? `<span class="ozon-order-product-qty">x${escapeHtml(quantity)}</span>` : ''}</div>`;
      })() : `<div class="ozon-list-title">${escapeHtml(order.productsCount || 0)} 个商品</div>`;
      const firstCells = isFirst ? `
      <td rowspan="${rowSpan}" class="ozon-order-index-cell">${escapeHtml(serialNumber)}</td>
      <td rowspan="${rowSpan}" class="ozon-order-code-cell">
        <div class="ozon-order-code-line is-posting">
          <span class="ozon-order-code-label">订单</span>
          <span class="ozon-list-title ozon-mono" title="${escapeHtml(orderNumberText || '')}">${escapeHtml(orderNumberText || '--')}</span>
          <button class="btn btn-default btn-sm" type="button" data-ozon-order-copy="${escapeHtml(orderNumberText)}" data-ozon-copy-kind="posting"${orderNumberText ? '' : ' disabled'}>复制</button>
        </div>
        <div class="ozon-order-code-line is-offer">
          <span class="ozon-order-code-label">货号</span>
          <span class="ozon-list-title ozon-mono" title="${escapeHtml(offerCopyText || '')}">${escapeHtml(offerText || '--')}</span>
          <button class="btn btn-default btn-sm" type="button" data-ozon-order-copy="${escapeHtml(offerCopyText)}" data-ozon-copy-kind="offer"${offerCopyText ? '' : ' disabled'}>复制</button>
        </div>
      </td>
      <td rowspan="${rowSpan}" class="ozon-order-detail-action-cell">
        <button class="btn btn-default btn-sm" type="button" data-ozon-order-detail="${escapeHtml(order.postingNumber || '')}" data-ozon-order-account-id="${escapeHtml(getOzonOrderAccountId(order))}">查看详情</button>
      </td>` : '';
      const orderTailCells = isFirst ? `
      <td rowspan="${rowSpan}" class="ozon-order-shop-cell"><div class="ozon-list-title">${escapeHtml(accountTitle)}</div></td>
      <td rowspan="${rowSpan}" class="ozon-order-purchase-action-cell">
        <button class="btn btn-default btn-sm ozon-order-cost-button${costMutedClass}" type="button" data-ozon-order-cost="${escapeHtml(orderKeyText)}">${escapeHtml(orderCostText || '添加采购')}</button>
      </td>
      ` : '';
      const finalCells = isFirst ? `
      <td rowspan="${rowSpan}" class="ozon-order-status-cell"><span class="ozon-status-pill ozon-status-${escapeHtml(statusTone)}" title="${escapeHtml(statusTitle)}">${escapeHtml(officialStatusLabel)}</span></td>
      <td rowspan="${rowSpan}" class="ozon-order-purchase-cell"><span class="ozon-mono">${escapeHtml(purchaseNo || '--')}</span></td>
      <td rowspan="${rowSpan}" class="ozon-order-waybill-cell"><div class="ozon-list-meta">${waybillHtml}</div></td>
      <td rowspan="${rowSpan}" class="ozon-order-time-cell"><div class="ozon-list-meta">${escapeHtml(orderTimeText)}</div></td>` : '';
      return `<tr class="${groupClass}${isFirst ? ' ozon-order-main-row' : ' ozon-order-sub-row'}" data-ozon-order-bucket="${escapeHtml(bucket)}" data-ozon-order-row-key="${escapeHtml(getOzonOrderProductRowKey(order, product || {}, productIndex))}">
      ${firstCells}
      <td class="ozon-order-products-cell"><div class="ozon-order-products-layout"><div class="ozon-order-product-list">${detailsHtml}</div></div></td>
      ${orderTailCells}
      <td class="ozon-order-amount-cell ozon-money-cell"><strong>${escapeHtml(formatOzonOrderAmount(money.amount || 0, money.currency || order.currencyCode || 'RUB'))}</strong></td>
      <td class="ozon-order-fee-cell ozon-money-cell"><span class="ozon-cost-fee${money.hasEstimatedCommission ? ' is-estimated' : ''}" title="${escapeHtml(money.commissionEstimate?.reason || '')}">${escapeHtml(commissionFeeText)}</span></td>
      <td class="ozon-order-delivery-fee-cell ozon-money-cell"><span class="ozon-cost-fee">${escapeHtml(deliveryFeeText)}</span></td>
      <td class="ozon-order-cost-cell ozon-money-cell"><span class="ozon-order-cost-value">${escapeHtml(costText || '')}</span></td>
      <td class="ozon-order-profit-cell ozon-money-cell"><span class="${profitClass}">${escapeHtml(profitText)}</span></td>
      <td class="ozon-order-rate-cell ozon-money-cell"><span class="${profitClass}">${escapeHtml(profitRateText)}</span></td>
      ${finalCells}
    </tr>`;
    }).join('');
  }).join('')}</tbody></table>`;
  setupOzonResizableTable(wrap.querySelector('.ozon-order-table'), 'orderListFull1080', OZON_ORDER_LIST_COLUMN_DEFAULTS, OZON_ORDER_LIST_COLUMN_MINS);
  updateOzonPagination('order');
}

function ensureOzonOrderDetailModal() {
  let modal = document.getElementById('ozonOrderDetailModal');
  if (modal) return modal;
  modal = document.createElement('div');
  modal.id = 'ozonOrderDetailModal';
  modal.style.cssText = 'position:fixed;inset:0;z-index:10000;display:none;align-items:center;justify-content:center;background:rgba(15,23,42,.45);padding:20px;';
  modal.setAttribute('aria-hidden', 'true');
  modal.innerHTML = `<div style="width:min(920px,96vw);max-height:86vh;overflow:auto;background:#fff;border-radius:8px;box-shadow:0 24px 70px rgba(15,23,42,.28);">
    <div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--border);gap:12px;">
      <h3 style="margin:0;font-size:16px;color:var(--text-primary);">OZON 订单明细</h3>
      <button class="btn btn-default btn-sm" type="button" data-ozon-detail-close>关闭</button>
    </div>
    <div id="ozonOrderDetailBody" style="padding:16px;"></div>
  </div>`;
  modal.querySelector('[data-ozon-detail-close]')?.addEventListener('click', () => setOzonOrderDetailModalVisible(modal, false));
  modal.addEventListener('click', (event) => {
    if (event.target === modal) setOzonOrderDetailModalVisible(modal, false);
  });
  document.body.appendChild(modal);
  return modal;
}

function setOzonOrderDetailModalVisible(modal, visible) {
  if (!modal) return;
  modal.style.setProperty('display', visible ? 'flex' : 'none', 'important');
  modal.style.visibility = visible ? 'visible' : 'hidden';
  modal.style.opacity = visible ? '1' : '0';
  modal.setAttribute('aria-hidden', visible ? 'false' : 'true');
}

function ensureOzonPurchaseInfoModal() {
  let modal = document.getElementById('ozonPurchaseInfoModal');
  if (modal) return modal;
  modal = document.createElement('div');
  modal.id = 'ozonPurchaseInfoModal';
  modal.className = 'ozon-modal ozon-purchase-modal';
  modal.setAttribute('aria-hidden', 'true');
  modal.innerHTML = `<div class="ozon-modal-panel">
    <div class="ozon-modal-head">
      <h3>编辑采购信息</h3>
      <button class="ozon-icon-btn" type="button" data-ozon-purchase-close aria-label="关闭">×</button>
    </div>
    <form id="ozonPurchaseInfoForm" class="ozon-purchase-form">
      <input type="hidden" id="ozonPurchaseOrderKey">
      <input type="hidden" id="ozonPurchasePostingNumber">
      <input type="hidden" id="ozonPurchaseAccountId">
      <label class="ozon-field">
        <span>商品成本（人民币）</span>
        <input id="ozonPurchaseCostInput" type="number" min="0" step="0.01" placeholder="例如 35.80">
      </label>
      <label class="ozon-field">
        <span>采购单号</span>
        <input id="ozonPurchaseOrderNumberInput" type="text" maxlength="120" placeholder="填写采购平台订单号">
      </label>
      <label class="ozon-field">
        <span>采购供应商</span>
        <select id="ozonPurchaseSupplierInput">
          <option value="">请选择</option>
          <option value="淘宝">淘宝</option>
          <option value="拼多多">拼多多</option>
          <option value="1688">1688</option>
          <option value="微信">微信</option>
        </select>
      </label>
      <div id="ozonPurchaseInfoResult" class="ozon-modal-result"></div>
      <div class="ozon-actions">
        <button class="btn btn-default" type="button" data-ozon-purchase-close>取消</button>
        <button class="btn btn-primary" type="button" id="ozonPurchaseSaveBtn">保存</button>
      </div>
    </form>
  </div>`;
  modal.querySelectorAll('[data-ozon-purchase-close]').forEach(button => {
    button.addEventListener('click', () => setOzonPurchaseInfoModalVisible(false));
  });
  modal.addEventListener('click', (event) => {
    if (event.target === modal) setOzonPurchaseInfoModalVisible(false);
  });
  modal.querySelector('#ozonPurchaseInfoForm')?.addEventListener('submit', saveOzonPurchaseInfo);
  modal.querySelector('#ozonPurchaseSaveBtn')?.addEventListener('click', saveOzonPurchaseInfo);
  document.body.appendChild(modal);
  return modal;
}

function setOzonPurchaseInfoModalVisible(visible) {
  const modal = ensureOzonPurchaseInfoModal();
  modal.classList.toggle('is-open', Boolean(visible));
  modal.setAttribute('aria-hidden', visible ? 'false' : 'true');
}

function setOzonPurchaseInfoResult(message = '', tone = 'info') {
  const el = document.getElementById('ozonPurchaseInfoResult');
  if (!el) return;
  el.textContent = message || '';
  el.style.color = tone === 'success' ? '#16a34a' : tone === 'error' ? '#dc2626' : '#475569';
}

function openOzonPurchaseInfoModal(orderKey = '') {
  const order = findOzonOrderByKey(orderKey);
  if (!order) {
    setOzonResult('未找到该订单，请刷新订单列表后重试', 'error');
    return;
  }
  const modal = ensureOzonPurchaseInfoModal();
  modal.querySelector('#ozonPurchaseOrderKey').value = getOzonOrderKey(order);
  modal.querySelector('#ozonPurchasePostingNumber').value = order.postingNumber || '';
  modal.querySelector('#ozonPurchaseAccountId').value = getOzonOrderAccountId(order);
  modal.querySelector('#ozonPurchaseCostInput').value = order.purchaseCost ?? '';
  modal.querySelector('#ozonPurchaseOrderNumberInput').value = order.purchaseOrderNumber || '';
  modal.querySelector('#ozonPurchaseSupplierInput').value = order.purchaseSupplier || '';
  setOzonPurchaseInfoResult('');
  setOzonPurchaseInfoModalVisible(true);
  setTimeout(() => modal.querySelector('#ozonPurchaseCostInput')?.focus(), 0);
}

async function saveOzonPurchaseInfo(event) {
  event?.preventDefault?.();
  event?.stopPropagation?.();
  const form = document.getElementById('ozonPurchaseInfoForm');
  const submitButton = document.getElementById('ozonPurchaseSaveBtn') || form?.querySelector('button[type="submit"]');
  const previousSubmitText = submitButton?.textContent || '保存';
  if (submitButton?.dataset.saving === '1') return;
  const key = document.getElementById('ozonPurchaseOrderKey')?.value || '';
  const postingNumber = document.getElementById('ozonPurchasePostingNumber')?.value || '';
  const ozonAccountId = document.getElementById('ozonPurchaseAccountId')?.value || '';
  const purchaseCost = document.getElementById('ozonPurchaseCostInput')?.value ?? '';
  const purchaseOrderNumber = document.getElementById('ozonPurchaseOrderNumberInput')?.value || '';
  const purchaseSupplier = document.getElementById('ozonPurchaseSupplierInput')?.value || '';
  const costNumber = parseOzonNumber(purchaseCost);
  if (!postingNumber || !ozonAccountId) {
    setOzonPurchaseInfoResult('缺少订单号或店铺信息，请刷新订单列表后重试', 'error');
    return;
  }
  if (purchaseCost !== '' && !Number.isFinite(costNumber)) {
    setOzonPurchaseInfoResult('商品成本必须填写数字，例如 35.80', 'error');
    return;
  }
  setOzonPurchaseInfoResult('正在保存...');
  setOzonResult('正在保存采购信息...');
  if (submitButton) {
    submitButton.dataset.saving = '1';
    submitButton.disabled = true;
    submitButton.textContent = '保存中...';
  }
  const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
  const timeoutId = controller ? window.setTimeout(() => controller.abort(), 15000) : null;
  try {
    const result = await ozonRequest(`/orders/${encodeURIComponent(postingNumber)}/purchase-info`, {
      method: 'POST',
      signal: controller?.signal,
      body: JSON.stringify({
        ozonAccountId,
        purchaseCost,
        purchaseOrderNumber,
        purchaseSupplier
      })
    });
    if (result.ok === false) throw new Error(result.error || '保存采购信息失败');
    const saved = result.order || {};
    clearOzonReadCacheByBase('/orders');
    const index = (ozonState.orders || []).findIndex(order => {
      if (getOzonOrderKey(order) === key) return true;
      return String(order.postingNumber || '') === String(postingNumber) && String(getOzonOrderAccountId(order)) === String(ozonAccountId);
    });
    const savedCost = saved.purchaseCost ?? saved.purchase_cost;
    const savedPurchaseOrderNumber = saved.purchaseOrderNumber ?? saved.purchase_order_number;
    const savedPurchaseSupplier = saved.purchaseSupplier ?? saved.purchase_supplier;
    if (index >= 0) {
      ozonState.orders[index] = {
        ...ozonState.orders[index],
        purchaseCost: savedCost ?? (purchaseCost === '' ? null : parseOzonNumber(purchaseCost)),
        purchaseOrderNumber: savedPurchaseOrderNumber ?? purchaseOrderNumber,
        purchaseSupplier: savedPurchaseSupplier ?? purchaseSupplier,
        purchaseUpdatedAt: saved.purchaseUpdatedAt || saved.purchase_updated_at || new Date().toISOString(),
        purchaseUpdatedBy: saved.purchaseUpdatedBy || saved.purchase_updated_by || ''
      };
      if (ozonState.orders[index].rawData && typeof ozonState.orders[index].rawData === 'object') {
        ozonState.orders[index].rawData = {
          ...ozonState.orders[index].rawData,
          purchase_cost: ozonState.orders[index].purchaseCost,
          purchase_order_number: ozonState.orders[index].purchaseOrderNumber,
          purchase_supplier: ozonState.orders[index].purchaseSupplier
        };
      }
    }
    renderOzonOrders();
    setOzonPurchaseInfoResult('采购信息已保存', 'success');
    setOzonResult('采购信息已保存', 'success');
    loadOzonOrders({ forceRefresh: true, silent: true }).catch(err => {
      console.warn('[小青蛙电商助手][OZON] 成本保存后刷新订单失败:', err?.message || err);
      setOzonResult(`采购信息已保存，但刷新订单失败：${err?.message || err}`, 'warn');
    });
    setTimeout(() => setOzonPurchaseInfoModalVisible(false), 900);
  } catch (err) {
    const message = err?.name === 'AbortError'
      ? '保存采购信息超时，请稍后重试'
      : (err.message || '保存采购信息失败');
    setOzonPurchaseInfoResult(message, 'error');
    setOzonResult(message, 'error');
  } finally {
    if (timeoutId) window.clearTimeout(timeoutId);
    if (submitButton) {
      delete submitButton.dataset.saving;
      submitButton.disabled = false;
      submitButton.textContent = previousSubmitText;
    }
  }
}

function renderOzonOrderDetail(order = {}) {
  const products = getOzonOrderProducts({ rawData: order });
  const customer = order.customer || {};
  const addressee = order.addressee || {};
  const delivery = order.delivery_method || {};
  const analytics = order.analytics_data || {};
  const money = getOzonOrderMoney({ rawData: order, customerPrice: order.customer_price, currencyCode: order.currency_code });
  const deliveryUi = getOzonOrderDelivery({ rawData: order });
  const cancelText = getOzonOrderCancelText({ rawData: order });
  const modal = ensureOzonOrderDetailModal();
  const body = document.getElementById('ozonOrderDetailBody');
  if (!body) return;
  const productRows = products.map((product, index) => {
    const fin = product.financial || {};
    const image = pickOzonImage(product);
    return `<tr>
      <td>${image ? `<img class="ozon-order-product-thumb" src="${escapeHtml(image)}" alt="">` : '<div class="ozon-order-product-thumb"></div>'}</td>
      <td><span class="ozon-mono">${escapeHtml(product.offer_id || product.offerId || product.sku || fin.offer_id || '')}</span></td>
      <td>${escapeHtml(product.name || fin.name || '')}</td>
      <td>${escapeHtml(getOzonOrderProductQuantity(product) || '--')}</td>
      <td>${escapeHtml(formatOzonOrderAmount(product.price || fin.price || 0, product.currency_code || product.currencyCode || fin.currency_code || fin.currencyCode || order.currency_code || order.currencyCode || 'RUB'))}</td>
    </tr>`;
  }).join('');
  body.innerHTML = `<div class="ozon-card-grid" style="grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin-bottom:14px;">
    <div class="ozon-mini-card"><div class="ozon-mini-label">Posting</div><div class="ozon-list-title">${escapeHtml(order.posting_number || '')}</div></div>
    <div class="ozon-mini-card"><div class="ozon-mini-label">Order</div><div class="ozon-list-title">${escapeHtml(order.order_number || order.order_id || '')}</div></div>
    <div class="ozon-mini-card"><div class="ozon-mini-label">状态</div><div class="ozon-list-title">${escapeHtml(getOzonOrderStatusLabel(order.status || '', order))}</div></div>
    <div class="ozon-mini-card"><div class="ozon-mini-label">订单金额</div><div class="ozon-list-title">${escapeHtml(formatOzonOrderAmount(money.amount || 0, money.currency || order.currency_code || order.currencyCode || 'RUB'))}</div></div>
  </div>
  <div class="ozon-table-wrap" style="margin-bottom:14px;"><table class="ozon-table"><tbody>
    <tr><th>物流方式</th><td>${escapeHtml(delivery.name || deliveryUi.method || '')}</td><th>物流单号</th><td><span class="ozon-mono">${escapeHtml(deliveryUi.tracking || '')}</span></td></tr>
    <tr><th>收件人</th><td>${escapeHtml(addressee.name || customer.name || '')}</td><th>电话</th><td>${escapeHtml(addressee.phone || customer.phone || '')}</td></tr>
    <tr><th>城市</th><td>${escapeHtml(analytics.city || '')}</td><th>仓库</th><td>${escapeHtml(deliveryUi.warehouse || '')}</td></tr>
    <tr><th>手续费</th><td>${escapeHtml(getOzonOfficialCommissionText(money))}</td><th>运费</th><td>${escapeHtml(getOzonOfficialDeliveryFeeText(money))}</td></tr>
    <tr><th>采购单</th><td>${escapeHtml(order.purchaseOrderNumber || '')}</td><th>成本</th><td>${escapeHtml(formatOzonCnyAmount(money.cost || 0))}</td></tr>
    <tr><th>利润</th><td colspan="3">${money.profit === null || money.profit === undefined ? '待数据' : escapeHtml(formatOzonAmount(money.profit || 0, money.profitCurrency || 'CNY'))}</td></tr>
    <tr><th>取消信息</th><td colspan="3">${escapeHtml(cancelText)}</td></tr>
  </tbody></table></div>
  <div class="ozon-table-wrap"><table class="ozon-table"><thead><tr><th>图片</th><th>Offer / SKU</th><th>商品</th><th>数量</th><th>价格</th></tr></thead><tbody>${productRows || '<tr><td colspan="5">暂无商品明细</td></tr>'}</tbody></table></div>`;
  setOzonOrderDetailModalVisible(modal, true);
}

async function loadOzonOrderDetail(postingNumber, accountId = '') {
  if (!postingNumber) return;
  const resolvedAccountId = accountId || getSelectedOzonScopeId('order');
  if (!resolvedAccountId) {
    setOzonResult('未找到该订单所属店铺，请先确认服务器订单缓存中有该订单', 'error');
    return;
  }
  setOzonResult('正在读取 OZON 订单明细...');
  try {
    const result = await ozonRequest(`/orders/${encodeURIComponent(postingNumber)}?ozonAccountId=${encodeURIComponent(resolvedAccountId)}`);
    const cached = (ozonState.orders || []).find(order => String(order.postingNumber || '') === String(postingNumber) && (!resolvedAccountId || String(getOzonOrderAccountId(order)) === String(resolvedAccountId))) || {};
    const detail = result.order || {};
    renderOzonOrderDetail({
      ...detail,
      purchaseCost: cached.purchaseCost,
      purchaseOrderNumber: cached.purchaseOrderNumber,
      purchaseSupplier: cached.purchaseSupplier,
      financeSummary: cached.financeSummary
    });
    setOzonResult('订单明细已加载', 'success');
  } catch (err) {
    setOzonResult(err.message || '读取订单明细失败', 'error');
  }
}

function renderOzonCollectionLinks() {
  const list = document.getElementById('ozonCollectionList');
  setOzonCollectionCounts();
  if (!list) return;
  if (!ozonState.collectionLinks.length) {
    list.innerHTML = '<div class="ozon-empty">暂无采集记录。打开 OZON 商品页后点击“采集到小青蛙”。</div>';
    return;
  }
  list.innerHTML = ozonState.collectionLinks.slice(0, 200).map(link => {
    const id = String(link.id || '');
    const url = link.url || '#';
    const status = link.status || 'collected';
    const checked = ozonState.selectedCollectionIds.has(id) ? 'checked' : '';
    const image = link.imageUrl ? `<img class="ozon-thumb" src="${escapeHtml(link.imageUrl)}" alt="">` : '';
    return `<div class="ozon-list-item" data-ozon-link-id="${escapeHtml(id)}">
      <div class="ozon-list-head">
        <input class="ozon-check" type="checkbox" data-ozon-link-check="${escapeHtml(id)}" ${checked}>
        <div>
          <div class="ozon-list-title">${escapeHtml(link.title || link.url || 'OZON 商品')}</div>
          <div class="ozon-list-meta">
            ${escapeHtml(link.priceText || '')}${link.productId ? '<br>商品ID: ' + escapeHtml(link.productId) : ''}${link.accountTitle ? '<br>店铺: ' + escapeHtml(link.accountTitle) : ''}<br>
            <a href="${escapeHtml(url)}" target="_blank" rel="noopener">${escapeHtml(url)}</a>
          </div>
        </div>
        <div style="display:grid;gap:8px;justify-items:end;">${image}<span class="ozon-status-pill ozon-status-${escapeHtml(status)}">${escapeHtml(getOzonStatusLabel(status))}</span></div>
      </div>
      <div class="ozon-actions">
        <button class="btn btn-default btn-sm" type="button" data-ozon-open-link="${escapeHtml(url)}">打开</button>
        <button class="btn btn-default btn-sm" type="button" data-ozon-copy-link="${escapeHtml(url)}">复制链接</button>
        <button class="btn btn-default btn-sm" type="button" data-ozon-status="selected" data-ozon-link-id="${escapeHtml(id)}">加入选品池</button>
        <button class="btn btn-primary btn-sm" type="button" data-ozon-status="ready" data-ozon-link-id="${escapeHtml(id)}">准备上架</button>
        <button class="btn btn-default btn-sm" type="button" data-ozon-status="listed" data-ozon-link-id="${escapeHtml(id)}">已上架</button>
        <button class="btn btn-default btn-sm" type="button" data-ozon-status="archived" data-ozon-link-id="${escapeHtml(id)}">归档</button>
      </div>
    </div>`;
  }).join('');
  list.querySelectorAll('[data-ozon-link-check]').forEach(input => {
    input.addEventListener('change', rememberOzonCheckedState);
  });
  list.querySelectorAll('[data-ozon-status]').forEach(button => {
    button.addEventListener('click', () => updateOzonCollectionStatus([Number(button.dataset.ozonLinkId)], button.dataset.ozonStatus));
  });
  list.querySelectorAll('[data-ozon-open-link]').forEach(button => {
    button.addEventListener('click', () => window.open(button.dataset.ozonOpenLink || 'https://www.ozon.ru/', '_blank'));
  });
  list.querySelectorAll('[data-ozon-copy-link]').forEach(button => {
    button.addEventListener('click', () => copyOzonText(button.dataset.ozonCopyLink || ''));
  });
}

async function loadOzonAccounts(options = {}) {
  try {
    await loadOzonCached('/accounts', (result) => {
      ozonState.accountsError = '';
      ozonState.accounts = Array.isArray(result.accounts) ? result.accounts : [];
      if (ozonState.selectedAccountId && !ozonState.accounts.some(account => String(account.id) === String(ozonState.selectedAccountId))) {
        ozonState.selectedAccountId = '';
      }
      ensureOzonScopedAccountDefaults();
      syncOzonAccountsToExtension();
      renderOzonAccounts();
      renderOzonHomeAuthorizedAccounts(ozonState.accounts);
      updateOzonTabSyncStamps();
    }, { requestTimeoutMs: OZON_HOME_READ_TIMEOUT_MS, ...options });
  } catch (err) {
    ozonState.accountsError = err.message || '店铺授权读取失败';
    renderOzonAccounts();
    renderOzonOverview();
    updateOzonTabSyncStamps();
    throw err;
  }
}

async function loadOzonProducts(options = {}) {
  const productList = document.getElementById('ozonProductList');
  const restoreScrollTop = options.preserveScroll ? Number(productList?.scrollTop || 0) : undefined;
  const query = buildOzonProductQuery();
  await loadOzonCached(`/products${query}`, (result) => {
    ozonState.products = Array.isArray(result.products) ? result.products : [];
    ozonState.productTotal = Number(result.total || ozonState.products.length || 0);
    ozonState.productStatusCounts = result.statusCounts || {};
    renderOzonProducts({ restoreScrollTop });
  }, options);
}

function buildOzonAnalyticsQuery(type = 'product') {
  const params = new URLSearchParams();
  const accountId = type === 'order'
    ? getOzonScopedAccountId(document.getElementById('ozonOrderAnalysisAccountFilter')?.value ?? ozonState.orderAnalysisAccountId ?? '', { allowAll: true })
    : getOzonActiveAccountId(document.getElementById('ozonProductDiagnosisAccountFilter')?.value || ozonState.productDiagnosisAccountId || '');
  if (accountId) params.set('ozonAccountId', accountId);
  if (type === 'order') {
    const sku = document.getElementById('ozonOrderAnalysisSkuInput')?.value?.trim() || ozonState.orderAnalysisSku || '';
    ozonState.orderAnalysisAccountId = accountId;
    ozonState.orderAnalysisSku = sku;
    params.set('days', '70');
    params.set('limit', '12000');
    if (sku) params.set('sku', sku);
  } else {
    const sku = document.getElementById('ozonProductDiagnosisSkuInput')?.value?.trim() || ozonState.productDiagnosisSku || '';
    const risk = document.getElementById('ozonProductRiskFilter')?.value || ozonState.productDiagnosisRisk || '';
    const keyword = document.getElementById('ozonProductDiagnosisKeywordInput')?.value?.trim() || ozonState.productDiagnosisKeyword || '';
    const visibility = normalizeOzonVisibilityForUi(document.getElementById('ozonProductVisibilityFilter')?.value || ozonState.productVisibility || 'IN_SALE') || 'IN_SALE';
    const page = Math.max(1, Number(ozonState.productDiagnosisPage || 1) || 1);
    const limit = Math.max(1, Number(ozonState.productDiagnosisLimit || OZON_PRODUCT_DIAGNOSIS_RESPONSE_LIMIT) || OZON_PRODUCT_DIAGNOSIS_RESPONSE_LIMIT);
    ozonState.productDiagnosisAccountId = accountId;
    ozonState.productDiagnosisSku = sku;
    ozonState.productDiagnosisRisk = risk;
    ozonState.productDiagnosisKeyword = keyword;
    ozonState.productVisibility = visibility;
    params.set('limit', '12000');
    params.set('orderLimit', '12000');
    params.set('hydrate', '0');
    params.set('responseLimit', String(limit));
    params.set('responseOffset', String((page - 1) * limit));
    if (visibility) params.set('visibility', visibility);
    if (sku) params.set('sku', sku);
    if (risk) params.set('risk', risk);
    if (keyword) params.set('keyword', keyword);
  }
  const text = params.toString();
  return text ? `?${text}` : '';
}

function buildOzonProductListDiagnosisQuery() {
  const params = new URLSearchParams();
  const accountId = getOzonActiveAccountId(document.getElementById('ozonProductAccountFilter')?.value || ozonState.productAccountId || '');
  const visibility = normalizeOzonVisibilityForUi(document.getElementById('ozonProductListVisibilityFilter')?.value || ozonState.productListVisibility || 'ALL') || 'ALL';
  const productIds = new Set();
  const offerIds = new Set();
  const skus = new Set();
  (ozonState.products || []).forEach((product) => {
    const identity = getOzonProductIdentity(product);
    if (identity.productId) productIds.add(identity.productId);
    if (identity.offerId) offerIds.add(identity.offerId);
    if (identity.sku) skus.add(identity.sku);
  });
  if (accountId) params.set('ozonAccountId', accountId);
  if (visibility) params.set('visibility', visibility);
  if (productIds.size) params.set('productIds', Array.from(productIds).join(','));
  if (offerIds.size) params.set('offerIds', Array.from(offerIds).join(','));
  if (skus.size) params.set('skus', Array.from(skus).join(','));
  params.set('limit', String(Math.max(50, (ozonState.products || []).length || ozonState.productLimit || 50)));
  params.set('orderLimit', '12000');
  params.set('hydrate', '0');
  params.set('responseLimit', String(Math.min(500, Math.max(50, (ozonState.products || []).length || 50))));
  params.set('responseOffset', '0');
  const text = params.toString();
  return text ? `?${text}` : '';
}

async function loadOzonProductListDiagnosis(options = {}) {
  const restoreScrollTop = Math.max(0, Number(document.getElementById('ozonProductList')?.scrollTop || 0));
  if (!Array.isArray(ozonState.products) || !ozonState.products.length) {
    ozonState.productListDiagnosis = null;
    ozonState.productListDiagnosisLoading = false;
    renderOzonProducts({ restoreScrollTop });
    return null;
  }
  const seq = (ozonState.productListDiagnosisRequestSeq || 0) + 1;
  ozonState.productListDiagnosisRequestSeq = seq;
  ozonState.productListDiagnosisLoading = true;
  renderOzonProducts({ restoreScrollTop });
  const requestPath = `/analytics/products${buildOzonProductListDiagnosisQuery()}`;
  if (options.forceRefresh) clearOzonReadCacheForPath(requestPath);
  try {
    let latestResult = null;
    await loadOzonCached(requestPath, (result) => {
      if (seq !== ozonState.productListDiagnosisRequestSeq) return;
      latestResult = result;
      ozonState.productListDiagnosis = result;
      ozonState.productListDiagnosisLoading = false;
      renderOzonProducts({ restoreScrollTop });
    }, options);
    if (seq === ozonState.productListDiagnosisRequestSeq) {
      ozonState.productListDiagnosisLoading = false;
      renderOzonProducts({ restoreScrollTop });
    }
    return latestResult || ozonState.productListDiagnosis;
  } catch (err) {
    if (seq !== ozonState.productListDiagnosisRequestSeq) return null;
    ozonState.productListDiagnosisLoading = false;
    renderOzonProducts({ restoreScrollTop });
    if (!options.silent) setOzonResult(err.message || '商品列表评分读取失败', 'error');
    return null;
  }
}

async function loadOzonOrderAnalysis(options = {}) {
  const seq = (ozonState.orderAnalysisRequestSeq || 0) + 1;
  ozonState.orderAnalysisRequestSeq = seq;
  if (options.forceRefresh) resetOzonSalesAiAdvice();
  const hasExistingRows = Array.isArray(ozonState.orderAnalysis?.items) && ozonState.orderAnalysis.items.length > 0;
  ozonState.orderAnalysisLoading = true;
  const wrap = document.getElementById('ozonOrderAnalysisList');
  if (wrap && !hasExistingRows && !options.silent) wrap.innerHTML = renderOzonLoadingEmpty('正在读取订单分析...');
  const query = buildOzonAnalyticsQuery('order');
  const requestPath = `/analytics/orders${query}`;
  if (options.forceRefresh) clearOzonReadCacheForPath(requestPath);
  try {
    await loadOzonCached(requestPath, (result) => {
      if (seq !== ozonState.orderAnalysisRequestSeq) return;
      ozonState.orderAnalysis = result;
      ozonState.orderAnalysisLoading = false;
      ozonState.orderAnalysisTotal = Number(result.summary?.activeProductCount || result.items?.length || 0);
      renderOzonOrderAnalysis();
    }, options);
    if (seq === ozonState.orderAnalysisRequestSeq) ozonState.orderAnalysisLoading = false;
  } catch (err) {
    if (seq !== ozonState.orderAnalysisRequestSeq) return;
    ozonState.orderAnalysisLoading = false;
    const message = err.message || '订单分析读取失败';
    if (hasExistingRows) {
      setOzonResult(`订单分析刷新失败，已保留上次结果：${message}`, 'error');
    } else {
      if (wrap) wrap.innerHTML = renderOzonErrorEmpty(message);
    }
    throw err;
  }
}

async function refreshOzonOrderAnalysis() {
  ozonState.orderAnalysisAccountId = getOzonScopedAccountId(document.getElementById('ozonOrderAnalysisAccountFilter')?.value ?? ozonState.orderAnalysisAccountId ?? '', { allowAll: true });
  ozonState.orderAnalysisPage = 1;
  updateOzonTabSyncStamps('ozonOrders');
  await loadOzonOrderAnalysis({ forceRefresh: true, keepStaleOnError: false });
  setOzonResult('订单分析已按服务器订单缓存重新计算', 'success');
}

async function loadOzonProductDiagnosis(options = {}) {
  const seq = (ozonState.productDiagnosisRequestSeq || 0) + 1;
  ozonState.productDiagnosisRequestSeq = seq;
  const hasExistingRows = Array.isArray(ozonState.productDiagnosis?.items) && ozonState.productDiagnosis.items.length > 0;
  ozonState.productDiagnosisLoading = true;
  if (!options.silent && (!hasExistingRows || options.forceRefresh)) setOzonHtml('ozonProductDiagnosisMeta', '正在读取商品诊断...');
  const wrap = document.getElementById('ozonProductDiagnosisList');
  if (wrap && !hasExistingRows && !options.silent) wrap.innerHTML = renderOzonLoadingEmpty('正在读取商品诊断...');
  const query = buildOzonAnalyticsQuery('product');
  try {
    let latestResult = null;
    await loadOzonCached(`/analytics/products${query}`, (result) => {
      if (seq !== ozonState.productDiagnosisRequestSeq) return;
      latestResult = result;
      ozonState.productDiagnosis = result;
      ozonState.productDiagnosisLoading = false;
      renderOzonProductDiagnosis();
    }, options);
    if (seq === ozonState.productDiagnosisRequestSeq) {
      ozonState.productDiagnosisLoading = false;
      renderOzonProductDiagnosis();
    }
  } catch (err) {
    if (seq !== ozonState.productDiagnosisRequestSeq) return;
    ozonState.productDiagnosisLoading = false;
    const message = err.message || '商品诊断读取失败';
    setOzonHtml('ozonProductDiagnosisMeta', renderOzonMetaItems([`状态：<strong>读取失败</strong>`, escapeHtml(message)]));
    if (wrap) wrap.innerHTML = renderOzonErrorEmpty(message);
    throw err;
  }
}

async function requestOzonSalesAiAdvice(item = {}, trafficLookup = null) {
  const lookup = trafficLookup || buildOzonTrafficLookup();
  const payload = buildOzonSalesAdvicePayload(item, getOzonTrafficForSalesItem(item, lookup));
  const result = await aiRequest('/ozon/sales-advice', {
    method: 'POST',
    body: JSON.stringify({
      context: {
        storeScope: getOzonAccountLabel(ozonState.orderAnalysisAccountId || item.accountId || '')
      },
      items: [payload]
    })
  });
  const adviceRows = Array.isArray(result.advices)
    ? result.advices
    : (Array.isArray(result.items) ? result.items : (Array.isArray(result.results) ? result.results : []));
  const advice = adviceRows.find(row => [
    row?.key,
    row?.productId,
    row?.product_id,
    row?.offerId,
    row?.offer_id,
    row?.sku
  ].map(value => normalizeOzonText(value)).filter(Boolean).some(value => [
    payload.key,
    payload.productId,
    payload.offerId,
    payload.sku
  ].map(normalizeOzonText).includes(value))) || adviceRows[0];
  if (!advice?.advice) throw new Error('AI不可用：模型未返回该链接的整改方案');
  return {
    status: 'success',
    advice: advice.advice,
    basis: (Array.isArray(advice.basis) ? advice.basis : []).filter(Boolean),
    priority: advice.priority || 'medium',
    model: result.model || '',
    provider: result.provider || '',
    error: ''
  };
}

async function runOzonSingleOrderAiAdvice(item = null) {
  if (!item) {
    setOzonResult('没有找到要诊断的订单分析行，请刷新列表后重试', 'error');
    return;
  }
  const targetKey = getOzonSalesAdviceTargetKey(item);
  if (!targetKey || targetKey === '|||') {
    setOzonResult('该订单分析行缺少店铺或商品标识，无法单独诊断', 'error');
    return;
  }
  if (ozonState.orderAnalysisAiAdviceRunningKeys?.has?.(targetKey)) return;
  const key = getOzonSalesAdviceKey(item);
  ozonState.orderAnalysisAiAdviceRunning = true;
  ozonState.orderAnalysisAiAdviceRunningKeys?.add?.(targetKey);
  ozonState.orderAnalysisAiAdvice[key] = {
    status: 'loading',
    advice: '',
    basis: [],
    priority: 'medium',
    error: ''
  };
  renderOzonOrderAnalysis();
  setOzonResult('正在诊断当前订单分析商品...');
  try {
    const state = await requestOzonSalesAiAdvice(item, buildOzonTrafficLookup());
    ozonState.orderAnalysisAiAdvice[key] = state;
    item.aiAdvice = state;
    const saved = await saveOzonSalesAdviceCache(item);
    const savedAt = saved?.advice?.diagnosedAt || saved?.diagnosedAt || '';
    setOzonResult(`订单分析单条 AI 诊断已保存${savedAt ? `：${formatOzonDateTime(savedAt)}` : ''}`, 'success');
  } catch (err) {
    const reason = normalizeOzonAiUnavailableMessage(err.message || 'AI不可用：请稍后再试');
    ozonState.orderAnalysisAiAdvice[key] = {
      status: isOzonTemporaryAiFailure(reason) ? 'pending' : 'unavailable',
      advice: '',
      basis: [],
      priority: 'medium',
      error: reason
    };
    setOzonResult(reason, 'error');
  } finally {
    ozonState.orderAnalysisAiAdviceRunningKeys?.delete?.(targetKey);
    ozonState.orderAnalysisAiAdviceRunning = Boolean(ozonState.orderAnalysisAiAdviceRunningKeys?.size);
    renderOzonOrderAnalysis();
  }
}

async function enrichOzonProductDiagnosisWithAi(options = {}) {
  const data = ozonState.productDiagnosis || {};
  const items = Array.isArray(data.items) ? data.items : [];
  if (!items.length) return;
  const filteredItems = options.items
    ? (Array.isArray(options.items) ? options.items : [])
    : getFilteredOzonDiagnosisItems();
  const targetSourceItems = options.force === true
    ? filteredItems
    : filteredItems.filter(isOzonDiagnosisMissingAi);
  const targetItems = targetSourceItems.slice(0, OZON_AI_DIAGNOSIS_MAX_ITEMS);
  if (!targetItems.length) return;
  const fingerprint = options.fingerprint || getOzonProductDiagnosisAiFingerprint(targetItems);
  if (ozonState.productDiagnosisAiPromise && ozonState.productDiagnosisAiFingerprint === fingerprint) {
    return ozonState.productDiagnosisAiPromise;
  }
  if (!options.force && ozonState.productDiagnosisAiCompletedFingerprints?.has(fingerprint)) return;
  ozonState.productDiagnosisAiRunning = true;
  ozonState.productDiagnosisAiFingerprint = fingerprint;
  const runPromise = (async () => {
    targetItems.forEach((item) => {
      item.diagnosis = item.diagnosis || {};
      item.diagnosis.aiDiagnosisStatus = 'loading';
      item.diagnosis.aiDiagnosisError = '';
      item.diagnosis.aiTitleStatus = 'loading';
      item.diagnosis.aiTitleError = '';
      item.diagnosis.aiProblems = [];
      item.diagnosis.aiSuggestions = [];
      item.diagnosis.aiBasis = [];
    });
    renderOzonProductDiagnosis();
    const payload = buildOzonTitleSuggestionPayload(targetItems);
    if (!payload.length) throw new Error('AI不可用：没有可诊断的商品数据');
    const storeScope = getOzonAccountLabel(ozonState.productDiagnosisAccountId || ozonState.productAccountId || '');
    const progressScope = options.progressScope || '当前页';
    let successCount = 0;
    let unavailableCount = 0;
    let temporaryFailureCount = 0;
    for (let start = 0; start < payload.length; start += OZON_AI_DIAGNOSIS_BATCH_SIZE) {
      const batchPayload = payload.slice(start, start + OZON_AI_DIAGNOSIS_BATCH_SIZE);
      const batchItems = targetItems.slice(start, start + OZON_AI_DIAGNOSIS_BATCH_SIZE);
      const batchNo = Math.floor(start / OZON_AI_DIAGNOSIS_BATCH_SIZE) + 1;
      const batchTotal = Math.ceil(payload.length / OZON_AI_DIAGNOSIS_BATCH_SIZE);
      const progressUnit = OZON_AI_DIAGNOSIS_BATCH_SIZE === 1 ? '个商品' : '批';
      setOzonSyncProgress('product', Math.min(96, 58 + Math.round((start / payload.length) * 36)), `${progressScope}AI诊断中：第 ${batchNo}/${batchTotal} ${progressUnit}...`);
      try {
        const result = await aiRequest('/ozon/title-suggestions', {
          method: 'POST',
          body: JSON.stringify({
            context: { storeScope },
            products: batchPayload
          })
        });
        const suggestionRows = Array.isArray(result.suggestions)
          ? result.suggestions
          : (Array.isArray(result.items) ? result.items : (Array.isArray(result.results) ? result.results : []));
        const suggestions = new Map();
        suggestionRows.forEach((row) => {
          [
            row?.key,
            row?.productId,
            row?.product_id,
            row?.offerId,
            row?.offer_id,
            row?.sku,
            row?.title,
            row?.name
          ].map(value => normalizeOzonText(value)).filter(Boolean).forEach(key => suggestions.set(key, row));
        });
        batchItems.forEach((item, batchIndex) => {
          const diag = item.diagnosis || {};
          const suggestion = getOzonDiagnosisKeyAliases(item, start + batchIndex)
            .map(key => suggestions.get(key))
            .find(Boolean) || suggestionRows[batchIndex];
          const aiProblems = (Array.isArray(suggestion?.problems) ? suggestion.problems : []).filter(Boolean);
          const aiSuggestions = (Array.isArray(suggestion?.suggestions) ? suggestion.suggestions : []).filter(Boolean);
          const aiBasis = (Array.isArray(suggestion?.basis) ? suggestion.basis : []).filter(Boolean);
          if (suggestion?.optimizedTitle && aiProblems.length && aiSuggestions.length) {
            diag.aiDiagnosisStatus = 'success';
            diag.aiProblems = aiProblems;
            diag.aiSuggestions = aiSuggestions;
            diag.aiBasis = aiBasis;
            diag.aiDiagnosisError = '';
            diag.aiTitleStatus = 'success';
            diag.aiOptimizedTitle = suggestion.optimizedTitle;
            diag.aiTitleBasis = suggestion.titleBasis || suggestion.keywordBasis || aiBasis.join('；') || '';
            diag.aiKeywordBasis = suggestion.keywordBasis || '';
            diag.aiTitleError = '';
            diag.aiModel = result.model || '';
            diag.aiProvider = result.provider || '';
            successCount += 1;
          } else {
            setOzonDiagnosisAiUnavailable(diag, '模型未返回该商品的完整 AI 诊断');
            unavailableCount += 1;
          }
        });
      } catch (batchErr) {
        const message = normalizeOzonAiUnavailableMessage(batchErr.message || 'AI不可用：请稍后再试');
        const temporary = isOzonTemporaryAiFailure(message);
        batchItems.forEach((item) => {
          item.diagnosis = item.diagnosis || {};
          if (temporary) {
            item.diagnosis.aiDiagnosisStatus = 'pending';
            item.diagnosis.aiDiagnosisError = message;
            item.diagnosis.aiTitleStatus = 'pending';
            item.diagnosis.aiTitleError = message;
            item.diagnosis.aiProblems = [];
            item.diagnosis.aiSuggestions = [];
            item.diagnosis.aiBasis = [];
            temporaryFailureCount += 1;
          } else {
            setOzonDiagnosisAiUnavailable(item.diagnosis, message);
          }
          unavailableCount += 1;
        });
      }
      renderOzonProductDiagnosis();
      await wait(900);
    }
    const tone = successCount ? 'success' : 'error';
    const retryText = temporaryFailureCount ? `，临时失败 ${temporaryFailureCount} 个稍后重试` : '';
    setOzonSyncProgress('product', 100, `${progressScope}AI诊断完成：成功 ${successCount} 个，未返回 ${unavailableCount} 个${retryText}`, tone);
    if (!temporaryFailureCount) ozonState.productDiagnosisAiCompletedFingerprints?.add(fingerprint);
  })();
  ozonState.productDiagnosisAiPromise = runPromise;
  try {
    await runPromise;
  } catch (err) {
    const message = normalizeOzonAiUnavailableMessage(err.message || 'AI不可用：请稍后再试');
    targetItems.forEach((item) => {
      item.diagnosis = item.diagnosis || {};
      setOzonDiagnosisAiUnavailable(item.diagnosis, message);
    });
  } finally {
    if (ozonState.productDiagnosisAiFingerprint === fingerprint) {
      ozonState.productDiagnosisAiRunning = false;
      ozonState.productDiagnosisAiPromise = null;
    }
    renderOzonProductDiagnosis();
  }
}

const enrichOzonDiagnosisTitlesWithAi = enrichOzonProductDiagnosisWithAi;

async function runOzonSingleProductDiagnosis(itemIndex = 0) {
  const items = getFilteredOzonDiagnosisItems();
  const item = items[Number(itemIndex || 0)];
  if (!item) {
    setOzonResult('没有找到要诊断的商品行，请刷新列表后重试', 'error');
    return;
  }
  if (ozonState.productDiagnosisAiRunning) {
    setOzonResult('已有商品正在 AI 诊断，请等当前诊断完成后再操作', 'warn');
    return;
  }
  const targetKey = getOzonDiagnosisTargetKey(item);
  if (!targetKey || targetKey === '|||') {
    setOzonResult('该商品缺少店铺或商品标识，无法单独诊断', 'error');
    return;
  }
  ozonState.productDiagnosisAiRunningKeys?.add?.(targetKey);
  renderOzonProductDiagnosis();
  setOzonSyncProgress('product', 35, '正在诊断当前商品...');
  try {
    await enrichOzonProductDiagnosisWithAi({
      items: [item],
      force: true,
      fingerprint: `single::${targetKey}::${Date.now()}`,
      progressScope: '单条商品'
    });
    const state = getOzonAiDiagnosisState(item.diagnosis || {});
    if (state.status !== 'success') {
      throw new Error((item.diagnosis?.aiDiagnosisError || item.diagnosis?.aiTitleError || 'AI 未返回完整诊断结果').replace(/^AI不可用：/, ''));
    }
    const saved = await saveOzonProductAiDiagnosisCache(item);
    const savedAt = saved?.diagnosis?.aiDiagnosedAt || saved?.diagnosedAt || '';
    setOzonSyncProgress('product', 100, `单条商品 AI 诊断已保存${savedAt ? `：${formatOzonDateTime(savedAt)}` : ''}`, 'success');
    setOzonResult('单条商品 AI 诊断已保存到服务器', 'success');
  } catch (err) {
    setOzonSyncProgress('product', 100, err.message || '单条商品 AI 诊断失败', 'error');
    setOzonResult(err.message || '单条商品 AI 诊断失败', 'error');
  } finally {
    ozonState.productDiagnosisAiRunningKeys?.delete?.(targetKey);
    renderOzonProductDiagnosis();
  }
}

async function loadOzonOverview(options = {}) {
  const seq = (ozonState.overviewRequestSeq || 0) + 1;
  ozonState.overviewRequestSeq = seq;
  try {
    await loadOzonCached('/overview', (result) => {
      if (seq !== ozonState.overviewRequestSeq) return;
      ozonState.overviewError = '';
      ozonState.overview = result;
      renderOzonOverview();
    }, { requestTimeoutMs: OZON_HOME_READ_TIMEOUT_MS, ...options });
  } catch (err) {
    if (seq === ozonState.overviewRequestSeq) {
      ozonState.overviewError = err.message || '首页数据读取失败';
      renderOzonOverview();
    }
    throw err;
  }
}

function buildOzonOrderQuery() {
  const params = new URLSearchParams();
  const accountEl = document.getElementById('ozonOrderAccountFilter');
  const accountId = getOzonScopedAccountId(accountEl ? accountEl.value : (ozonState.orderAccountId || ''), { allowAll: true });
  const dateFromEl = document.getElementById('ozonOrderDateFromInput');
  const dateToEl = document.getElementById('ozonOrderDateToInput');
  const keywordEl = document.getElementById('ozonOrderKeywordInput');
  const status = normalizeOzonOrderBucket(ozonState.orderStatus || 'all');
  const dateFrom = normalizeOzonDateKey(dateFromEl ? dateFromEl.value : (ozonState.orderDateFrom || ''));
  const dateTo = normalizeOzonDateKey(dateToEl ? dateToEl.value : (ozonState.orderDateTo || ''));
  const keyword = keywordEl ? keywordEl.value.trim() : (ozonState.orderKeyword || '');
  ozonState.orderAccountId = accountId;
  ozonState.orderStatus = status;
  ozonState.orderDateFrom = dateFrom;
  ozonState.orderDateTo = dateTo;
  ozonState.orderKeyword = keyword;
  if (accountId) params.set('ozonAccountId', accountId);
  if (status && status !== 'all') params.set('bucket', status);
  if (status === 'delivered') params.set('includeFinance', '1');
  if (dateFrom) params.set('dateFrom', dateFrom);
  if (dateTo) params.set('dateTo', dateTo);
  if (keyword) params.set('keyword', keyword);
  params.set('limit', String(ozonState.orderLimit || 50));
  params.set('offset', String(Math.max(0, ((ozonState.orderPage || 1) - 1) * (ozonState.orderLimit || 50))));
  const text = params.toString();
  return text ? `?${text}` : '';
}

function syncOzonOrderDateState() {
  const fromInput = document.getElementById('ozonOrderDateFromInput');
  const toInput = document.getElementById('ozonOrderDateToInput');
  if (fromInput) fromInput.value = normalizeOzonDateInputValue(fromInput.value || '');
  if (toInput) toInput.value = normalizeOzonDateInputValue(toInput.value || '');
  const dateFrom = normalizeOzonDateKey(fromInput?.value || '');
  const dateTo = normalizeOzonDateKey(toInput?.value || '');
  ozonState.orderDateFrom = dateFrom;
  ozonState.orderDateTo = dateTo;
  return { dateFrom, dateTo };
}

function setOzonOrderDateRange(dateFrom = '', dateTo = '') {
  const fromInput = document.getElementById('ozonOrderDateFromInput');
  const toInput = document.getElementById('ozonOrderDateToInput');
  const normalizedFrom = normalizeOzonDateKey(dateFrom);
  const normalizedTo = normalizeOzonDateKey(dateTo);
  if (fromInput) fromInput.value = normalizedFrom;
  if (toInput) toInput.value = normalizedTo;
  ozonState.orderDateFrom = normalizedFrom;
  ozonState.orderDateTo = normalizedTo;
  ozonState.orderPage = 1;
}

function applyOzonOrderDatePreset(preset = '') {
  const today = getOzonBusinessDateKey();
  if (preset === 'today') {
    setOzonOrderDateRange(today, today);
  } else if (preset === 'last7') {
    setOzonOrderDateRange(addOzonDateKeyDays(today, -6), today);
  } else if (preset === 'month') {
    setOzonOrderDateRange(`${today.slice(0, 7)}-01`, today);
  } else if (preset === 'clear') {
    setOzonOrderDateRange('', '');
  }
}

function bindOzonOrderDateInput(id) {
  const input = document.getElementById(id);
  if (!input || input.dataset.ozonDateBound === '1') return;
  input.dataset.ozonDateBound = '1';
  const openPicker = () => {
    input.focus();
    try {
      if (typeof input.showPicker === 'function') input.showPicker();
    } catch {
      // Some browsers only allow showPicker during direct user activation.
    }
  };
  input.addEventListener('pointerdown', openPicker);
  input.addEventListener('click', openPicker);
  input.addEventListener('blur', () => {
    input.value = normalizeOzonDateInputValue(input.value || '');
    if (input.value && !isOzonValidDateInput(input.value)) {
      input.setCustomValidity('请输入 YYYY-MM-DD 格式的日期');
    } else {
      input.setCustomValidity('');
    }
    syncOzonOrderDateState();
  });
  input.addEventListener('change', () => {
    input.value = normalizeOzonDateInputValue(input.value || '');
    input.setCustomValidity(input.value && !isOzonValidDateInput(input.value) ? '请输入 YYYY-MM-DD 格式的日期' : '');
    syncOzonOrderDateState();
    ozonState.orderPage = 1;
  });
  input.addEventListener('keydown', (event) => {
    if (event.key === 'Enter') {
      event.preventDefault();
      syncOzonOrderDateState();
      document.getElementById('ozonSearchOrdersBtn')?.click();
    }
  });
}

function buildOzonProductQuery() {
  const params = new URLSearchParams();
  const accountEl = document.getElementById('ozonProductAccountFilter');
  const visibilityEl = document.getElementById('ozonProductListVisibilityFilter');
  const keywordEl = document.getElementById('ozonProductKeywordInput');
  const nameEl = document.getElementById('ozonProductNameInput');
  const skuEl = document.getElementById('ozonProductSkuInput');
  const accountId = getOzonActiveAccountId(accountEl ? accountEl.value : (ozonState.productAccountId || ''));
  const visibility = normalizeOzonVisibilityForUi(visibilityEl ? visibilityEl.value : (ozonState.productListVisibility || 'ALL')) || 'ALL';
  const name = nameEl ? nameEl.value.trim() : (ozonState.productName || '');
  const sku = skuEl ? skuEl.value.trim() : (ozonState.productSku || '');
  const keyword = keywordEl ? keywordEl.value.trim() : (ozonState.productKeyword || '');
  ozonState.productAccountId = accountId;
  ozonState.productListVisibility = visibility;
  ozonState.productName = name;
  ozonState.productSku = sku;
  ozonState.productKeyword = keyword;
  if (accountId) params.set('ozonAccountId', accountId);
  if (visibility) params.set('visibility', visibility);
  if (name) params.set('name', name);
  if (sku) params.set('sku', sku);
  if (keyword) params.set('keyword', keyword);
  params.set('statusScope', 'all');
  params.set('limit', String(ozonState.productLimit || 50));
  params.set('offset', String(Math.max(0, ((ozonState.productPage || 1) - 1) * (ozonState.productLimit || 50))));
  const text = params.toString();
  return text ? `?${text}` : '';
}

function applyOzonProductListFiltersFromControls() {
  const accountEl = document.getElementById('ozonProductAccountFilter');
  const visibilityEl = document.getElementById('ozonProductListVisibilityFilter');
  const keywordEl = document.getElementById('ozonProductKeywordInput');
  const nameEl = document.getElementById('ozonProductNameInput');
  const skuEl = document.getElementById('ozonProductSkuInput');
  ozonState.productAccountId = getOzonActiveAccountId(accountEl?.value || ozonState.productAccountId || '');
  ozonState.productAccountTouched = true;
  ozonState.productListVisibility = normalizeOzonVisibilityForUi(visibilityEl?.value || ozonState.productListVisibility || 'ALL') || 'ALL';
  ozonState.productKeyword = keywordEl?.value?.trim() || '';
  ozonState.productName = nameEl?.value?.trim() || '';
  ozonState.productSku = skuEl?.value?.trim() || '';
  ozonState.productPage = 1;
  clearOzonProductSelection();
  updateOzonTabSyncStamps('ozonProductOnline');
}

async function loadOzonOrders(options = {}) {
  const path = `/orders${buildOzonOrderQuery()}`;
  const orderLoadOptions = ozonState.orderStatus === 'delivered'
    ? { ...options, requestTimeoutMs: Math.max(Number(options.requestTimeoutMs || options.timeoutMs || 0), 45000) }
    : options;
  await loadOzonCached(path, (result) => {
    ozonState.orders = sortOzonOrdersNewestFirst(result.orders);
    ozonState.orderTotal = Number(result.total || ozonState.orders.length || 0);
    ozonState.orderStatusCounts = result.statusCounts || {};
    ozonState.orderBucketCounts = result.bucketCounts || {};
    ozonState.orderSummary = result.orderSummary || {};
    renderOzonOrders();
  }, orderLoadOptions);
}

function buildOzonCollectionQuery() {
  const params = new URLSearchParams();
  const statusEl = document.getElementById('ozonCollectionStatusFilter');
  const keywordEl = document.getElementById('ozonCollectionKeywordInput');
  const status = statusEl ? statusEl.value : (ozonState.collectionStatus || '');
  const keyword = keywordEl ? keywordEl.value.trim() : (ozonState.collectionKeyword || '');
  ozonState.collectionStatus = status;
  ozonState.collectionKeyword = keyword;
  if (status) params.set('status', status);
  if (keyword) params.set('keyword', keyword);
  const text = params.toString();
  return text ? `?${text}` : '';
}

async function loadOzonCollectionLinks(options = {}) {
  rememberOzonCheckedState();
  const list = document.getElementById('ozonCollectionList');
  const diagnosisList = document.getElementById('ozonLinkDiagnosisList');
  const hasPrevious = Array.isArray(ozonState.collectionLinks) && ozonState.collectionLinks.length > 0;
  if (!options.silent && (!hasPrevious || options.forceRefresh)) {
    if (list && !hasPrevious) list.innerHTML = '<div class="ozon-empty">正在读取采集链接...</div>';
    if (diagnosisList && !hasPrevious) diagnosisList.innerHTML = '<div class="ozon-empty">正在生成链接诊断...</div>';
  }
  try {
    const path = `/collection-links${buildOzonCollectionQuery()}`;
    await loadOzonCached(path, (result) => {
      ozonState.collectionLinks = Array.isArray(result.links) ? result.links : [];
      const visibleIds = new Set(ozonState.collectionLinks.map(link => String(link.id || '')));
      ozonState.selectedCollectionIds = new Set(Array.from(ozonState.selectedCollectionIds).filter(id => visibleIds.has(id)));
      renderOzonCollectionLinks();
      renderOzonLinkDiagnosis();
    }, options);
  } catch (err) {
    const message = err.message || '选品链接读取失败';
    if (hasPrevious) {
      renderOzonCollectionLinks();
      renderOzonLinkDiagnosis();
      const summary = document.getElementById('ozonLinkDiagnosisSummary');
      if (summary) summary.textContent = `显示上次采集链接，最新刷新失败：${message}`;
      return;
    }
    if (list) list.innerHTML = renderOzonErrorEmpty(message);
    if (diagnosisList) diagnosisList.innerHTML = renderOzonErrorEmpty(message);
    const summary = document.getElementById('ozonLinkDiagnosisSummary');
    if (summary) summary.textContent = message;
    throw err;
  }
}

async function loadOzonDashboard(options = {}) {
  if (!isOzonWorkspace()) return;
  const token = await getServerAuthTokenForRead({ timeoutMs: options.authTimeoutMs ?? 3200 });
  if (!token) return;
  const tabLoadOptions = { allowStale: true, silent: true, keepStaleOnError: true, authTimeoutMs: 0 };
  try {
    const activeTabId = document.querySelector('.section.active')?.id || 'ozonHome';
    if (activeTabId === 'ozonHome') {
      ozonState.overviewError = '';
      ozonState.accountsError = '';
    }
    renderOzonTabShell(activeTabId);
    await Promise.allSettled([loadOzonAccounts(tabLoadOptions), loadOzonOverview(tabLoadOptions)]);
  } catch (err) {
    console.warn('[小青蛙电商助手][OZON] 加载失败:', err);
    setOzonResult(err.message || '\u52a0\u8f7d\u5931\u8d25', 'error');
  }
}

async function loadOzonTab(tabId, options = {}) {
  if (!isOzonWorkspace()) return;
  const tabLoadOptions = { allowStale: true, silent: options.silent === true, keepStaleOnError: true };
  ensureOzonTabSyncStamp(tabId);
  renderOzonTabShell(tabId);
  if (tabId === 'ozonHome') {
    await Promise.allSettled([loadOzonOverview(tabLoadOptions), loadOzonAccounts(tabLoadOptions)]);
    scheduleOzonHomeBackgroundLoad();
  } else if (tabId === 'ozonCollection') {
    await loadOzonCollectionLinks(tabLoadOptions);
  } else if (tabId === 'ozonProducts') {
    ozonState.productDiagnosisPage = 1;
    await Promise.allSettled([
      loadOzonAccounts(tabLoadOptions),
      loadOzonProducts({ ...tabLoadOptions, silent: true }),
      loadOzonProductDiagnosis({ ...tabLoadOptions, waitForAi: false })
    ]);
  } else if (tabId === 'ozonProductOnline') {
    await Promise.allSettled([loadOzonAccounts(tabLoadOptions), loadOzonProducts(tabLoadOptions)]);
  } else if (tabId === 'ozonUpload') {
    await Promise.allSettled([loadOzonAccounts(tabLoadOptions), loadOzonUploadTemplates(tabLoadOptions)]);
  } else if (tabId === 'ozonUploadTest') {
    await loadOzonAccounts(tabLoadOptions);
    await window.loadOzonUploadTest?.(tabLoadOptions);
  } else if (tabId === 'ozonOrders') {
    await loadOzonOrderAnalysis(tabLoadOptions);
  } else if (tabId === 'ozonOrderList') {
    await Promise.allSettled([loadOzonAccounts(tabLoadOptions), loadOzonOrders(tabLoadOptions)]);
  } else if (tabId === 'ozonAds') {
    await Promise.allSettled([loadOzonAccounts(tabLoadOptions), loadOzonPerformanceConfig(tabLoadOptions), loadOzonAds(tabLoadOptions)]);
  } else if (tabId === 'ozonTraffic') {
    await Promise.allSettled([loadOzonAccounts(tabLoadOptions), loadOzonTraffic({ ...tabLoadOptions, forceRefresh: false })]);
  } else if (tabId === 'ozonMarketing') {
    restoreOzonAccountsFromLocalCache();
    await Promise.allSettled([
      loadOzonMarketing({ ...tabLoadOptions, includeProducts: false }),
      loadOzonAccounts(tabLoadOptions)
    ]);
  } else if (tabId === 'ozonFinance') {
    await Promise.allSettled([loadOzonAccounts(tabLoadOptions), loadOzonFinance(tabLoadOptions)]);
  } else if (tabId === 'ozonMessages') {
    ozonState.notificationDrawerOpen = false;
    await Promise.allSettled([loadOzonAccounts(tabLoadOptions), loadOzonMessages(tabLoadOptions)]);
    startOzonMessageRealtimeSync();
  }
  updateOzonTabSyncStamps(tabId);
}

async function saveOzonAccountFromForm(checkOnly = false) {
  const title = document.getElementById('ozonShopTitleInput')?.value?.trim() || 'OZON \u5e97\u94fa';
  const clientId = document.getElementById('ozonClientIdInput')?.value?.trim();
  const apiKey = document.getElementById('ozonApiKeyInput')?.value?.trim();
  const shopCardKey = document.getElementById('ozonShopCardKeyInput')?.value?.trim();
  if (!clientId || !apiKey) {
    setOzonAuthModalResult('Client-Id 和 Api-Key 不能为空', 'error');
    return;
  }
  if (!checkOnly && !shopCardKey) {
    setOzonAuthModalResult('店铺授权密钥不能为空，请填写小青蛙店铺授权密钥后再绑定', 'error');
    return;
  }
  setOzonAuthModalResult(checkOnly ? '正在校验 OZON API...' : '正在校验 API 和店铺授权密钥...');
  try {
    const path = checkOnly ? '/accounts/check' : '/accounts';
    const result = await ozonRequest(path, {
      method: 'POST',
      body: JSON.stringify({ title, clientId, apiKey, shopCardKey })
    });
    if (!checkOnly && result.account?.id) {
      document.getElementById('ozonApiKeyInput').value = '';
      document.getElementById('ozonShopCardKeyInput').value = '';
      await loadOzonAccounts();
    }
    if (checkOnly) {
      setOzonAuthModalResult('OZON API 校验通过；保存绑定时还必须填写店铺授权密钥', 'success');
    } else {
      setOzonAuthModalResult('店铺绑定与授权成功', 'success');
      setOzonResult('店铺绑定与授权成功', 'success');
      window.setTimeout(() => toggleOzonAccountAuthPanel(false), 500);
      await Promise.allSettled([loadOzonOverview(), loadOzonProducts(), loadOzonProductDiagnosis({ enrichAi: false }), loadOzonOrderAnalysis()]);
    }
  } catch (err) {
    setOzonAuthModalResult(err.message || '操作失败', 'error');
  }
}

async function syncOzonProductsFromSelectedAccount() {
  const accountIds = getOzonFullSyncAccountIds('product');
  if (!accountIds.length) {
    setOzonResult(getOzonNoAuthorizedAccountMessage(), 'error');
    return;
  }
  const isAll = accountIds.length > 1;
  setOzonSyncButtonsDisabled(true, 'product');
  setOzonSyncProgress('product', 3, `正在同步 OZON ${isAll ? `全部 ${accountIds.length} 个店铺` : '所选店铺'}商品...`);
  try {
    let totalFetched = 0;
    let totalSaved = 0;
    for (let index = 0; index < accountIds.length; index += 1) {
      const accountId = accountIds[index];
      const accountTitle = getOzonAccountTitle(accountId) || `店铺 ${index + 1}`;
      const basePct = Math.round((index / accountIds.length) * 82) + 5;
      setOzonSyncProgress('product', basePct, `正在同步商品：${accountTitle}（${index + 1}/${accountIds.length}）...`);
      const requestStartedAt = Date.now();
      let result;
      try {
        result = await ozonRequest('/products/sync', {
          method: 'POST',
          body: JSON.stringify({ ozonAccountId: accountId, filter: { visibility: 'ALL' }, limit: 1000, maxPages: 200 })
        });
      } catch (err) {
        if (!isOzonSyncTimeoutError(err)) throw err;
        setOzonSyncProgress('product', 88, `商品同步已在服务器继续执行：${accountTitle}，正在等待结果...`);
        result = await waitForOzonProductSyncTask(accountId, requestStartedAt);
      }
      totalFetched += Number(result.totalFetched || 0);
      totalSaved += Number(result.totalSaved || 0);
      setOzonSyncProgress('product', Math.round(((index + 1) / accountIds.length) * 82) + 8, `商品同步中：${accountTitle} 已保存 ${result.totalSaved || 0} 个`);
    }
    setOzonSyncProgress('product', 92, '商品已同步，正在刷新商品列表...');
    ozonState.productPage = 1;
    clearOzonProductSelection();
    await Promise.allSettled([
      loadOzonAccounts({ forceRefresh: true }),
      loadOzonProducts({ forceRefresh: true }),
      loadOzonProductDiagnosis({ enrichAi: false, forceRefresh: true, resetAiQueue: false })
    ]);
    setOzonSyncProgress('product', 100, `商品同步完成：拉取 ${totalFetched} 个，保存 ${totalSaved} 个`, 'success');
  } catch (err) {
    setOzonSyncProgress('product', 100, err.message || '\u540c\u6b65\u5931\u8d25', 'error');
  } finally {
    setOzonSyncButtonsDisabled(false, 'product');
    updateOzonProductSelectionUi();
  }
}

async function syncSelectedOzonProducts() {
  const selectedRows = getSelectedOzonProductRows();
  if (!selectedRows.length) {
    setOzonResult('请先勾选要同步的 OZON 商品', 'error');
    return;
  }
  const fallbackAccountId = getSelectedOzonScopeId('product');
  const groups = selectedRows.reduce((acc, row) => {
    const accountId = normalizeOzonText(row.ozonAccountId || fallbackAccountId);
    if (!accountId) return acc;
    if (!acc.has(accountId)) acc.set(accountId, []);
    acc.get(accountId).push(row);
    return acc;
  }, new Map());
  if (!groups.size) {
    setOzonResult('请先选择 OZON 店铺', 'error');
    return;
  }
  const limitedGroups = new Map(Array.from(groups.entries()).map(([accountId, rows]) => [accountId, rows.slice(0, 200)]));
  const totalTarget = Array.from(limitedGroups.values()).reduce((sum, rows) => sum + rows.length, 0);
  setOzonSyncButtonsDisabled(true, 'product');
  setOzonSyncProgress('product', 5, `正在同步所选 ${totalTarget} 个 OZON 商品...`);
  try {
    let totalSaved = 0;
    const entries = Array.from(limitedGroups.entries());
    for (let index = 0; index < entries.length; index += 1) {
      const [accountId, rows] = entries[index];
      setOzonSyncProgress('product', Math.round((index / entries.length) * 80) + 8, `正在同步所选商品：${getOzonAccountTitle(accountId) || `店铺 ${index + 1}`}（${index + 1}/${entries.length}）`);
      const result = await ozonRequest('/products/sync', {
        method: 'POST',
        body: JSON.stringify({
          ozonAccountId: accountId,
          includeList: false,
          productIds: rows.map(row => Number(row.productId || 0)).filter(n => Number.isFinite(n) && n > 0),
          offerIds: rows.map(row => row.offerId).filter(Boolean),
          skus: rows.map(row => Number(row.sku || 0)).filter(n => Number.isFinite(n) && n > 0)
        })
      });
      totalSaved += Number(result.totalSaved || 0);
    }
    setOzonSyncProgress('product', 92, '商品已同步，正在刷新商品列表...');
    clearOzonProductSelection();
    await Promise.allSettled([
      loadOzonProducts({ forceRefresh: true }),
      loadOzonProductDiagnosis({ enrichAi: false, forceRefresh: true, resetAiQueue: false })
    ]);
    setOzonSyncProgress('product', 100, `所选商品同步完成：保存 ${totalSaved} 个`, 'success');
  } catch (err) {
    setOzonSyncProgress('product', 100, err.message || '同步当前筛选商品失败', 'error');
  } finally {
    setOzonSyncButtonsDisabled(false, 'product');
    updateOzonProductSelectionUi();
  }
}

async function syncOzonOrdersFromSelectedAccount() {
  const accountIds = getOzonFullSyncAccountIds('order');
  if (!accountIds.length) {
    setOzonOrderSyncStatus(`订单同步状态：${getOzonNoAuthorizedAccountMessage()}`, 'error');
    return;
  }
  const isAll = accountIds.length > 1;
  setOzonSyncButtonsDisabled(true, 'order');
  setOzonSyncProgress('order', 3, `正在手动拉取 OZON ${isAll ? `全部 ${accountIds.length} 个店铺` : '所选店铺'}订单，通知未覆盖时用于补漏...`);
  try {
    const activeSectionId = document.querySelector('.section.active')?.id || '';
    const onOrderListTab = activeSectionId === 'ozonOrderList';
    const dateFromInput = onOrderListTab ? (document.getElementById('ozonOrderDateFromInput')?.value || '') : '';
    const dateToInput = onOrderListTab ? (document.getElementById('ozonOrderDateToInput')?.value || '') : '';
    const status = onOrderListTab ? (document.getElementById('ozonOrderStatusFilter')?.value || '') : '';
    const dateTo = normalizeOzonDateKey(dateToInput) || getOzonBusinessDateKey();
    const dateFrom = normalizeOzonDateKey(dateFromInput) || addOzonDateKeyDays(dateTo, -6);
    const chunks = buildOzonOrderSyncDateChunks(dateFrom, dateTo, 7);
    const totalJobs = Math.max(1, accountIds.length * chunks.length);
    let totalFetched = 0;
    let totalSaved = 0;
    let totalFinanceUpdated = 0;
    let completedJobs = 0;
    for (let index = 0; index < accountIds.length; index += 1) {
      const accountId = accountIds[index];
      const accountTitle = getOzonAccountTitle(accountId) || `店铺 ${index + 1}`;
      const selectedChunks = chunks;
      for (let chunkIndex = 0; chunkIndex < selectedChunks.length; chunkIndex += 1) {
        const chunk = selectedChunks[chunkIndex];
        const pct = 5 + Math.round((completedJobs / totalJobs) * 84);
        setOzonSyncProgress('order', pct, `正在手动拉取：${accountTitle} ${chunk.dateFrom} 至 ${chunk.dateTo}（${completedJobs + 1}/${totalJobs}）...`);
        const result = await ozonRequest('/orders/sync', {
          method: 'POST',
          body: JSON.stringify({
            ozonAccountId: accountId,
            mode: 'manual_backfill',
            limit: 200,
            maxPages: 6,
            dateFrom: chunk.dateFrom,
            dateTo: chunk.dateTo,
            rangeDays: 7,
            syncTypes: ['fbs', 'fbo'],
            filter: status ? { status } : {}
          })
        });
        totalFetched += Number(result.totalFetched || 0);
        totalSaved += Number(result.totalSaved || 0);
        totalFinanceUpdated += Number(result.finance?.totalUpdated || 0);
        completedJobs += 1;
        setOzonSyncProgress('order', 5 + Math.round((completedJobs / totalJobs) * 84), `服务器订单缓存更新中：${accountTitle} ${chunk.dateFrom} 至 ${chunk.dateTo}，本段保存 ${result.totalSaved || 0} 个，财务更新 ${result.finance?.totalUpdated || 0} 单，累计 ${totalSaved} 个`);
      }
    }
    setOzonSyncProgress('order', 92, '订单已保存到服务器，正在刷新各 Tab 缓存数据...');
    ozonState.orderPage = 1;
    await Promise.allSettled([loadOzonOverview({ forceRefresh: true }), loadOzonOrders({ forceRefresh: true }), loadOzonOrderAnalysis({ forceRefresh: true }), loadOzonProductDiagnosis({ enrichAi: false, forceRefresh: true })]);
    setOzonSyncProgress('order', 100, `手动拉取完成：拉取 ${totalFetched} 个，保存 ${totalSaved} 个，财务更新 ${totalFinanceUpdated} 单；页面已刷新服务器订单缓存`, 'success');
    hideOzonSyncProgress('order', 1800);
  } catch (err) {
    setOzonSyncProgress('order', 100, err.message || '手动拉取订单失败', 'error');
  } finally {
    setOzonSyncButtonsDisabled(false, 'order');
  }
}

async function syncOzonFullHistoryOrdersFromSelectedAccount() {
  const accountIds = getOzonFullSyncAccountIds('order');
  if (!accountIds.length) {
    setOzonOrderSyncStatus(`订单同步状态：${getOzonNoAuthorizedAccountMessage()}`, 'error');
    return;
  }
  const { dateFrom, dateTo } = getOzonFullHistoryOrderDateRange();
  const chunks = buildOzonOrderSyncDateChunks(dateFrom, dateTo, 7);
  const isAll = accountIds.length > 1;
  setOzonSyncButtonsDisabled(true, 'order');
  setOzonSyncProgress('order', 2, `正在全量拉取 OZON ${isAll ? `全部 ${accountIds.length} 个店铺` : '所选店铺'}近 3 年历史订单...`);
  try {
    const totalJobs = Math.max(1, accountIds.length * chunks.length);
    let totalFetched = 0;
    let totalSaved = 0;
    let totalFinanceUpdated = 0;
    let completedJobs = 0;
    for (let index = 0; index < accountIds.length; index += 1) {
      const accountId = accountIds[index];
      const accountTitle = getOzonAccountTitle(accountId) || `店铺 ${index + 1}`;
      for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex += 1) {
        const chunk = chunks[chunkIndex];
        const pct = 4 + Math.round((completedJobs / totalJobs) * 86);
        setOzonSyncProgress('order', pct, `正在全量拉取：${accountTitle} ${chunk.dateFrom} 至 ${chunk.dateTo}（${completedJobs + 1}/${totalJobs}）...`);
        const result = await ozonRequest('/orders/sync', {
          method: 'POST',
          body: JSON.stringify({
            ozonAccountId: accountId,
            mode: 'full_history',
            limit: 500,
            maxPages: 40,
            dateFrom: chunk.dateFrom,
            dateTo: chunk.dateTo,
            rangeDays: 7,
            syncTypes: ['fbs', 'fbo']
          })
        });
        totalFetched += Number(result.totalFetched || 0);
        totalSaved += Number(result.totalSaved || 0);
        totalFinanceUpdated += Number(result.finance?.totalUpdated || 0);
        completedJobs += 1;
        setOzonSyncProgress('order', 4 + Math.round((completedJobs / totalJobs) * 86), `历史订单已写入服务器：${accountTitle} ${chunk.dateFrom} 至 ${chunk.dateTo}，本段保存 ${result.totalSaved || 0} 个，财务更新 ${result.finance?.totalUpdated || 0} 单，累计 ${totalSaved} 个`);
      }
    }
    setOzonSyncProgress('order', 94, '历史订单已保存到服务器，正在刷新订单列表和分析数据...');
    ozonState.orderPage = 1;
    await Promise.allSettled([loadOzonOverview({ forceRefresh: true }), loadOzonOrders({ forceRefresh: true }), loadOzonOrderAnalysis({ forceRefresh: true }), loadOzonProductDiagnosis({ enrichAi: false, forceRefresh: true })]);
    setOzonSyncProgress('order', 100, `全量历史拉取完成：拉取 ${totalFetched} 个，保存 ${totalSaved} 个，财务更新 ${totalFinanceUpdated} 单；服务器订单缓存已更新`, 'success');
    hideOzonSyncProgress('order', 2500);
  } catch (err) {
    setOzonSyncProgress('order', 100, err.message || '全量拉取历史订单失败', 'error');
  } finally {
    setOzonSyncButtonsDisabled(false, 'order');
  }
}

async function pullNewOzonOrders() {
  const accountIds = getOzonFullSyncAccountIds('order');
  if (!accountIds.length) {
    setOzonOrderSyncStatus(`订单同步状态：${getOzonNoAuthorizedAccountMessage()}`, 'error');
    return;
  }
  return syncOzonOrdersFromSelectedAccount();
}

async function updateOzonCollectionStatus(ids, status) {
  const cleanIds = (ids || []).map(id => Number(id)).filter(id => Number.isInteger(id) && id > 0);
  if (!cleanIds.length) {
    setOzonResult('请选择要处理的采集记录', 'error');
    return;
  }
  setOzonResult(`正在更新为${getOzonStatusLabel(status)}...`);
  try {
    await ozonRequest('/collection-links/status', {
      method: 'POST',
      body: JSON.stringify({ ids: cleanIds, status })
    });
    cleanIds.forEach(id => ozonState.selectedCollectionIds.delete(String(id)));
    setOzonResult(`已更新 ${cleanIds.length} 条为${getOzonStatusLabel(status)}`, 'success');
    await loadOzonCollectionLinks();
  } catch (err) {
    setOzonResult(err.message || '更新失败', 'error');
  }
}

async function copyOzonText(text, message = '已复制') {
  try {
    await navigator.clipboard.writeText(text || '');
    setOzonResult(message, 'success');
  } catch (_) {
    const input = document.createElement('textarea');
    input.value = text || '';
    input.style.position = 'fixed';
    input.style.left = '-9999px';
    document.body.appendChild(input);
    input.select();
    document.execCommand('copy');
    input.remove();
    setOzonResult(message, 'success');
  }
}

function exportOzonProducts() {
  const selected = getSelectedOzonProductRows();
  const rows = selected.length ? selected : ozonState.products;
  if (!rows.length) {
    setOzonResult('暂无商品可导出', 'error');
    return;
  }
  downloadOzonCsv(`xiaoqingwa-ozon-products-${Date.now()}.csv`, rows, [
    { label: 'Product ID', value: row => normalizeOzonProductForUi(row).productId },
    { label: 'Offer ID', value: row => normalizeOzonProductForUi(row).offerId },
    { label: 'SKU', value: row => normalizeOzonProductForUi(row).sku },
    { label: '商品名称', value: row => normalizeOzonProductForUi(row).title },
    { label: '商品图片', value: row => normalizeOzonProductForUi(row).image },
    { label: '类目', value: row => normalizeOzonProductForUi(row).category },
    { label: '店铺', value: row => normalizeOzonProductForUi(row).accountTitle },
    { label: '状态', value: row => normalizeOzonProductForUi(row).statusLabel },
    { label: '销售价', value: row => normalizeOzonProductForUi(row).price.price },
    { label: '划线价', value: row => normalizeOzonProductForUi(row).price.oldPrice },
    { label: '最低价', value: row => normalizeOzonProductForUi(row).price.minPrice },
    { label: '促销价', value: row => normalizeOzonProductForUi(row).price.promoPrice },
    { label: '佣金比例', value: row => normalizeOzonProductForUi(row).commission.percent },
    { label: '库存', value: row => normalizeOzonProductForUi(row).stock.available },
    { label: '预留库存', value: row => normalizeOzonProductForUi(row).stock.reserved },
    { label: '错误原因', value: row => normalizeOzonProductForUi(row).errorText },
    { label: '创建日期', value: row => formatOzonDateTime(normalizeOzonProductForUi(row).createdAt) },
    { label: '更新时间', value: row => formatOzonDateTime(normalizeOzonProductForUi(row).updatedAt) }
  ]);
  setOzonResult(`已导出 ${rows.length} 个商品`, 'success');
}

function exportOzonOrders() {
  const rows = ozonState.orders || [];
  if (!rows.length) {
    setOzonResult('暂无订单可导出', 'error');
    return;
  }
  const detailRows = rows.flatMap(order => {
    const products = getOzonOrderProducts(order);
    const money = getOzonOrderMoney(order);
    const delivery = getOzonOrderDelivery(order);
    const cancelText = getOzonOrderCancelText(order);
    const base = { order, money, delivery, cancelText };
    if (!products.length) return [{ ...base, product: {} }];
    return products.map(product => ({ ...base, product }));
  });
  downloadOzonCsv(`xiaoqingwa-ozon-orders-${Date.now()}.csv`, detailRows, [
    { label: '销售单号', value: row => row.order.postingNumber },
    { label: '图片', value: row => pickOzonImage(row.product) },
    { label: '金额', value: row => formatOzonOrderAmount(row.money.amount || 0, row.money.currency || row.order.currencyCode || 'RUB') },
    { label: '手续费', value: row => getOzonOfficialCommissionText(row.money) },
    { label: '运费', value: row => getOzonOfficialDeliveryFeeText(row.money) },
    { label: '成本', value: row => formatOzonCnyAmount(row.money.cost || 0) },
    { label: '利润', value: row => row.money.profit === null || row.money.profit === undefined ? '' : formatOzonAmount(row.money.profit, row.money.profitCurrency || 'CNY') },
    { label: '利润率', value: row => row.money.profitRate === null || row.money.profitRate === undefined ? '' : `${Number(row.money.profitRate || 0).toFixed(2)}%` },
    { label: '状态', value: row => getOzonOrderStatusLabel(row.order.status, row.order) },
    { label: '采购单', value: row => row.order.purchaseOrderNumber || row.order.rawData?.purchase_order_number || row.order.rawData?.purchaseOrderNumber || '' },
    { label: '采购供应商', value: row => row.order.purchaseSupplier || row.order.rawData?.purchase_supplier || row.order.rawData?.purchaseSupplier || '' },
    { label: '运单', value: row => formatOzonWaybillText(row.delivery.tracking || '') },
    { label: '下单时间', value: row => getOzonOfficialOrderDateText(row.order) },
    { label: '店铺', value: row => row.order.accountTitle },
    { label: '商品名称', value: row => row.product.name || row.product.financial?.name || '' },
    { label: 'Offer ID', value: row => row.product.offer_id || row.product.offerId || row.product.financial?.offer_id || '' },
    { label: 'SKU', value: row => row.product.sku || row.product.financial?.sku || '' },
    { label: '数量', value: row => row.product.quantity || row.product.financial?.quantity || 0 },
    { label: '商品价格', value: row => row.product.price || row.product.financial?.price || '' }
  ]);
  setOzonResult(`已导出 ${rows.length} 个订单，${detailRows.length} 行明细`, 'success');
}

function exportOzonOrderAnalysis() {
  const rows = ozonState.orderAnalysis?.items || [];
  if (!rows.length) {
    setOzonResult('暂无订单分析可导出', 'error');
    return;
  }
  downloadOzonCsv(`xiaoqingwa-ozon-order-analysis-${Date.now()}.csv`, rows, [
    { label: 'Product ID', value: row => row.productId },
    { label: 'Offer ID', value: row => row.offerId },
    { label: 'SKU', value: row => row.sku },
    { label: '商品名称', value: row => row.name },
    { label: '类目', value: row => row.category },
    { label: '店铺', value: row => row.accountTitle },
    { label: '今日订单数', value: row => row.todayOrders ?? row.todayQty },
    { label: '近7日订单数', value: row => row.orderCount7 ?? row.qty7 },
    { label: '上个7日订单数', value: row => row.prevOrderCount7 ?? row.prevQty7 },
    { label: '7日环比', value: row => `${row.orderTrend7Percent ?? row.trend7Percent}%` },
    { label: '近28日订单数', value: row => row.orderCount28 ?? row.qty28 },
    { label: '近28日日均订单数', value: row => row.orderCount28 === undefined ? row.avg28 : Number((Number(row.orderCount28 || 0) / 28).toFixed(2)) },
    { label: '订单占比', value: row => `${row.share28Percent}%` },
    { label: '经营标签', value: row => row.tag },
    { label: '预警', value: row => row.alert ? '是' : '否' }
  ]);
  setOzonResult(`已导出 ${rows.length} 条订单分析`, 'success');
}

function exportOzonProductDiagnosis() {
  const rows = getFilteredOzonDiagnosisItems();
  if (!rows.length) {
    setOzonResult('暂无商品诊断可导出', 'error');
    return;
  }
  downloadOzonCsv(`xiaoqingwa-ozon-product-diagnosis-${Date.now()}.csv`, rows, [
    { label: 'Product ID', value: row => normalizeOzonProductForUi(row.product || {}).productId },
    { label: 'Offer ID', value: row => normalizeOzonProductForUi(row.product || {}).offerId },
    { label: 'SKU', value: row => normalizeOzonProductForUi(row.product || {}).sku },
    { label: '商品名称', value: row => normalizeOzonProductForUi(row.product || {}).title },
    { label: '店铺', value: row => normalizeOzonProductForUi(row.product || {}).accountTitle },
    { label: '标题得分', value: row => row.diagnosis?.score },
    { label: '风险等级', value: row => row.diagnosis?.riskLevel },
    { label: '商品状态', value: row => normalizeOzonProductForUi(row.product || {}).statusLabel },
    { label: '价格指数', value: row => row.diagnosis?.priceIndex?.label || normalizeOzonProductForUi(row.product || {}).priceIndex?.label || '' },
    { label: '库存', value: row => row.diagnosis?.stock ?? normalizeOzonProductForUi(row.product || {}).stock?.available ?? '' },
    { label: '图片数', value: row => row.diagnosis?.imageCount ?? '' },
    { label: '近7日订单数', value: row => (row.sales?.orderCount7 ?? row.sales?.qty7) || 0 },
    { label: '近28日订单数', value: row => (row.sales?.orderCount28 ?? row.sales?.qty28) || 0 },
    { label: '7日环比', value: row => `${Number(row.sales?.trend7Percent || 0).toFixed(1)}%` },
    { label: '现存问题', value: row => getOzonAiDiagnosisState(row.diagnosis || {}).problems.join('；') },
    { label: '优化建议', value: row => getOzonAiDiagnosisState(row.diagnosis || {}).suggestions.join('；') },
    { label: '优化依据', value: row => getOzonAiDiagnosisState(row.diagnosis || {}).basis.join('；') },
    { label: 'AI建议标题', value: row => getOzonAiTitleState(row.diagnosis || {}).title },
    { label: 'AI标题依据', value: row => getOzonAiTitleState(row.diagnosis || {}).basis },
    { label: '关键词依据', value: row => row.diagnosis?.aiKeywordBasis || getOzonAiTitleState(row.diagnosis || {}).basis || '' }
  ]);
  setOzonResult(`已导出 ${rows.length} 条商品诊断`, 'success');
}

function exportOzonLinkDiagnosis() {
  const rows = (ozonState.collectionLinks || []).map(link => ({ link, diagnosis: analyzeOzonCollectionLink(link) }));
  if (!rows.length) {
    setOzonResult('暂无链接诊断可导出', 'error');
    return;
  }
  downloadOzonCsv(`xiaoqingwa-ozon-link-diagnosis-${Date.now()}.csv`, rows, [
    { label: '标题', value: row => row.link.title },
    { label: '链接', value: row => row.link.url },
    { label: '状态', value: row => getOzonStatusLabel(row.link.status) },
    { label: '质量分', value: row => row.diagnosis.score },
    { label: '问题', value: row => row.diagnosis.problems.join('；') },
    { label: '建议', value: row => row.diagnosis.suggestion }
  ]);
  setOzonResult(`已导出 ${rows.length} 条链接诊断`, 'success');
}

function copySelectedOzonProductIds() {
  const selected = getSelectedOzonProductRows();
  if (!selected.length) {
    setOzonResult('请先勾选商品', 'error');
    return;
  }
  copyOzonText(selected.map(row => row.offerId || row.sku || row.productId || '').filter(Boolean).join('\n'));
}

function closeOzonProductBatchPriceModal() {
  const modal = document.getElementById('ozonProductBatchPriceModal');
  if (!modal) return;
  modal.classList.remove('is-open');
  modal.setAttribute('aria-hidden', 'true');
}

function ensureOzonProductBatchPriceModal() {
  let modal = document.getElementById('ozonProductBatchPriceModal');
  if (modal) return modal;
  modal = document.createElement('div');
  modal.id = 'ozonProductBatchPriceModal';
  modal.className = 'ozon-modal';
  modal.setAttribute('aria-hidden', 'true');
  modal.innerHTML = `<div class="ozon-modal-panel ozon-product-batch-price-panel ozon-product-seller-edit-panel" role="dialog" aria-modal="true" aria-labelledby="ozonProductBatchPriceTitle">
    <div class="ozon-modal-head ozon-product-seller-edit-head">
      <h3 id="ozonProductBatchPriceTitle">批量改价</h3>
      <button class="ozon-icon-btn" type="button" data-ozon-close-product-price-modal title="关闭">×</button>
    </div>
    <div class="ozon-product-batch-price-summary" id="ozonProductBatchPriceSummary"></div>
    <label class="ozon-product-batch-price-field ozon-seller-float-field"><span>销售价</span><input id="ozonProductBatchPriceInput" type="number" inputmode="decimal" min="0.01" step="0.01" placeholder="请输入销售价"></label>
    <div class="ozon-product-batch-price-note">只提交销售价。最低价、划线价、促销设置均不修改；OZON 返回的逐项结果将如实显示。</div>
    <div class="ozon-modal-result" id="ozonProductBatchPriceResult"></div>
    <div class="ozon-actions ozon-product-seller-edit-actions">
      <button class="btn btn-primary" id="ozonSubmitProductBatchPriceBtn" type="button">应用</button>
      <button class="btn btn-default" type="button" data-ozon-close-product-price-modal>取消</button>
    </div>
  </div>`;
  document.body.appendChild(modal);
  modal.addEventListener('click', (event) => {
    if (event.target === modal || event.target.closest?.('[data-ozon-close-product-price-modal]')) {
      closeOzonProductBatchPriceModal();
      return;
    }
    const submit = event.target.closest?.('#ozonSubmitProductBatchPriceBtn');
    if (submit) submitOzonProductBatchPrice();
  });
  return modal;
}

function openOzonProductBatchPriceModal(inputRows = null) {
  const list = document.getElementById('ozonProductList');
  const restoreScrollTop = Math.max(0, Number(
    ozonState.productBatchPriceOpenScrollTop ?? list?.scrollTop ?? 0
  ));
  delete ozonState.productBatchPriceOpenScrollTop;
  const selectedRows = Array.isArray(inputRows) ? inputRows : getSelectedOzonProductRows();
  if (!selectedRows.length) {
    setOzonResult('请先勾选要改价的商品', 'error');
    return;
  }
  const validRows = selectedRows.filter((row) => {
    const ui = normalizeOzonProductForUi(row);
    return Boolean(ui.productId || ui.offerId);
  });
  if (!validRows.length) {
    setOzonResult('所选商品缺少 Product ID 和货号，不能调用 OZON 官方改价接口', 'error');
    return;
  }
  const modal = ensureOzonProductBatchPriceModal();
  modal.dataset.ozonProductListScrollTop = String(restoreScrollTop);
  modal.dataset.productKeys = validRows.map(getOzonProductKey).filter(Boolean).join(',');
  const title = modal.querySelector('#ozonProductBatchPriceTitle');
  if (title) title.textContent = validRows.length === 1 ? '价格' : '批量改价';
  const accountNames = Array.from(new Set(validRows.map(row => normalizeOzonProductForUi(row).accountTitle).filter(Boolean)));
  const skipped = selectedRows.length - validRows.length;
  const summary = modal.querySelector('#ozonProductBatchPriceSummary');
  if (summary) {
    summary.textContent = `将为 ${validRows.length} 个商品提交销售价${accountNames.length ? `，店铺：${accountNames.join('、')}` : ''}${skipped ? `；${skipped} 个缺少官方商品标识，已排除` : ''}`;
  }
  const result = modal.querySelector('#ozonProductBatchPriceResult');
  if (result) result.textContent = '';
  const input = modal.querySelector('#ozonProductBatchPriceInput');
  if (input) input.value = '';
  modal.classList.add('is-open');
  modal.setAttribute('aria-hidden', 'false');
  restoreOzonProductBatchStockListScroll(modal);
  window.requestAnimationFrame(() => restoreOzonProductBatchStockListScroll(modal));
  window.setTimeout(() => restoreOzonProductBatchStockListScroll(modal), 50);
}

async function submitOzonProductBatchPrice() {
  const modal = document.getElementById('ozonProductBatchPriceModal');
  if (!modal) return;
  const input = modal.querySelector('#ozonProductBatchPriceInput');
  const resultEl = modal.querySelector('#ozonProductBatchPriceResult');
  const submit = modal.querySelector('#ozonSubmitProductBatchPriceBtn');
  const price = normalizeOzonText(input?.value || '');
  if (!/^\d+(?:\.\d{1,2})?$/.test(price) || Number(price) <= 0) {
    if (resultEl) resultEl.textContent = '请输入大于 0 且最多两位小数的销售价';
    return;
  }
  const groups = new Map();
  const productKeys = normalizeOzonText(modal.dataset.productKeys || '').split(',').filter(Boolean);
  const sourceRows = productKeys.length
    ? productKeys.map(findOzonProductByKey).filter(Boolean)
    : getSelectedOzonProductRows();
  sourceRows.forEach((row) => {
    const ui = normalizeOzonProductForUi(row);
    const accountId = normalizeOzonText(row.ozonAccountId || ozonState.productAccountId || getDefaultOzonAccountId());
    if (!accountId || (!ui.productId && !ui.offerId)) return;
    const productId = Number(ui.productId);
    if (!groups.has(accountId)) groups.set(accountId, []);
    groups.get(accountId).push({
      productId: Number.isFinite(productId) && productId > 0 ? productId : undefined,
      offerId: ui.offerId || undefined,
      price
    });
  });
  if (!groups.size) {
    if (resultEl) resultEl.textContent = '没有可提交给 OZON 的商品标识';
    return;
  }
  if (submit) submit.disabled = true;
  try {
    let submitted = 0;
    for (const [accountId, items] of groups) {
      const response = await ozonRequest('/products/prices', {
        method: 'POST',
        body: JSON.stringify({ ozonAccountId: accountId, items })
      });
      submitted += Number(response.submitted || items.length);
    }
    const message = `已向 OZON 提交 ${submitted} 个商品的改价请求。请点击“同步所选商品”读取 OZON 最终处理后的价格。`;
    if (resultEl) resultEl.textContent = message;
    setOzonResult(message, 'success');
  } catch (err) {
    const message = err.message || '批量改价失败';
    if (resultEl) resultEl.textContent = message;
    setOzonResult(message, 'error');
  } finally {
    if (submit) submit.disabled = false;
  }
}

function showOzonProductBatchNeedsForm(actionName = '批量操作') {
  const selectedCount = getSelectedOzonProductRows().length;
  if (!selectedCount) {
    setOzonResult('请先勾选要操作的商品', 'error');
    return;
  }
  setOzonResult(`${actionName}需要官方必填参数后才能提交，当前不提交任何数据。`, 'error');
}

function closeOzonProductBatchStockModal() {
  const modal = document.getElementById('ozonProductBatchStockModal');
  if (!modal) return;
  modal.classList.remove('is-open');
  modal.setAttribute('aria-hidden', 'true');
}

function restoreOzonProductBatchStockListScroll(modal) {
  const list = document.getElementById('ozonProductList');
  const scrollTop = Math.max(0, Number(modal?.dataset?.ozonProductListScrollTop || 0));
  if (list?.isConnected) list.scrollTop = scrollTop;
}

function ensureOzonProductBatchStockModal() {
  let modal = document.getElementById('ozonProductBatchStockModal');
  if (modal) return modal;
  modal = document.createElement('div');
  modal.id = 'ozonProductBatchStockModal';
  modal.className = 'ozon-modal';
  modal.setAttribute('aria-hidden', 'true');
  modal.innerHTML = `<div class="ozon-modal-panel ozon-product-batch-price-panel ozon-product-seller-edit-panel" role="dialog" aria-modal="true" aria-labelledby="ozonProductBatchStockTitle">
    <div class="ozon-modal-head ozon-product-seller-edit-head"><h3 id="ozonProductBatchStockTitle">在我的仓库中</h3><button class="ozon-icon-btn" type="button" data-ozon-close-product-stock-modal title="关闭">×</button></div>
    <div class="ozon-product-batch-price-summary" id="ozonProductBatchStockSummary"></div>
    <label class="ozon-product-batch-price-field ozon-seller-float-field"><span>仓库</span><select id="ozonProductBatchStockWarehouse"><option value="">正在读取仓库...</option></select></label>
    <label class="ozon-product-batch-price-field ozon-seller-float-field"><span>可订购数量</span><input id="ozonProductBatchStockInput" type="number" inputmode="numeric" min="0" step="1" placeholder="请输入非负整数"></label>
    <label class="ozon-product-batch-price-field ozon-seller-float-field is-disabled"><span>待备货订单预留库存</span><input id="ozonProductBatchStockReservedInput" type="text" disabled value="--"></label>
    <div class="ozon-product-batch-price-note">库存会直接写入所选 OZON 仓库；0 表示该仓库可售库存为 0。不会修改其他仓库库存。</div>
    <div class="ozon-modal-result" id="ozonProductBatchStockResult"></div>
    <div class="ozon-actions ozon-product-seller-edit-actions"><button class="btn btn-primary" id="ozonSubmitProductBatchStockBtn" type="button">应用</button><button class="btn btn-default" type="button" data-ozon-close-product-stock-modal>取消</button></div>
  </div>`;
  document.body.appendChild(modal);
  const warehouseSelect = modal.querySelector('#ozonProductBatchStockWarehouse');
  const restoreAfterWarehouseInteraction = () => {
    restoreOzonProductBatchStockListScroll(modal);
    window.requestAnimationFrame(() => restoreOzonProductBatchStockListScroll(modal));
    window.setTimeout(() => restoreOzonProductBatchStockListScroll(modal), 80);
  };
  warehouseSelect?.addEventListener('pointerdown', () => {
    const list = document.getElementById('ozonProductList');
    modal.dataset.ozonProductListScrollTop = String(Math.max(0, Number(list?.scrollTop || 0)));
    window.setTimeout(restoreAfterWarehouseInteraction, 80);
  }, true);
  warehouseSelect?.addEventListener('change', restoreAfterWarehouseInteraction);
  warehouseSelect?.addEventListener('blur', restoreAfterWarehouseInteraction);
  modal.addEventListener('click', (event) => {
    if (event.target === modal || event.target.closest?.('[data-ozon-close-product-stock-modal]')) return closeOzonProductBatchStockModal();
    if (event.target.closest?.('#ozonSubmitProductBatchStockBtn')) submitOzonProductBatchStock();
  });
  return modal;
}

async function openOzonProductBatchStockModal(inputRows = null) {
  const list = document.getElementById('ozonProductList');
  const restoreScrollTop = Math.max(0, Number(
    ozonState.productBatchStockOpenScrollTop ?? list?.scrollTop ?? 0
  ));
  delete ozonState.productBatchStockOpenScrollTop;
  const selectedRows = Array.isArray(inputRows) ? inputRows : getSelectedOzonProductRows();
  if (!selectedRows.length) return setOzonResult('请先勾选要改库存的商品', 'error');
  const fallbackAccountId = getSelectedOzonScopeId('product');
  const accountIds = Array.from(new Set(selectedRows.map(row => normalizeOzonText(row.ozonAccountId || fallbackAccountId)).filter(Boolean)));
  if (accountIds.length !== 1) return setOzonResult('批量改库存一次只能处理同一店铺的已勾选商品，请按店铺分别操作', 'error');
  const validRows = selectedRows.filter(row => {
    const ui = normalizeOzonProductForUi(row);
    return Boolean(ui.productId || ui.offerId);
  });
  if (!validRows.length) return setOzonResult('所选商品缺少 Product ID 和货号，不能调用 OZON 官方库存接口', 'error');
  const modal = ensureOzonProductBatchStockModal();
  modal.dataset.ozonProductListScrollTop = String(restoreScrollTop);
  const accountId = accountIds[0];
  modal.dataset.accountId = accountId;
  modal.dataset.productKeys = validRows.map(getOzonProductKey).filter(Boolean).join(',');
  const title = modal.querySelector('#ozonProductBatchStockTitle');
  if (title) title.textContent = validRows.length === 1 ? '在我的仓库中' : '批量改库存';
  const summary = modal.querySelector('#ozonProductBatchStockSummary');
  if (summary) summary.textContent = `将为 ${validRows.length} 个已勾选商品写入同一仓库库存，店铺：${getOzonAccountTitle(accountId) || accountId}`;
  const result = modal.querySelector('#ozonProductBatchStockResult');
  if (result) result.textContent = '';
  const input = modal.querySelector('#ozonProductBatchStockInput');
  if (input) input.value = '';
  const reservedInput = modal.querySelector('#ozonProductBatchStockReservedInput');
  if (reservedInput) {
    const reservedValues = validRows.map(row => normalizeOzonProductForUi(row).stock?.reserved).filter(value => value !== undefined && value !== null && value !== '');
    reservedInput.value = reservedValues.length === 1 ? String(reservedValues[0]) : (reservedValues.length > 1 ? '--' : '--');
  }
  const select = modal.querySelector('#ozonProductBatchStockWarehouse');
  if (select) { select.disabled = true; select.innerHTML = '<option value="">正在读取官方仓库...</option>'; }
  const submitButton = modal.querySelector('#ozonSubmitProductBatchStockBtn');
  if (submitButton) submitButton.disabled = true;
  modal.classList.add('is-open');
  modal.setAttribute('aria-hidden', 'false');
  const restoreListScroll = () => {
    restoreOzonProductBatchStockListScroll(modal);
  };
  restoreListScroll();
  window.requestAnimationFrame(restoreListScroll);
  window.setTimeout(restoreListScroll, 50);
  try {
    const cacheKey = `account:${accountId}`;
    const cached = ozonState.productWarehouseCache?.[cacheKey];
    const now = Date.now();
    const response = cached && Array.isArray(cached.warehouses) && now - Number(cached.loadedAt || 0) < 5 * 60 * 1000
      ? { warehouses: cached.warehouses }
      : await ozonRequest(`/products/warehouses?ozonAccountId=${encodeURIComponent(accountId)}`);
    const warehouses = Array.isArray(response.warehouses) ? response.warehouses : [];
    if (!warehouses.length) throw new Error('OZON 未返回可用仓库，不能提交库存');
    ozonState.productWarehouseCache[cacheKey] = { loadedAt: now, warehouses };
    if (select) {
      select.innerHTML = `<option value="">请选择仓库</option>${warehouses.map(warehouse => `<option value="${escapeHtml(String(warehouse.warehouseId))}">${escapeHtml(warehouse.name || `仓库 ${warehouse.warehouseId}`)}${warehouse.status ? ` (${escapeHtml(warehouse.status)})` : ''}</option>`).join('')}`;
      select.disabled = false;
    }
    if (submitButton) submitButton.disabled = false;
    // 不自动聚焦仓库下拉框，也不改变商品列表的滚动位置。
  } catch (err) {
    const message = err.message || '读取 OZON 仓库失败';
    if (select) {
      select.innerHTML = '<option value="">官方仓库读取失败</option>';
      select.disabled = true;
    }
    if (submitButton) submitButton.disabled = true;
    if (result) result.textContent = message;
  }
}

async function submitOzonProductBatchStock() {
  const modal = document.getElementById('ozonProductBatchStockModal');
  if (!modal) return;
  const warehouseId = Number(modal.querySelector('#ozonProductBatchStockWarehouse')?.value || 0);
  const stockText = normalizeOzonText(modal.querySelector('#ozonProductBatchStockInput')?.value || '');
  const resultEl = modal.querySelector('#ozonProductBatchStockResult');
  const submit = modal.querySelector('#ozonSubmitProductBatchStockBtn');
  if (!Number.isInteger(warehouseId) || warehouseId <= 0) { if (resultEl) resultEl.textContent = '请选择 OZON 官方仓库'; return; }
  if (!/^\d+$/.test(stockText)) { if (resultEl) resultEl.textContent = '库存必须是非负整数'; return; }
  const accountId = normalizeOzonText(modal.dataset.accountId || '');
  const productKeys = normalizeOzonText(modal.dataset.productKeys || '').split(',').filter(Boolean);
  const rows = productKeys.length
    ? productKeys.map(findOzonProductByKey).filter(Boolean)
    : getSelectedOzonProductRows();
  const items = rows.map((row) => {
    const ui = normalizeOzonProductForUi(row);
    return { productId: ui.productId || undefined, offerId: ui.offerId || undefined };
  }).filter(item => item.productId || item.offerId);
  if (!accountId || !items.length) { if (resultEl) resultEl.textContent = '没有可提交给 OZON 的商品标识'; return; }
  if (submit) submit.disabled = true;
  try {
    const response = await ozonRequest('/products/stocks', { method: 'POST', body: JSON.stringify({ ozonAccountId: accountId, warehouseId, stock: Number(stockText), items }) });
    const submitted = Number(response.submitted || items.length);
    setOzonProductStockSyncing(rows, true);
    closeOzonProductBatchStockModal();
    setOzonResult(`已向 OZON 提交 ${submitted} 个商品的仓库库存更新，正在同步商品最新库存...`, 'success');
    try {
      const syncResult = await syncOzonProductRows(rows, accountId);
      setOzonResult(`库存已提交并同步完成：提交 ${submitted} 个，保存 ${Number(syncResult.totalSaved || 0)} 个`, 'success');
    } catch (syncErr) {
      setOzonResult(`库存已提交，但自动同步失败：${syncErr.message || '请稍后手动同步该商品'}`, 'error');
    } finally {
      setOzonProductStockSyncing(rows, false);
    }
  } catch (err) {
    const message = err.message || '批量改库存失败';
    if (resultEl) resultEl.textContent = message;
    setOzonResult(message, 'error');
  } finally { if (submit) submit.disabled = false; }
}

function closeOzonProductEditModal() {
  const modal = document.getElementById('ozonProductEditModal');
  if (!modal) return;
  modal.classList.remove('is-open');
  modal.setAttribute('aria-hidden', 'true');
}

function ensureOzonProductEditModal() {
  let modal = document.getElementById('ozonProductEditModal');
  if (modal) return modal;
  modal = document.createElement('div');
  modal.id = 'ozonProductEditModal';
  modal.className = 'ozon-modal';
  modal.setAttribute('aria-hidden', 'true');
  modal.innerHTML = `<div class="ozon-modal-panel ozon-product-edit-panel" role="dialog" aria-modal="true" aria-labelledby="ozonProductEditTitle">
    <div class="ozon-modal-head">
      <h3 id="ozonProductEditTitle">商品编辑</h3>
      <button class="ozon-icon-btn" type="button" data-ozon-close-product-edit-modal title="关闭">×</button>
    </div>
    <div id="ozonProductEditBody" class="ozon-product-edit-body"></div>
    <div class="ozon-modal-result" id="ozonProductEditResult"></div>
    <div class="ozon-actions">
      <button class="btn btn-default" type="button" data-ozon-close-product-edit-modal>取消并关闭</button>
    </div>
  </div>`;
  document.body.appendChild(modal);
  modal.addEventListener('click', (event) => {
    if (event.target === modal || event.target.closest?.('[data-ozon-close-product-edit-modal]')) {
      closeOzonProductEditModal();
      return;
    }
    const warehouseSelect = event.target.closest?.('#ozonProductEditWarehouseSelect');
    if (warehouseSelect) {
      const input = modal.querySelector('#ozonProductEditStockInput');
      const stockMap = parseJson(modal.dataset.stockByWarehouse || '{}', {});
      const stockValue = stockMap[normalizeOzonText(warehouseSelect.value || '')];
      if (input) input.value = stockValue === undefined || stockValue === null ? '' : String(stockValue);
    }
    if (event.target.closest?.('#ozonProductEditSubmitPriceBtn')) submitOzonProductEditPrice();
    if (event.target.closest?.('#ozonProductEditSubmitStockBtn')) submitOzonProductEditStock();
  });
  modal.addEventListener('change', (event) => {
    const warehouseSelect = event.target.closest?.('#ozonProductEditWarehouseSelect');
    if (!warehouseSelect) return;
    const input = modal.querySelector('#ozonProductEditStockInput');
    const stockMap = parseJson(modal.dataset.stockByWarehouse || '{}', {});
    const stockValue = stockMap[normalizeOzonText(warehouseSelect.value || '')];
    if (input) input.value = stockValue === undefined || stockValue === null ? '' : String(stockValue);
  });
  return modal;
}

function renderOzonProductEditModalBody(product = {}) {
  const ui = normalizeOzonProductForUi(product);
  const key = getOzonProductKey(product);
  const image = renderOzonClickableImageThumb(ui.image, 'ozon-product-edit-thumb', ui.title);
  const priceValue = parseOzonNumber(ui.price?.price);
  return `<div class="ozon-product-edit-summary">
    ${image}
    <div class="ozon-product-edit-summary-main">
      <strong title="${escapeHtml(ui.title)}">${escapeHtml(ui.title)}</strong>
      <span>货号：${escapeHtml(ui.offerId || '--')} · SKU：${escapeHtml(ui.sku || '--')}</span>
      <span>类目：${escapeHtml(ui.category || '--')}</span>
    </div>
  </div>
  <div class="ozon-product-edit-grid">
    <section class="ozon-product-edit-section">
      <div class="ozon-product-edit-section-head"><strong>价格</strong><span>官方改价接口</span></div>
      <label class="ozon-product-edit-field">销售价
        <input id="ozonProductEditPriceInput" type="number" inputmode="decimal" min="0.01" step="0.01" value="${Number.isFinite(priceValue) ? escapeHtml(String(priceValue)) : ''}" placeholder="请输入销售价">
      </label>
      <div class="ozon-product-edit-metrics">
        <span>划线价：${escapeHtml(formatOzonAmount(ui.price?.oldPrice, ui.price?.currency || 'RUB'))}</span>
        <span>最低价：${escapeHtml(formatOzonAmount(ui.price?.minPrice, ui.price?.currency || 'RUB'))}</span>
      </div>
      <button class="btn btn-primary" id="ozonProductEditSubmitPriceBtn" type="button" data-product-key="${escapeHtml(key)}">提交价格</button>
    </section>
    <section class="ozon-product-edit-section">
      <div class="ozon-product-edit-section-head"><strong>库存</strong><span>官方仓库库存</span></div>
      <label class="ozon-product-edit-field">仓库
        <select id="ozonProductEditWarehouseSelect"><option value="">正在读取官方仓库...</option></select>
      </label>
      <label class="ozon-product-edit-field">可售库存
        <input id="ozonProductEditStockInput" type="number" inputmode="numeric" min="0" step="1" value="" placeholder="选择仓库后填写库存">
      </label>
      <div class="ozon-product-edit-metrics"><span>当前缓存总库存：${escapeHtml(formatOzonNumber(ui.stock?.available) || '--')}</span></div>
      <button class="btn btn-primary" id="ozonProductEditSubmitStockBtn" type="button" data-product-key="${escapeHtml(key)}" disabled>提交库存</button>
    </section>
  </div>
  <div class="ozon-product-edit-note">标题、品牌、类目属性等商品资料更新需要官方完整必填属性和属性 ID。当前弹窗只提交已验证的官方价格/库存写入，不生成兜底字段。</div>`;
}

async function openOzonProductEditModal(key = '') {
  const product = findOzonProductByKey(key);
  if (!product) return setOzonResult('没有找到要编辑的商品，请刷新列表后重试', 'error');
  const ui = normalizeOzonProductForUi(product);
  if (!ui.productId && !ui.offerId) return setOzonResult('该商品缺少 Product ID 和货号，不能提交官方编辑接口', 'error');
  const accountId = normalizeOzonText(product.ozonAccountId || ozonState.productAccountId || getDefaultOzonAccountId());
  if (!accountId) return setOzonResult('请先选择 OZON 店铺', 'error');
  const modal = ensureOzonProductEditModal();
  modal.dataset.ozonProductListScrollTop = String(Math.max(0, Number(document.getElementById('ozonProductList')?.scrollTop || 0)));
  modal.dataset.productKey = key;
  modal.dataset.accountId = accountId;
  modal.dataset.stockByWarehouse = JSON.stringify(getOzonWarehouseStockMap(product));
  const body = modal.querySelector('#ozonProductEditBody');
  const result = modal.querySelector('#ozonProductEditResult');
  if (body) body.innerHTML = renderOzonProductEditModalBody(product);
  if (result) result.textContent = '';
  modal.classList.add('is-open');
  modal.setAttribute('aria-hidden', 'false');
  const select = modal.querySelector('#ozonProductEditWarehouseSelect');
  const stockSubmit = modal.querySelector('#ozonProductEditSubmitStockBtn');
  const restoreListScroll = () => {
    restoreOzonProductBatchStockListScroll(modal);
    window.requestAnimationFrame(() => restoreOzonProductBatchStockListScroll(modal));
    window.setTimeout(() => restoreOzonProductBatchStockListScroll(modal), 80);
  };
  select?.addEventListener('pointerdown', () => {
    modal.dataset.ozonProductListScrollTop = String(Math.max(0, Number(document.getElementById('ozonProductList')?.scrollTop || 0)));
    window.setTimeout(restoreListScroll, 80);
  }, true);
  select?.addEventListener('change', restoreListScroll);
  select?.addEventListener('blur', restoreListScroll);
  try {
    const response = await ozonRequest(`/products/warehouses?ozonAccountId=${encodeURIComponent(accountId)}`);
    const warehouses = Array.isArray(response.warehouses) ? response.warehouses : [];
    if (!warehouses.length) throw new Error('OZON 未返回可用仓库，不能提交库存');
    if (select) {
      select.innerHTML = `<option value="">请选择仓库</option>${warehouses.map(warehouse => `<option value="${escapeHtml(String(warehouse.warehouseId))}">${escapeHtml(warehouse.name || `仓库 ${warehouse.warehouseId}`)}${warehouse.status ? ` (${escapeHtml(warehouse.status)})` : ''}</option>`).join('')}`;
      select.disabled = false;
    }
    if (stockSubmit) stockSubmit.disabled = false;
  } catch (err) {
    if (select) {
      select.innerHTML = '<option value="">官方仓库读取失败</option>';
      select.disabled = true;
    }
    if (stockSubmit) stockSubmit.disabled = true;
    if (result) result.textContent = err.message || '读取 OZON 仓库失败';
  }
}

async function submitOzonProductEditPrice() {
  const modal = document.getElementById('ozonProductEditModal');
  if (!modal) return;
  const product = findOzonProductByKey(modal.dataset.productKey || '');
  const result = modal.querySelector('#ozonProductEditResult');
  const submit = modal.querySelector('#ozonProductEditSubmitPriceBtn');
  const price = normalizeOzonText(modal.querySelector('#ozonProductEditPriceInput')?.value || '');
  if (!product) { if (result) result.textContent = '没有找到要编辑的商品'; return; }
  if (!/^\d+(?:\.\d{1,2})?$/.test(price) || Number(price) <= 0) { if (result) result.textContent = '请输入大于 0 且最多两位小数的销售价'; return; }
  const ui = normalizeOzonProductForUi(product);
  const productId = Number(ui.productId);
  const accountId = normalizeOzonText(modal.dataset.accountId || product.ozonAccountId || ozonState.productAccountId || '');
  if (submit) submit.disabled = true;
  try {
    const response = await ozonRequest('/products/prices', {
      method: 'POST',
      body: JSON.stringify({
        ozonAccountId: accountId,
        items: [{
          productId: Number.isFinite(productId) && productId > 0 ? productId : undefined,
          offerId: ui.offerId || undefined,
          price
        }]
      })
    });
    const message = `已向 OZON 提交价格更新：${Number(response.submitted || 1)} 个商品`;
    if (result) result.textContent = message;
    setOzonResult(message, 'success');
  } catch (err) {
    const message = err.message || '商品改价失败';
    if (result) result.textContent = message;
    setOzonResult(message, 'error');
  } finally {
    if (submit) submit.disabled = false;
  }
}

async function submitOzonProductEditStock() {
  const modal = document.getElementById('ozonProductEditModal');
  if (!modal) return;
  const product = findOzonProductByKey(modal.dataset.productKey || '');
  const result = modal.querySelector('#ozonProductEditResult');
  const submit = modal.querySelector('#ozonProductEditSubmitStockBtn');
  const warehouseId = Number(modal.querySelector('#ozonProductEditWarehouseSelect')?.value || 0);
  const stockText = normalizeOzonText(modal.querySelector('#ozonProductEditStockInput')?.value || '');
  if (!product) { if (result) result.textContent = '没有找到要编辑的商品'; return; }
  if (!Number.isInteger(warehouseId) || warehouseId <= 0) { if (result) result.textContent = '请选择 OZON 官方仓库'; return; }
  if (!/^\d+$/.test(stockText)) { if (result) result.textContent = '库存必须是非负整数'; return; }
  const ui = normalizeOzonProductForUi(product);
  const productId = Number(ui.productId);
  const accountId = normalizeOzonText(modal.dataset.accountId || product.ozonAccountId || ozonState.productAccountId || '');
  if (submit) submit.disabled = true;
  try {
    const response = await ozonRequest('/products/stocks', {
      method: 'POST',
      body: JSON.stringify({
        ozonAccountId: accountId,
        warehouseId,
        stock: Number(stockText),
        items: [{
          productId: Number.isFinite(productId) && productId > 0 ? productId : undefined,
          offerId: ui.offerId || undefined
        }]
      })
    });
    const message = `已向 OZON 提交库存更新：${Number(response.submitted || 1)} 个商品`;
    if (result) result.textContent = message;
    setOzonProductStockSyncing([product], true);
    closeOzonProductEditModal();
    setOzonResult(`${message}，正在同步商品最新库存...`, 'success');
    try {
      const syncResult = await syncOzonProductRows([product], accountId);
      setOzonResult(`库存已提交并同步完成：提交 1 个，保存 ${Number(syncResult.totalSaved || 0)} 个`, 'success');
    } catch (syncErr) {
      setOzonResult(`库存已提交，但自动同步失败：${syncErr.message || '请稍后手动同步该商品'}`, 'error');
    } finally {
      setOzonProductStockSyncing([product], false);
    }
  } catch (err) {
    const message = err.message || '商品改库存失败';
    if (result) result.textContent = message;
    setOzonResult(message, 'error');
  } finally {
    if (submit) submit.disabled = false;
  }
}

async function archiveSelectedOzonProducts() {
  const selectedRows = getSelectedOzonProductRows();
  if (!selectedRows.length) {
    setOzonResult('请先勾选要归档的商品', 'error');
    return;
  }
  const productIds = selectedRows
    .map(row => Number(normalizeOzonProductForUi(row).productId || 0))
    .filter(id => Number.isFinite(id) && id > 0);
  if (!productIds.length) {
    setOzonResult('所选商品缺少 Product ID，不能调用 OZON 官方归档接口', 'error');
    return;
  }
  const fallbackAccountId = getSelectedOzonScopeId('product');
  const groups = selectedRows.reduce((acc, row) => {
    const accountId = normalizeOzonText(row.ozonAccountId || fallbackAccountId);
    const productId = Number(normalizeOzonProductForUi(row).productId || 0);
    if (!accountId || !Number.isFinite(productId) || productId <= 0) return acc;
    if (!acc.has(accountId)) acc.set(accountId, []);
    acc.get(accountId).push(productId);
    return acc;
  }, new Map());
  if (!groups.size) {
    setOzonResult('请先选择 OZON 店铺', 'error');
    return;
  }
  const total = Array.from(groups.values()).reduce((sum, ids) => sum + ids.length, 0);
  if (!window.confirm(`确定将所选 ${total} 个商品归档吗？该操作会调用 OZON 官方接口。`)) return;
  setOzonSyncButtonsDisabled(true, 'product');
  setOzonSyncProgress('product', 8, `正在归档所选 ${total} 个 OZON 商品...`);
  try {
    let archived = 0;
    const entries = Array.from(groups.entries());
    for (let index = 0; index < entries.length; index += 1) {
      const [accountId, ids] = entries[index];
      const uniqueIds = Array.from(new Set(ids));
      for (let offset = 0; offset < uniqueIds.length; offset += 100) {
        const chunk = uniqueIds.slice(offset, offset + 100);
        setOzonSyncProgress('product', 8 + Math.round((archived / Math.max(1, total)) * 76), `正在归档：${getOzonAccountTitle(accountId) || 'OZON 店铺'} ${archived + 1}-${archived + chunk.length}/${total}`);
        const result = await ozonRequest('/products/archive', {
          method: 'POST',
          body: JSON.stringify({ ozonAccountId: accountId, productIds: chunk })
        });
        archived += Number(result.archived || chunk.length);
      }
    }
    setOzonSyncProgress('product', 90, '归档完成，正在刷新商品列表和诊断缓存...');
    clearOzonProductSelection();
    await Promise.allSettled([
      loadOzonProducts({ forceRefresh: true }),
      loadOzonProductDiagnosis({ enrichAi: false, forceRefresh: true, resetAiQueue: false })
    ]);
    setOzonSyncProgress('product', 100, `已归档 ${archived} 个商品`, 'success');
  } catch (err) {
    setOzonSyncProgress('product', 100, err.message || '商品归档失败', 'error');
  } finally {
    setOzonSyncButtonsDisabled(false, 'product');
    updateOzonProductSelectionUi();
  }
}

function toggleOzonBulkProductMenu(force) {
  const menu = document.getElementById('ozonBulkProductMenu');
  const button = document.getElementById('ozonBulkProductMenuBtn');
  if (!menu || !button) return;
  const willOpen = typeof force === 'boolean' ? force : !menu.classList.contains('is-open');
  menu.classList.toggle('is-open', willOpen);
  menu.setAttribute('aria-hidden', willOpen ? 'false' : 'true');
  button.setAttribute('aria-expanded', willOpen ? 'true' : 'false');
}

function bindOzonDashboardEvents() {
  bindOzonHomeIntegrationDelegates();
  const bindOnce = (id, handler) => {
    const el = document.getElementById(id);
    if (!el || el.dataset.ozonBound === '1') return;
    el.dataset.ozonBound = '1';
    el.addEventListener('click', handler);
  };
  bindOnce('ozonRunAiOrderAnalysisBtn', () => runOzonAiAnalysis('orders'));
  bindOnce('ozonRunAiProductAnalysisBtn', () => runOzonAiAnalysis('links'));
  bindOnce('ozonOpenSellerProductsBtn', () => window.open('https://seller.ozon.ru/app/products', '_blank'));
  bindOnce('ozonOpenUploadTemplateModalBtn', () => {
    if (!openOzonUploadTemplateModalForAccount()) {
      setOzonResult('请先选择 OZON 店铺', 'error');
    }
  });
  bindOnce('ozonUploadTemplateModalCloseBtn', () => setOzonUploadTemplateModalVisible(false));
  bindOnce('ozonUploadTemplateModalCancelBtn', () => setOzonUploadTemplateModalVisible(false));
  bindOnce('ozonSaveUploadTemplateBtn', () => withOzonButtonLoading('ozonSaveUploadTemplateBtn', '保存中...', async () => {
    const accountId = getOzonUploadAccountId();
    const sourceRef = document.getElementById('ozonUploadSourceProductId')?.value?.trim() || '';
    const templateName = document.getElementById('ozonUploadTemplateName')?.value?.trim() || '';
    if (!accountId) throw new Error('请先选择 OZON 店铺');
    if (!sourceRef) throw new Error('请填写已上架商品的 Product ID、Offer ID 或 SKU');
    if (!templateName) throw new Error('请填写模板名称');
    const result = await ozonRequest('/upload-templates/from-product', {
      method: 'POST',
      body: JSON.stringify({ ozonAccountId: accountId, sourceRef, templateName })
    });
    await loadOzonUploadTemplates({ forceRefresh: true });
    window.dispatchEvent(new CustomEvent('ozon:upload-templates-changed', { detail: { accountId } }));
    setOzonUploadTemplateModalResult(result.message || '已保存模板', 'success');
    setOzonResult(result.message || '已保存 OZON 官方商品属性模板', 'success');
    window.setTimeout(() => setOzonUploadTemplateModalVisible(false), 500);
  }).catch(err => {
    setOzonUploadTemplateModalResult(err.message || '保存模板失败', 'error');
    setOzonResult(err.message || '保存模板失败', 'error');
  }));
  bindOnce('ozonRefreshUploadTemplatesBtn', () => withOzonButtonLoading('ozonRefreshUploadTemplatesBtn', '读取中...', () => loadOzonUploadTemplates({ forceRefresh: true })).catch(err => setOzonResult(err.message || '读取模板失败', 'error')));
  bindOnce('ozonDownloadUploadExcelTemplateBtn', downloadOzonUploadExcelTemplate);
  const bindUploadFileName = (inputId, nameId, emptyText, kind = '') => {
    const input = document.getElementById(inputId);
    if (!input || input.dataset.ozonBound === '1') return;
    input.dataset.ozonBound = '1';
    input.addEventListener('change', () => {
      const name = document.getElementById(nameId);
      if (!name) return;
      const files = Array.from(input.files || []);
      if (kind === 'excel') ozonState.uploadExcelFile = files[0] || null;
      if (kind === 'zip') ozonState.uploadImageZipFile = files[0] || null;
      if (kind === 'images') ozonState.uploadImageFiles = files.map(file => createOzonUploadFileItem(file, file.webkitRelativePath || file.name));
      clearOzonUploadPreviewUrls();
      ozonState.uploadPrecheckRows = [];
      const submitButton = document.getElementById('ozonSubmitUploadBtn');
      if (submitButton) submitButton.disabled = true;
      name.textContent = files.length ? (kind === 'images' && files.length > 1 ? `已选择 ${files.length} 张图片` : files[0].name) : emptyText;
      if (files.length) setOzonUploadResultMessage(kind === 'excel' ? '已读取 Excel 文件，请继续选择图片。' : kind === 'zip' ? '已读取图片 ZIP，可以点击预检数据。' : `已读取 ${files.length} 张图片，可以点击预检数据。`, 'success');
    });
  };
  const bindUploadDropZone = (labelId, nameId, kind = '') => {
      const zone = document.querySelector(`[data-ozon-upload-drop-zone="${labelId}"], [data-ozon-upload-zone="${labelId}"], [for="${labelId}"]`);
    const name = document.getElementById(nameId);
    if (!zone || zone.dataset.ozonDropBound === '1') return;
    zone.dataset.ozonDropBound = '1';
    ['dragenter', 'dragover'].forEach(type => {
      zone.addEventListener(type, (event) => {
        event.preventDefault();
        event.stopPropagation();
        zone.classList.add('is-dragover');
      });
    });
    ['dragleave', 'drop'].forEach(type => {
      zone.addEventListener(type, (event) => {
        event.preventDefault();
        event.stopPropagation();
        if (type === 'dragleave' && zone.contains(event.relatedTarget)) return;
        zone.classList.remove('is-dragover');
      });
    });
    zone.addEventListener('drop', async (event) => {
      const files = await getOzonDroppedFileItems(event.dataTransfer);
      if (kind === 'excel') {
        const excel = files.map(item => getOzonUploadFileItemFile(item)).find(file => /\.(xlsx|xls|csv)$/i.test(file?.name || ''));
        if (!excel) {
          setOzonResult('拖拽文件里没有找到 .xlsx / .xls / .csv 文件', 'error');
          return;
        }
        ozonState.uploadExcelFile = excel;
        clearOzonUploadPreviewUrls();
        if (name) name.textContent = excel.name;
      } else if (kind === 'images') {
        const zip = files.map(item => getOzonUploadFileItemFile(item)).find(file => /\.zip$/i.test(file?.name || ''));
        if (zip) {
          ozonState.uploadImageZipFile = zip;
          clearOzonUploadPreviewUrls();
          const zipName = document.getElementById('ozonUploadImageZipFileName');
          if (zipName) zipName.textContent = zip.name;
          setOzonUploadResultMessage('已读取拖拽的图片 ZIP，可以点击预检数据。', 'success');
          ozonState.uploadPrecheckRows = [];
          const submitButton = document.getElementById('ozonSubmitUploadBtn');
          if (submitButton) submitButton.disabled = true;
          return;
        }
        const images = files.filter(item => /\.(jpe?g|png)$/i.test(getOzonUploadFileItemPath(item)));
        if (!images.length) {
          setOzonResult('拖拽内容里没有找到 ZIP 或 jpg / png 图片', 'error');
          return;
        }
        ozonState.uploadImageFiles = images;
        clearOzonUploadPreviewUrls();
        if (name) name.textContent = `已拖拽 ${images.length} 张图片`;
      }
      ozonState.uploadPrecheckRows = [];
      const submitButton = document.getElementById('ozonSubmitUploadBtn');
      if (submitButton) submitButton.disabled = true;
      setOzonUploadResultMessage(kind === 'excel' ? '已读取拖拽的 Excel 文件，可以继续选择图片文件夹。' : '已读取拖拽的图片文件夹，可以点击预检数据。', 'success');
    });
  };
  bindUploadFileName('ozonUploadExcelInput', 'ozonUploadExcelFileName', '未选择 .xlsx / .xls / .csv', 'excel');
  bindUploadFileName('ozonUploadImageZipInput', 'ozonUploadImageZipFileName', '未选择 .zip，ZIP 内保持 编号/1.jpg 结构', 'zip');
  bindUploadFileName('ozonUploadImageFolderInput', 'ozonUploadImageFolderFileName', '未选择图片文件夹', 'images');
  bindUploadDropZone('ozonUploadExcelInput', 'ozonUploadExcelFileName', 'excel');
  bindUploadDropZone('ozonUploadImageFolderInput', 'ozonUploadImageFolderFileName', 'images');
  bindOnce('ozonPrecheckUploadBtn', () => withOzonButtonLoading('ozonPrecheckUploadBtn', '预检中...', precheckOzonUpload).catch(err => setOzonUploadResultMessage(err.message || '预检失败', 'error')));
  bindOnce('ozonSubmitUploadBtn', () => submitOzonUpload().catch(err => setOzonUploadResultMessage(err.message || '提交 OZON 上架失败', 'error')));
  const uploadAccountFilter = document.getElementById('ozonUploadAccountFilter');
  if (uploadAccountFilter && uploadAccountFilter.dataset.ozonBound !== '1') {
    uploadAccountFilter.dataset.ozonBound = '1';
    uploadAccountFilter.addEventListener('change', () => {
      ozonState.uploadAccountId = getOzonActiveAccountId(uploadAccountFilter.value || '');
      ozonState.uploadTemplates = [];
      document.getElementById('ozonUploadSourceProductId').value = '';
      loadOzonUploadTemplates({ forceRefresh: true }).catch(err => setOzonResult(err.message || '读取模板失败', 'error'));
    });
  }
  document.querySelectorAll('[data-ozon-open-seller-orders]').forEach((button) => {
    if (button.dataset.ozonBound === '1') return;
    button.dataset.ozonBound = '1';
    button.addEventListener('click', () => window.open('https://seller.ozon.ru/app/postings', '_blank'));
  });
  bindOnce('ozonOpenOzonMallBtn', () => window.open('https://www.ozon.ru/', '_blank'));
  bindOnce('ozonHomeTaskRefreshBtn', () => withOzonButtonLoading('ozonHomeTaskRefreshBtn', '↻', refreshOzonHomeTaskSnapshots).catch(err => setOzonResult(err.message || '刷新今日任务失败', 'error')));
  bindOnce('ozonHomeAddAccountKpiBtn', () => toggleOzonAccountAuthPanel(true));
  bindOnce('ozonToggleAuthPanelBtn', () => toggleOzonAccountAuthPanel());
  bindOnce('ozonAccountAuthCloseBtn', () => toggleOzonAccountAuthPanel(false));
  bindOnce('ozonPerformanceAuthCloseBtn', () => toggleOzonPerformanceAuthPanel(false));
  bindOnce('ozonGoCollectionBtn', () => {
    const tab = document.getElementById('ozonCollectionNav');
    if (tab) handleTabClick(tab);
  });
  bindOnce('ozonSaveAccountBtn', () => saveOzonAccountFromForm(false));
  bindOnce('ozonCheckAccountBtn', () => saveOzonAccountFromForm(true));
  bindOnce('ozonSavePerformanceBtn', () => saveOzonPerformanceFromForm(false));
  bindOnce('ozonCheckPerformanceBtn', () => saveOzonPerformanceFromForm(true));
  bindOnce('ozonOpenPerformanceAuthBtn', () => toggleOzonPerformanceAuthPanel());
  bindOnce('ozonTestPerformanceBtn', async () => {
    try {
      const accountId = getOzonPerformanceAccountId();
      const result = await ozonRequest('/performance/config/test', {
        method: 'POST',
        body: JSON.stringify({ accountId })
      });
      ozonState.performanceConfig = result.config || ozonState.performanceConfig;
      renderOzonPerformanceConfig();
      setOzonText('ozonAdsMeta', result.text || 'Performance API 在线');
      setOzonResult(result.text || 'Performance API 在线', 'success');
    } catch (err) {
      setOzonText('ozonAdsMeta', err.message || 'Performance API 检测失败');
      setOzonResult(err.message || 'Performance API 检测失败', 'error');
    }
  });
  bindOnce('ozonNotificationGuideCloseBtn', () => toggleOzonNotificationGuideModal(false));
  bindOnce('ozonCopyNotificationGuideUrlBtn', () => copyOzonText(getOzonNotificationWebhookUrl()));
  bindOnce('ozonRefreshAdsBtn', () => withOzonButtonLoading('ozonRefreshAdsBtn', '读取中...', () => loadOzonAds({ forceRefresh: true })));
  const adsAccountFilter = document.getElementById('ozonAdsAccountFilter');
  if (adsAccountFilter && adsAccountFilter.dataset.ozonChangeBound !== '1') {
    adsAccountFilter.dataset.ozonChangeBound = '1';
    adsAccountFilter.addEventListener('change', () => {
      ozonState.adsAccountId = getOzonActiveAccountId(adsAccountFilter.value || '');
      ozonState.performanceAccountId = ozonState.adsAccountId;
      ozonState.performanceConfig = null;
      ozonState.ads = null;
      loadOzonPerformanceConfig({ forceRefresh: true }).then(() => renderOzonAds()).catch(() => renderOzonAds());
    });
  }
  bindOnce('ozonRefreshTrafficBtn', () => withOzonButtonLoading('ozonRefreshTrafficBtn', '读取中...', () => {
    ozonState.trafficPage = 1;
    return loadOzonTraffic({ forceRefresh: true });
  }));
  bindOnce('ozonSearchAdsBtn', () => {
    ozonState.adsState = document.getElementById('ozonAdsStateFilter')?.value || '';
    ozonState.adsType = document.getElementById('ozonAdsTypeFilter')?.value || '';
    ozonState.adsKeyword = document.getElementById('ozonAdsKeywordInput')?.value?.trim() || '';
    loadOzonAds({ allowStale: true, keepStaleOnError: true });
  });
  bindOnce('ozonClearAdsBtn', () => {
    const state = document.getElementById('ozonAdsStateFilter');
    const type = document.getElementById('ozonAdsTypeFilter');
    const keyword = document.getElementById('ozonAdsKeywordInput');
    if (state) state.value = '';
    if (type) type.value = '';
    if (keyword) keyword.value = '';
    ozonState.adsState = '';
    ozonState.adsType = '';
    ozonState.adsKeyword = '';
    loadOzonAds({ allowStale: true, keepStaleOnError: true });
  });
  bindOnce('ozonSearchTrafficBtn', () => {
    ozonState.trafficAccountId = getOzonActiveAccountId(document.getElementById('ozonTrafficAccountFilter')?.value || '');
    ozonState.trafficKeyword = document.getElementById('ozonTrafficKeywordInput')?.value?.trim() || '';
    ozonState.trafficPage = 1;
    updateOzonTabSyncStamps('ozonTraffic');
    loadOzonTraffic({ allowStale: true, keepStaleOnError: true });
  });
  bindOnce('ozonClearTrafficBtn', () => {
    const account = document.getElementById('ozonTrafficAccountFilter');
    const keyword = document.getElementById('ozonTrafficKeywordInput');
    if (account) account.value = getDefaultOzonAccountId();
    if (keyword) keyword.value = '';
    ozonState.trafficAccountId = getDefaultOzonAccountId();
    ozonState.trafficKeyword = '';
    ozonState.trafficPage = 1;
    loadOzonTraffic({ allowStale: true, keepStaleOnError: true });
  });
  const trafficAccountFilter = document.getElementById('ozonTrafficAccountFilter');
  if (trafficAccountFilter && trafficAccountFilter.dataset.ozonBound !== '1') {
    trafficAccountFilter.dataset.ozonBound = '1';
    trafficAccountFilter.addEventListener('change', () => {
      ozonState.trafficAccountId = trafficAccountFilter.value || '';
      ozonState.trafficPage = 1;
      updateOzonTabSyncStamps('ozonTraffic');
    });
  }
  const trafficKeywordInput = document.getElementById('ozonTrafficKeywordInput');
  if (trafficKeywordInput && trafficKeywordInput.dataset.ozonBound !== '1') {
    trafficKeywordInput.dataset.ozonBound = '1';
    trafficKeywordInput.addEventListener('keydown', (event) => {
      if (event.key === 'Enter') {
        document.getElementById('ozonSearchTrafficBtn')?.click();
      }
    });
  }
  bindOnce('ozonOpenSellerMarketingBtn', () => window.open('https://seller.ozon.ru/app/promo/actions', '_blank'));
  bindOnce('ozonSearchMarketingBtn', () => withOzonButtonLoading('ozonSearchMarketingBtn', '查询中...', async () => {
    const accountId = getOzonActiveAccountId(document.getElementById('ozonMarketingAccountFilter')?.value || '');
    if (!accountId) {
      setOzonResult('请先选择 OZON 店铺', 'error');
      return;
    }
    resetOzonMarketingAccountView(accountId);
    updateOzonTabSyncStamps('ozonMarketing');
    await loadOzonMarketing({ forceRefresh: true, includeProducts: true });
  }));
  bindOnce('ozonRefreshMarketingBtn', () => withOzonButtonLoading('ozonRefreshMarketingBtn', '同步中...', () => loadOzonMarketing({ forceRefresh: true, sync: true, includeProducts: true })));
  bindOnce('ozonRefreshDiscountTasksBtn', () => withOzonButtonLoading('ozonRefreshDiscountTasksBtn', '读取中...', () => loadOzonDiscountTasks({ forceRefresh: true })));
  bindOnce('ozonMarketingActionModalCloseBtn', () => setOzonMarketingActionModalVisible(false));
  bindOnce('ozonMarketingActionModalCancelBtn', () => setOzonMarketingActionModalVisible(false));
  bindOnce('ozonFreightCalcBtn', () => withOzonButtonLoading('ozonFreightCalcBtn', '计算中...', renderOzonFreightCalculator).catch(err => setOzonResult(err.message || '运费计算失败', 'error')));
  bindOnce('ozonFreightResetBtn', resetOzonFreightCalculator);
  bindOzonFreightAutoCalculate();
  document.querySelectorAll('[data-ozon-marketing-view]').forEach((button) => {
    if (button.dataset.ozonBound === '1') return;
    button.dataset.ozonBound = '1';
    button.addEventListener('click', () => {
      setOzonMarketingView(button.dataset.ozonMarketingView || 'actions');
      if (button.dataset.ozonMarketingView === 'discounts' && !ozonState.discountTasks) loadOzonDiscountTasks();
    });
  });
  const marketingAccountFilter = document.getElementById('ozonMarketingAccountFilter');
  if (marketingAccountFilter && marketingAccountFilter.dataset.ozonBound !== '1') {
    marketingAccountFilter.dataset.ozonBound = '1';
    marketingAccountFilter.addEventListener('change', () => {
      resetOzonMarketingAccountView(marketingAccountFilter.value || '');
      updateOzonTabSyncStamps('ozonMarketing');
      renderOzonMarketing();
    });
  }
  const marketingSection = document.getElementById('ozonMarketing');
  if (marketingSection && marketingSection.dataset.ozonMarketingBound !== '1') {
    marketingSection.dataset.ozonMarketingBound = '1';
    marketingSection.addEventListener('input', (event) => {
      if (event.target?.id === 'ozonMarketingDetailSearchInput') {
        ozonState.marketingProductKeyword = event.target.value || '';
        const key = normalizeOzonText(ozonState.marketingProductKeyword).toLowerCase();
        marketingSection.querySelectorAll('#ozonMarketingProductList [data-ozon-promo-row-text]').forEach(row => {
          row.style.display = !key || String(row.dataset.ozonPromoRowText || '').includes(key) ? '' : 'none';
        });
      }
    });
    marketingSection.addEventListener('change', (event) => {
      const detailCheck = event.target?.closest?.('[data-ozon-detail-check],[data-ozon-detail-check-all]');
      if (handleOzonMarketingDetailCheckInput(detailCheck, marketingSection)) return;
    });
    marketingSection.addEventListener('click', (event) => {
      const detailCheck = event.target?.closest?.('[data-ozon-detail-check],[data-ozon-detail-check-all]');
      if (detailCheck) {
        window.setTimeout(() => handleOzonMarketingDetailCheckInput(detailCheck, marketingSection), 0);
        event.stopPropagation();
        return;
      }
      const detailSelectCell = event.target?.closest?.('[data-ozon-detail-check-row]');
      if (detailSelectCell && !event.target?.closest?.('input,button,a,select,textarea')) {
        if (toggleOzonMarketingDetailRow(detailSelectCell, marketingSection)) {
          event.stopPropagation();
          return;
        }
      }
      const backBtn = event.target?.closest?.('[data-ozon-back-actions]');
      if (backBtn) {
        setOzonMarketingView('actions');
        return;
      }
      const detailSourceBtn = event.target?.closest?.('[data-ozon-detail-source]');
      if (detailSourceBtn) {
        const source = detailSourceBtn.dataset.ozonDetailSource || 'joined';
        ozonState.marketingDetailAutoAddDate = '';
        ozonState.marketingProductPage = 1;
        loadOzonMarketingProducts(ozonState.marketingSelectedAction || {}, source);
        return;
      }
      const detailAutoDateBtn = event.target?.closest?.('[data-ozon-detail-auto-date]');
      if (detailAutoDateBtn) {
        ozonState.marketingDetailAutoAddDate = detailAutoDateBtn.dataset.ozonDetailAutoDate || '';
        ozonState.marketingProductPage = 1;
        loadOzonMarketingProducts(ozonState.marketingSelectedAction || {}, 'auto', {
          keepAutoAddDate: true,
          hydrateDetails: true
        });
        return;
      }
      const refreshProductsBtn = event.target?.closest?.('[data-ozon-refresh-action-products]');
      if (refreshProductsBtn) {
        const source = ozonState.marketingDetailAutoAddDate ? 'auto' : normalizeOzonMarketingProductSource(ozonState.marketingDetailTab || 'joined');
        refreshProductsBtn.disabled = true;
        loadOzonMarketingProducts(ozonState.marketingSelectedAction || {}, source, {
          forceRefresh: true,
          keepAutoAddDate: Boolean(ozonState.marketingDetailAutoAddDate),
          hydrateDetails: true
        }).finally(() => {
          refreshProductsBtn.disabled = false;
        });
        return;
      }
      const deactivateSelectedBtn = event.target?.closest?.('[data-ozon-deactivate-selected-products]');
      if (deactivateSelectedBtn) {
        deactivateSelectedBtn.disabled = true;
        submitOzonDeactivateSelectedProducts().catch(err => {
          setOzonResult(err.message || '批量移出失败', 'error');
        }).finally(() => {
          syncOzonMarketingDetailSelectionUi(marketingSection);
        });
        return;
      }
      const productPageBtn = event.target?.closest?.('[data-ozon-product-page]');
      if (productPageBtn) {
        const page = Number(productPageBtn.dataset.ozonProductPage || 1);
        if (page > 0) {
          productPageBtn.disabled = true;
          const source = ozonState.marketingDetailAutoAddDate ? 'auto' : normalizeOzonMarketingProductSource(ozonState.marketingDetailTab || 'joined');
          loadOzonMarketingProducts(ozonState.marketingSelectedAction || {}, source, {
            page,
            keepAutoAddDate: Boolean(ozonState.marketingDetailAutoAddDate),
            hydrateDetails: source === 'auto'
          }).finally(() => {
            productPageBtn.disabled = false;
          });
        }
        return;
      }
      const sellerActionBtn = event.target?.closest?.('[data-ozon-open-seller-action]');
      if (sellerActionBtn) {
        window.open('https://seller.ozon.ru/app/promo/actions', '_blank');
        return;
      }
      const candidateModalBtn = event.target?.closest?.('[data-ozon-open-candidate-modal]');
      if (candidateModalBtn) {
        event.stopPropagation();
        openOzonMarketingCandidateModal({
          id: candidateModalBtn.dataset.ozonOpenCandidateModal || '',
          accountId: candidateModalBtn.dataset.accountId || '',
          title: candidateModalBtn.dataset.actionTitle || ''
        });
        return;
      }
      const openActionBtn = event.target?.closest?.('[data-ozon-open-action]');
      if (openActionBtn) {
        ozonState.marketingDetailAutoAddDate = '';
        openOzonMarketingDetail({
          id: openActionBtn.dataset.ozonOpenAction || '',
          accountId: openActionBtn.dataset.accountId || '',
          title: openActionBtn.dataset.actionTitle || ''
        }, 'joined');
        return;
      }
      const activateBtn = event.target?.closest?.('[data-ozon-activate-product]');
      if (activateBtn) {
        openOzonActivateProductModal(activateBtn.dataset.ozonActivateProduct || '');
        return;
      }
      const deactivateBtn = event.target?.closest?.('[data-ozon-deactivate-product]');
      if (deactivateBtn) {
        openOzonDeactivateProductModal(deactivateBtn.dataset.ozonDeactivateProduct || '');
        return;
      }
      const approveBtn = event.target?.closest?.('[data-ozon-approve-discount]');
      if (approveBtn) {
        openOzonDiscountTaskModal(approveBtn.dataset.ozonApproveDiscount || '', 'approve');
        return;
      }
      const declineBtn = event.target?.closest?.('[data-ozon-decline-discount]');
      if (declineBtn) {
        openOzonDiscountTaskModal(declineBtn.dataset.ozonDeclineDiscount || '', 'decline');
        return;
      }
    });
  }
  const marketingModal = document.getElementById('ozonMarketingActionModal');
  if (marketingModal && marketingModal.dataset.ozonCandidateBound !== '1') {
    marketingModal.dataset.ozonCandidateBound = '1';
    marketingModal.addEventListener('input', (event) => {
      if (event.target?.id === 'ozonMarketingCandidateSearchInput') {
        ozonState.marketingCandidateKeyword = event.target.value || '';
        const key = normalizeOzonText(ozonState.marketingCandidateKeyword).toLowerCase();
        marketingModal.querySelectorAll('#ozonMarketingCandidateModalList [data-ozon-promo-row-text]').forEach(row => {
          row.style.display = !key || String(row.dataset.ozonPromoRowText || '').includes(key) ? '' : 'none';
        });
        syncOzonMarketingCandidateSelectionUi(marketingModal);
        return;
      }
      const discountInput = event.target?.closest?.('[data-ozon-promo-discount]');
      if (discountInput) {
        const rowId = discountInput.dataset.ozonPromoDiscount || '';
        const basePrice = Number(discountInput.dataset.currentPrice || 0);
        const currencyCode = discountInput.dataset.currencyCode || 'RUB';
        const rawValue = String(discountInput.value || '').trim();
        const product = getOzonMarketingCandidateProductByRowId(rowId);
        if (!rawValue) {
          clearOzonMarketingCandidateEdit(rowId);
          const suggestedActionPrice = product ? getOzonPromoSuggestedActionPrice(product) : 0;
          const priceInput = marketingModal.querySelector(`[data-ozon-promo-action-price="${CSS.escape(rowId)}"]`);
          if (priceInput && document.activeElement !== priceInput) priceInput.value = suggestedActionPrice ? formatOzonPromoInputNumber(suggestedActionPrice) : '';
          const amountEl = marketingModal.querySelector(`[data-ozon-promo-discount-amount="${CSS.escape(rowId)}"]`);
          if (amountEl) amountEl.textContent = getOzonPromoDiscountAmountText(basePrice, suggestedActionPrice, currencyCode);
          return;
        }
        const discount = Math.max(0, Math.min(99.99, Number(rawValue || 0)));
        if (basePrice > 0) {
          const actionPrice = roundOzonPromoNumber(basePrice * (1 - discount / 100), 2);
          setOzonMarketingCandidateEdit(rowId, { discount, actionPrice });
          const priceInput = marketingModal.querySelector(`[data-ozon-promo-action-price="${CSS.escape(rowId)}"]`);
          if (priceInput && document.activeElement !== priceInput) priceInput.value = actionPrice ? formatOzonPromoInputNumber(actionPrice) : '';
          const amountEl = marketingModal.querySelector(`[data-ozon-promo-discount-amount="${CSS.escape(rowId)}"]`);
          if (amountEl) amountEl.textContent = getOzonPromoDiscountAmountText(basePrice, actionPrice, currencyCode);
        }
        return;
      }
      const priceInput = event.target?.closest?.('[data-ozon-promo-action-price]');
      if (priceInput) {
        const rowId = priceInput.dataset.ozonPromoActionPrice || '';
        const basePrice = Number(priceInput.dataset.currentPrice || 0);
        const currencyCode = priceInput.dataset.currencyCode || 'RUB';
        const rawValue = String(priceInput.value || '').trim();
        const product = getOzonMarketingCandidateProductByRowId(rowId);
        if (!rawValue) {
          clearOzonMarketingCandidateEdit(rowId);
          const suggestedDiscount = product ? getOzonPromoSuggestedDiscount(product) : null;
          const discountEl = marketingModal.querySelector(`[data-ozon-promo-discount="${CSS.escape(rowId)}"]`);
          if (discountEl && document.activeElement !== discountEl) discountEl.value = suggestedDiscount === null ? '' : formatOzonPromoInputNumber(suggestedDiscount);
          const amountEl = marketingModal.querySelector(`[data-ozon-promo-discount-amount="${CSS.escape(rowId)}"]`);
          if (amountEl) amountEl.textContent = getOzonPromoDiscountAmountText(basePrice, getOzonPromoSuggestedActionPrice(product || {}), currencyCode);
          return;
        }
        const actionPrice = roundOzonPromoNumber(rawValue || 0, 2);
        const discount = getOzonPromoDiscountFromPrice(basePrice, actionPrice);
        setOzonMarketingCandidateEdit(rowId, { discount, actionPrice });
        const discountEl = marketingModal.querySelector(`[data-ozon-promo-discount="${CSS.escape(rowId)}"]`);
        if (discountEl && document.activeElement !== discountEl) discountEl.value = actionPrice > 0 ? formatOzonPromoInputNumber(discount) : '';
        const amountEl = marketingModal.querySelector(`[data-ozon-promo-discount-amount="${CSS.escape(rowId)}"]`);
        if (amountEl) amountEl.textContent = getOzonPromoDiscountAmountText(basePrice, actionPrice, currencyCode);
      }
    });
    marketingModal.addEventListener('change', (event) => {
      const input = event.target?.closest?.('[data-ozon-candidate-check],[data-ozon-candidate-check-all]');
      if (handleOzonMarketingCandidateCheckInput(input, marketingModal)) return;
    });
    marketingModal.addEventListener('click', (event) => {
      const checkInput = event.target?.closest?.('[data-ozon-candidate-check],[data-ozon-candidate-check-all]');
      if (checkInput) {
        window.setTimeout(() => handleOzonMarketingCandidateCheckInput(checkInput, marketingModal), 0);
        event.stopPropagation();
        return;
      }
      const selectCell = event.target?.closest?.('[data-ozon-check-row]');
      if (selectCell && !event.target?.closest?.('input,button,a,select,textarea')) {
        if (toggleOzonMarketingCandidateRow(selectCell, marketingModal)) {
          event.stopPropagation();
          return;
        }
      }
      const pageBtn = event.target?.closest?.('[data-ozon-candidate-page]');
      if (pageBtn) {
        const page = Number(pageBtn.dataset.ozonCandidatePage || 1);
        if (page > 0) {
          pageBtn.disabled = true;
          loadOzonMarketingCandidateProducts(ozonState.marketingSelectedAction, { page }).finally(() => {
            pageBtn.disabled = false;
          });
        }
        return;
      }
      const bulkDiscountBtn = event.target?.closest?.('[data-ozon-apply-bulk-candidate-discount]');
      if (bulkDiscountBtn) {
        const input = marketingModal.querySelector('[data-ozon-bulk-candidate-discount]');
        const rawValue = String(input?.value || '').trim();
        const resultEl = document.getElementById('ozonMarketingActionModalResult');
        if (!rawValue) {
          if (resultEl) resultEl.textContent = '请先填写要批量应用的促销比例';
          return;
        }
        try {
          const result = applyOzonMarketingCandidateBulkDiscount(rawValue, marketingModal);
          if (resultEl) resultEl.textContent = `已为 ${result.applied} 条勾选链接批量填写促销比例${result.skipped ? `；${result.skipped} 条缺少您的价格未处理` : ''}`;
        } catch (err) {
          if (resultEl) resultEl.textContent = err.message || '批量填写失败';
          setOzonResult(err.message || '批量填写失败', 'error');
        }
        return;
      }
      const submitBtn = event.target?.closest?.('[data-ozon-submit-selected-candidates]');
      if (submitBtn) {
        submitBtn.disabled = true;
        submitOzonCandidateProducts().catch(err => {
          const resultEl = document.getElementById('ozonMarketingActionModalResult');
          if (resultEl) resultEl.textContent = err.message || '添加商品失败';
        }).finally(() => {
          syncOzonMarketingCandidateSelectionUi(marketingModal);
        });
      }
      if (event.target?.closest?.('[data-ozon-close-marketing-action]')) {
        setOzonMarketingActionModalVisible(false);
        return;
      }
      if (event.target?.closest?.('[data-ozon-add-by-template]')) {
        const resultEl = document.getElementById('ozonMarketingActionModalResult');
        if (resultEl) resultEl.textContent = 'OZON 公开 Seller API 当前未提供模板添加接口；请在 OZON 后台使用模板，或在这里使用勾选/添加全部商品。';
      }
    });
  }
  bindOnce('ozonSyncProductsBtn', syncOzonProductsFromSelectedAccount);
  bindOnce('ozonSyncProductsBtn2', syncOzonProductsFromSelectedAccount);
  bindOnce('ozonSyncSelectedProductsBtn', syncSelectedOzonProducts);
  bindOnce('ozonSyncOrdersBtnList', syncOzonOrdersFromSelectedAccount);
  bindOnce('ozonSyncHistoryOrdersBtnList', syncOzonFullHistoryOrdersFromSelectedAccount);
  bindOnce('ozonPullNewOrdersBtn', pullNewOzonOrders);
  bindOnce('ozonHomePullNewOrdersBtn', pullNewOzonOrders);
  bindOnce('ozonExportProductsBtn', exportOzonProducts);
  bindOnce('ozonExportOrdersBtn', exportOzonOrders);
  bindOnce('ozonExportOrderAnalysisBtn', exportOzonOrderAnalysis);
  bindOnce('ozonExportProductDiagnosisBtn', exportOzonProductDiagnosis);
  bindOnce('ozonExportLinkDiagnosisBtn', exportOzonLinkDiagnosis);
  bindOnce('ozonBulkExportOrdersBtn', exportOzonOrders);
  bindOnce('ozonBulkCopyProductIdsBtn', copySelectedOzonProductIds);
  bindOnce('ozonBulkOpenSellerProductsBtn', () => window.open('https://seller.ozon.ru/app/products', '_blank'));
  bindOnce('ozonBulkArchiveProductsBtn', archiveSelectedOzonProducts);
  bindOnce('ozonBulkProductPriceBtn', openOzonProductBatchPriceModal);
  bindOnce('ozonBulkProductStockBtn', () => openOzonProductBatchStockModal());
  bindOnce('ozonBulkProductPromoBtn', () => showOzonProductBatchNeedsForm('批量促销'));
  bindOnce('ozonBulkProductRepairImagesBtn', () => showOzonProductBatchNeedsForm('修复图片'));
  bindOnce('ozonBulkProductRepairCopyBtn', () => showOzonProductBatchNeedsForm('修复禁止复制'));
  bindOnce('ozonBulkProductMenuBtn', (event) => {
    event.stopPropagation();
    toggleOzonBulkProductMenu();
  });
  const bulkProductMenu = document.getElementById('ozonBulkProductMenu');
  if (bulkProductMenu && bulkProductMenu.dataset.ozonBound !== '1') {
    bulkProductMenu.dataset.ozonBound = '1';
    bulkProductMenu.addEventListener('click', (event) => {
      if (event.target?.closest?.('button')) toggleOzonBulkProductMenu(false);
    });
  }
  if (!window.__OZON_BULK_PRODUCT_MENU_CLOSE_BOUND__) {
    window.__OZON_BULK_PRODUCT_MENU_CLOSE_BOUND__ = true;
    document.addEventListener('click', (event) => {
      const wrap = event.target?.closest?.('.ozon-bulk-menu-wrap');
      if (!wrap) toggleOzonBulkProductMenu(false);
    });
  }
  bindOnce('ozonRefreshOrdersBtn', () => withOzonButtonLoading('ozonRefreshOrdersBtn', '刷新中...', () => loadOzonOrders({ forceRefresh: true })));
  bindOnce('ozonRefreshFinanceBtn', () => {
    ozonState.financeAccountId = getOzonActiveAccountId(document.getElementById('ozonFinanceAccountFilter')?.value || ozonState.financeAccountId || '');
    updateOzonTabSyncStamps('ozonFinance');
    return withOzonButtonLoading('ozonRefreshFinanceBtn', '查询中...', () => loadOzonFinance({ forceRefresh: true, sync: true })).catch(err => setOzonResult(err.message || '查询财务失败', 'error'));
  });
  bindOnce('ozonRefreshMessagesBtn', () => {
    persistCurrentOzonChatReplyDraft();
    setOzonSelectedMessageAccountId();
    ozonState.selectedMessageId = '';
    stopOzonMessageRealtimeSync();
    updateOzonTabSyncStamps('ozonMessages');
    updateOzonMessageControls({ loading: true, forceRender: true });
    return withOzonButtonLoading('ozonRefreshMessagesBtn', '查询中...', () => loadOzonMessages({ forceRefresh: true, forceRender: true, sync: true }))
      .then(() => startOzonMessageRealtimeSync())
      .catch(err => setOzonResult(err.message || '查询消息失败', 'error'));
  });
  bindOnce('ozonRefreshMessagesIconBtn', () => {
    persistCurrentOzonChatReplyDraft();
    stopOzonMessageRealtimeSync();
    updateOzonMessageControls({ loading: true });
    return withOzonButtonLoading('ozonRefreshMessagesIconBtn', '↻', () => loadOzonMessages({ forceRefresh: true, forceRender: true, sync: true }))
      .then(() => startOzonMessageRealtimeSync())
      .catch(err => setOzonResult(err.message || '刷新消息失败', 'error'));
  });
  const toggleMessageNotificationDrawer = () => {
    ozonState.notificationDrawerOpen = !ozonState.notificationDrawerOpen;
    renderOzonMessages();
    if (ozonState.notificationDrawerOpen) {
      loadOzonNotifications({ forceRefresh: true }).catch(err => setOzonResult(err.message || '通知记录读取失败', 'error'));
    }
  };
  bindOnce('ozonOpenNotificationDrawerBtn', toggleMessageNotificationDrawer);
  bindOnce('ozonCloseNotificationDrawerBtn', () => {
    ozonState.notificationDrawerOpen = false;
    renderOzonMessages();
  });
  const chatFileInput = document.getElementById('ozonChatFileInput');
  if (chatFileInput && chatFileInput.dataset.ozonBound !== '1') {
    chatFileInput.dataset.ozonBound = '1';
    chatFileInput.addEventListener('change', () => {
      const file = chatFileInput.files?.[0] || null;
      chatFileInput.value = '';
      if (!file) return;
      const current = getSelectedOzonMessage();
      if (!current) {
        setOzonResult('请先选择一个 OZON 会话，再发送附件', 'error');
        return;
      }
      sendOzonChatFile(current, file).catch(err => setOzonResult(err.message || '附件发送失败', 'error'));
    });
  }
  bindOnce('ozonCopyNotificationUrlBtn', async () => {
    const text = document.getElementById('ozonNotificationUrl')?.textContent?.trim() || '';
    if (!text) return;
    const ok = await copyText(text);
    showToast(ok ? '通知 URL 已复制' : '复制失败，请手动复制', ok ? 'success' : 'error');
  });
  bindOnce('ozonProductPrevPageBtn', () => {
    ozonState.productPage = Math.max(1, (ozonState.productPage || 1) - 1);
    loadOzonProducts();
  });
  bindOnce('ozonProductNextPageBtn', () => {
    ozonState.productPage = (ozonState.productPage || 1) + 1;
    loadOzonProducts();
  });
  bindOnce('ozonOrderPrevPageBtn', () => {
    ozonState.orderPage = Math.max(1, (ozonState.orderPage || 1) - 1);
    loadOzonOrders();
  });
  bindOnce('ozonOrderNextPageBtn', () => {
    ozonState.orderPage = (ozonState.orderPage || 1) + 1;
    loadOzonOrders();
  });
  bindOnce('ozonSearchProductsBtn', () => {
    applyOzonProductListFiltersFromControls();
    loadOzonProducts({ forceRefresh: true })
      .catch(err => setOzonResult(err.message || '商品查询失败', 'error'));
  });
  bindOnce('ozonSearchDiagnosisBtn', () => {
    ozonState.productDiagnosisAccountId = getOzonActiveAccountId(document.getElementById('ozonProductDiagnosisAccountFilter')?.value || ozonState.productDiagnosisAccountId || '');
    ozonState.productVisibility = normalizeOzonVisibilityForUi(document.getElementById('ozonProductVisibilityFilter')?.value || ozonState.productVisibility || 'IN_SALE') || 'IN_SALE';
    ozonState.productDiagnosisPage = 1;
    updateOzonTabSyncStamps('ozonProducts');
    loadOzonProductDiagnosis({ enrichAi: false, resetAiQueue: false });
  });
  bindOnce('ozonRefreshOrderAnalysisBtn', () => withOzonButtonLoading('ozonRefreshOrderAnalysisBtn', '刷新中...', refreshOzonOrderAnalysis).catch(err => setOzonResult(err.message || '刷新订单分析失败', 'error')));
  bindOnce('ozonSearchOrderAnalysisBtn', () => {
    ozonState.orderAnalysisAccountId = getOzonScopedAccountId(document.getElementById('ozonOrderAnalysisAccountFilter')?.value ?? ozonState.orderAnalysisAccountId ?? '', { allowAll: true });
    ozonState.orderAnalysisPage = 1;
    updateOzonTabSyncStamps('ozonOrders');
    loadOzonOrderAnalysis({ keepStaleOnError: false });
  });
  bindOnce('ozonClearOrderAnalysisBtn', () => {
    const account = document.getElementById('ozonOrderAnalysisAccountFilter');
    const sku = document.getElementById('ozonOrderAnalysisSkuInput');
    if (account) account.value = '';
    const view = document.getElementById('ozonOrderAnalysisViewFilter');
    if (view) view.value = 'all';
    if (sku) sku.value = '';
    ozonState.orderAnalysisAccountId = '';
    ozonState.orderAnalysisSku = '';
    ozonState.orderAnalysisView = 'all';
    ozonState.orderAnalysisPage = 1;
    loadOzonOrderAnalysis({ keepStaleOnError: false });
  });
  bindOnce('ozonClearDiagnosisBtn', () => {
    const account = document.getElementById('ozonProductDiagnosisAccountFilter');
    const sku = document.getElementById('ozonProductDiagnosisSkuInput');
    const risk = document.getElementById('ozonProductRiskFilter');
    const keyword = document.getElementById('ozonProductDiagnosisKeywordInput');
    if (account) account.value = getDefaultOzonAccountId();
    if (sku) sku.value = '';
    if (risk) risk.value = '';
    if (keyword) keyword.value = '';
    const visibility = document.getElementById('ozonProductVisibilityFilter');
    if (visibility) visibility.value = 'IN_SALE';
    ozonState.productVisibility = 'IN_SALE';
    ozonState.productDiagnosisAccountId = getDefaultOzonAccountId();
    ozonState.productDiagnosisSku = '';
    ozonState.productDiagnosisRisk = '';
    ozonState.productDiagnosisKeyword = '';
    ozonState.productDiagnosisPage = 1;
    loadOzonProductDiagnosis({ enrichAi: false, resetAiQueue: false });
  });
  bindOnce('ozonClearProductsBtn', () => {
    const account = document.getElementById('ozonProductAccountFilter');
    const visibility = document.getElementById('ozonProductListVisibilityFilter');
    const keyword = document.getElementById('ozonProductKeywordInput');
    const name = document.getElementById('ozonProductNameInput');
    const sku = document.getElementById('ozonProductSkuInput');
    if (account) account.value = getDefaultOzonAccountId();
    if (visibility) visibility.value = 'ALL';
    if (keyword) keyword.value = '';
    if (name) name.value = '';
    if (sku) sku.value = '';
    ozonState.productAccountId = getDefaultOzonAccountId();
    ozonState.productAccountTouched = true;
    ozonState.productListVisibility = 'ALL';
    ozonState.productKeyword = '';
    ozonState.productName = '';
    ozonState.productSku = '';
    clearOzonProductSelection();
    ozonState.productPage = 1;
    updateOzonTabSyncStamps('ozonProductOnline');
    loadOzonProducts({ forceRefresh: true })
      .catch(err => setOzonResult(err.message || '商品重置失败', 'error'));
  });
  bindOnce('ozonSearchOrdersBtn', () => {
    ozonState.orderAccountId = getOzonScopedAccountId(document.getElementById('ozonOrderAccountFilter')?.value ?? ozonState.orderAccountId ?? '', { allowAll: true });
    const rawDateFrom = normalizeOzonDateInputValue(document.getElementById('ozonOrderDateFromInput')?.value || '');
    const rawDateTo = normalizeOzonDateInputValue(document.getElementById('ozonOrderDateToInput')?.value || '');
    if (!isOzonValidDateInput(rawDateFrom) || !isOzonValidDateInput(rawDateTo)) {
      setOzonResult('订单日期格式必须为 YYYY-MM-DD，例如 2026-06-19', 'error');
      return;
    }
    syncOzonOrderDateState();
    if (ozonState.orderDateFrom && ozonState.orderDateTo && compareOzonDateKeys(ozonState.orderDateFrom, ozonState.orderDateTo) > 0) {
      setOzonResult('订单开始日期不能晚于结束日期', 'error');
      return;
    }
    ozonState.orderPage = 1;
    updateOzonTabSyncStamps('ozonOrderList');
    loadOzonOrders({ forceRefresh: true }).catch(err => setOzonResult(err.message || '订单查询失败', 'error'));
  });
  bindOnce('ozonClearOrdersBtn', () => {
    const account = document.getElementById('ozonOrderAccountFilter');
    const status = document.getElementById('ozonOrderStatusFilter');
    const dateFrom = document.getElementById('ozonOrderDateFromInput');
    const dateTo = document.getElementById('ozonOrderDateToInput');
    const datePreset = document.getElementById('ozonOrderDatePresetSelect');
    const keyword = document.getElementById('ozonOrderKeywordInput');
    if (account) account.value = '';
    if (status) status.value = '';
    if (dateFrom) dateFrom.value = '';
    if (dateTo) dateTo.value = '';
    if (datePreset) datePreset.value = '';
    if (keyword) keyword.value = '';
    ozonState.orderAccountId = '';
    ozonState.orderAccountTouched = true;
    ozonState.orderStatus = 'all';
    ozonState.orderDateFrom = '';
    ozonState.orderDateTo = '';
    ozonState.orderKeyword = '';
    ozonState.orderPage = 1;
    loadOzonOrders({ forceRefresh: true }).catch(err => setOzonResult(err.message || '订单重置失败', 'error'));
  });
  bindOnce('ozonSearchCollectionBtn', () => loadOzonCollectionLinks());
  bindOnce('ozonClearCollectionFilterBtn', () => {
    const status = document.getElementById('ozonCollectionStatusFilter');
    const keyword = document.getElementById('ozonCollectionKeywordInput');
    if (status) status.value = '';
    if (keyword) keyword.value = '';
    ozonState.collectionStatus = '';
    ozonState.collectionKeyword = '';
    ozonState.selectedCollectionIds.clear();
    loadOzonCollectionLinks();
  });
  bindOnce('ozonBulkSelectedBtn', () => updateOzonCollectionStatus(getSelectedOzonCollectionIds(), 'selected'));
  bindOnce('ozonBulkReadyBtn', () => updateOzonCollectionStatus(getSelectedOzonCollectionIds(), 'ready'));
  bindOnce('ozonBulkListedBtn', () => updateOzonCollectionStatus(getSelectedOzonCollectionIds(), 'listed'));
  bindOnce('ozonBulkArchiveBtn', () => updateOzonCollectionStatus(getSelectedOzonCollectionIds(), 'archived'));
  const statusFilter = document.getElementById('ozonCollectionStatusFilter');
  if (statusFilter && statusFilter.dataset.ozonBound !== '1') {
    statusFilter.dataset.ozonBound = '1';
    statusFilter.addEventListener('change', () => {
      ozonState.collectionStatus = statusFilter.value || '';
    });
  }
  const productVisibilityFilter = document.getElementById('ozonProductVisibilityFilter');
  if (productVisibilityFilter && productVisibilityFilter.dataset.ozonBound !== '1') {
    productVisibilityFilter.dataset.ozonBound = '1';
    productVisibilityFilter.addEventListener('change', () => {
      ozonState.productVisibility = normalizeOzonVisibilityForUi(productVisibilityFilter.value || 'IN_SALE') || 'IN_SALE';
      ozonState.productDiagnosisPage = 1;
      updateOzonProductStatusTabs();
      loadOzonProductDiagnosis({ forceRefresh: true, enrichAi: false, resetAiQueue: false })
        .catch(err => setOzonResult(err.message || '商品状态查询失败', 'error'));
    });
  }
  const productListVisibilityFilter = document.getElementById('ozonProductListVisibilityFilter');
  if (productListVisibilityFilter && productListVisibilityFilter.dataset.ozonBound !== '1') {
    productListVisibilityFilter.dataset.ozonBound = '1';
    productListVisibilityFilter.addEventListener('change', () => {
      ozonState.productListVisibility = normalizeOzonVisibilityForUi(productListVisibilityFilter.value || 'ALL') || 'ALL';
      clearOzonProductSelection();
      ozonState.productPage = 1;
      updateOzonProductListStatusTabs();
      loadOzonProducts({ forceRefresh: true })
        .catch(err => setOzonResult(err.message || '商品状态查询失败', 'error'));
    });
  }
  const productAccountFilter = document.getElementById('ozonProductAccountFilter');
  if (productAccountFilter && productAccountFilter.dataset.ozonBound !== '1') {
    productAccountFilter.dataset.ozonBound = '1';
    productAccountFilter.addEventListener('change', () => {
      ozonState.productAccountId = getOzonActiveAccountId(productAccountFilter.value || '');
      ozonState.productAccountTouched = true;
      clearOzonProductSelection();
      ozonState.productPage = 1;
      updateOzonTabSyncStamps('ozonProductOnline');
      setOzonResult('已切换商品店铺，请点击“查询”读取该店铺服务器缓存；如无缓存，请点击“同步所有商品”。', 'info');
    });
  }
  const productDiagnosisAccountFilter = document.getElementById('ozonProductDiagnosisAccountFilter');
  if (productDiagnosisAccountFilter && productDiagnosisAccountFilter.dataset.ozonBound !== '1') {
    productDiagnosisAccountFilter.dataset.ozonBound = '1';
    productDiagnosisAccountFilter.addEventListener('change', () => {
      ozonState.productDiagnosisAccountId = getOzonActiveAccountId(productDiagnosisAccountFilter.value || '');
      ozonState.productDiagnosisPage = 1;
      updateOzonTabSyncStamps('ozonProducts');
    });
  }
  document.querySelectorAll('[data-ozon-product-tab]').forEach(button => {
    if (button.dataset.ozonBound === '1') return;
    button.dataset.ozonBound = '1';
    button.addEventListener('click', () => {
      const visibility = button.dataset.ozonProductTab || 'IN_SALE';
      ozonState.productVisibility = visibility;
      ozonState.productDiagnosisPage = 1;
      const hidden = document.getElementById('ozonProductVisibilityFilter');
      if (hidden) hidden.value = visibility;
      updateOzonProductStatusTabs();
      loadOzonProductDiagnosis({ forceRefresh: true, enrichAi: false, resetAiQueue: false })
        .catch(err => setOzonResult(err.message || '商品状态查询失败', 'error'));
    });
  });
  document.querySelectorAll('[data-ozon-product-list-tab]').forEach(button => {
    if (button.dataset.ozonBound === '1') return;
    button.dataset.ozonBound = '1';
    button.addEventListener('click', () => {
      const visibility = button.dataset.ozonProductListTab || 'ALL';
      ozonState.productListVisibility = visibility;
      clearOzonProductSelection();
      ozonState.productPage = 1;
      const hidden = document.getElementById('ozonProductListVisibilityFilter');
      if (hidden) hidden.value = visibility;
      updateOzonProductListStatusTabs();
      loadOzonProducts({ forceRefresh: true })
        .catch(err => setOzonResult(err.message || '商品状态查询失败', 'error'));
    });
  });
  const financeAccountFilter = document.getElementById('ozonFinanceAccountFilter');
  if (financeAccountFilter && financeAccountFilter.dataset.ozonBound !== '1') {
    financeAccountFilter.dataset.ozonBound = '1';
    financeAccountFilter.addEventListener('change', () => {
      ozonState.financeAccountId = getOzonActiveAccountId(financeAccountFilter.value || '');
      updateOzonTabSyncStamps('ozonFinance');
    });
  }
  document.querySelectorAll('[data-ozon-message-tab]').forEach(button => {
    if (button.dataset.ozonBound === '1') return;
    button.dataset.ozonBound = '1';
    button.addEventListener('click', () => {
      persistCurrentOzonChatReplyDraft();
      ozonState.messageType = normalizeOzonMessageCategory(button.dataset.ozonMessageTab || '');
      ozonState.messageQuickFilter = '';
      ozonState.notificationDrawerOpen = false;
      ozonState.selectedMessageId = '';
      stopOzonMessageRealtimeSync();
      updateOzonMessageControls({ loading: true });
      loadOzonMessages({ forceRefresh: true, forceRender: true, sync: false })
        .then(() => startOzonMessageRealtimeSync())
        .catch(err => setOzonResult(err.message || '消息读取失败', 'error'));
    });
  });
  document.querySelectorAll('[data-ozon-message-quick-filter]').forEach(button => {
    if (button.dataset.ozonBound === '1') return;
    button.dataset.ozonBound = '1';
    button.addEventListener('click', () => {
      persistCurrentOzonChatReplyDraft();
      ozonState.messageQuickFilter = button.dataset.ozonMessageQuickFilter || '';
      ozonState.selectedMessageId = '';
      stopOzonMessageRealtimeSync();
      updateOzonMessageControls({ loading: true });
      loadOzonMessages({ forceRefresh: true, forceRender: true, sync: false })
        .then(() => startOzonMessageRealtimeSync())
        .catch(err => setOzonResult(err.message || '消息读取失败', 'error'));
    });
  });
  const messageSearchInput = document.getElementById('ozonMessageSearchInput');
  if (messageSearchInput && messageSearchInput.dataset.ozonBound !== '1') {
    messageSearchInput.dataset.ozonBound = '1';
    let messageSearchTimer = null;
    messageSearchInput.addEventListener('input', () => {
      window.clearTimeout(messageSearchTimer);
      messageSearchTimer = window.setTimeout(() => {
        persistCurrentOzonChatReplyDraft();
        ozonState.messageSearch = messageSearchInput.value || '';
        ozonState.selectedMessageId = '';
        renderOzonMessages({ forceRender: true });
      }, 160);
    });
    messageSearchInput.addEventListener('keydown', (event) => {
      if (event.key === 'Enter') {
        window.clearTimeout(messageSearchTimer);
        persistCurrentOzonChatReplyDraft();
        ozonState.messageSearch = messageSearchInput.value || '';
        ozonState.selectedMessageId = '';
        renderOzonMessages({ forceRender: true });
      }
    });
  }
  const productKeywordInput = document.getElementById('ozonProductKeywordInput');
  if (productKeywordInput && productKeywordInput.dataset.ozonBound !== '1') {
    productKeywordInput.dataset.ozonBound = '1';
    productKeywordInput.addEventListener('keydown', (event) => {
      if (event.key === 'Enter') {
        document.getElementById('ozonSearchProductsBtn')?.click();
      }
    });
  }
  ['ozonProductNameInput', 'ozonProductSkuInput'].forEach((id) => {
    const input = document.getElementById(id);
    if (input && input.dataset.ozonBound !== '1') {
      input.dataset.ozonBound = '1';
      input.addEventListener('keydown', (event) => {
        if (event.key === 'Enter') {
          document.getElementById('ozonSearchProductsBtn')?.click();
        }
      });
    }
  });
  const orderStatusFilter = document.getElementById('ozonOrderStatusFilter');
  if (orderStatusFilter && orderStatusFilter.dataset.ozonBound !== '1') {
    orderStatusFilter.dataset.ozonBound = '1';
    orderStatusFilter.addEventListener('change', () => {
      ozonState.orderStatus = normalizeOzonOrderBucket(orderStatusFilter.value || 'all');
      ozonState.orderPage = 1;
      updateOzonOrderStatusTabs();
      loadOzonOrders({ forceRefresh: true }).catch(err => setOzonResult(err.message || '订单状态查询失败', 'error'));
    });
  }
  const orderAccountFilter = document.getElementById('ozonOrderAccountFilter');
  if (orderAccountFilter && orderAccountFilter.dataset.ozonBound !== '1') {
    orderAccountFilter.dataset.ozonBound = '1';
    orderAccountFilter.addEventListener('change', () => {
      ozonState.orderAccountId = getOzonScopedAccountId(orderAccountFilter.value || '', { allowAll: true });
      ozonState.orderAccountTouched = true;
      ozonState.orderPage = 1;
      updateOzonTabSyncStamps('ozonOrders');
    });
  }
  const orderPageSizeSelect = document.getElementById('ozonOrderPageSizeSelect');
  if (orderPageSizeSelect) {
    const allowedOrderLimits = [20, 50, 100, 200];
    const currentLimit = allowedOrderLimits.includes(Number(ozonState.orderLimit)) ? Number(ozonState.orderLimit) : 50;
    orderPageSizeSelect.value = String(currentLimit);
    if (orderPageSizeSelect.dataset.ozonBound !== '1') {
      orderPageSizeSelect.dataset.ozonBound = '1';
      orderPageSizeSelect.addEventListener('change', () => {
        const nextLimit = allowedOrderLimits.includes(Number(orderPageSizeSelect.value)) ? Number(orderPageSizeSelect.value) : 50;
        ozonState.orderLimit = nextLimit;
        ozonState.orderPage = 1;
        loadOzonOrders({ forceRefresh: true }).catch(err => setOzonResult(err.message || '订单分页读取失败', 'error'));
      });
    }
  }
  const orderAnalysisAccountFilter = document.getElementById('ozonOrderAnalysisAccountFilter');
  if (orderAnalysisAccountFilter && orderAnalysisAccountFilter.dataset.ozonBound !== '1') {
    orderAnalysisAccountFilter.dataset.ozonBound = '1';
    orderAnalysisAccountFilter.addEventListener('change', () => {
      ozonState.orderAnalysisAccountId = getOzonScopedAccountId(orderAnalysisAccountFilter.value || '', { allowAll: true });
      ozonState.orderAnalysisPage = 1;
      updateOzonTabSyncStamps('ozonOrders');
    });
  }
  const orderAnalysisViewFilter = document.getElementById('ozonOrderAnalysisViewFilter');
  if (orderAnalysisViewFilter && orderAnalysisViewFilter.dataset.ozonBound !== '1') {
    orderAnalysisViewFilter.dataset.ozonBound = '1';
    orderAnalysisViewFilter.addEventListener('change', () => {
      ozonState.orderAnalysisView = orderAnalysisViewFilter.value || 'all';
      ozonState.orderAnalysisPage = 1;
    });
  }
  if (!window.__OZON_HOME_CHART_RESIZE_BOUND__) {
    window.__OZON_HOME_CHART_RESIZE_BOUND__ = true;
    let ozonHomeChartResizeTimer = null;
    window.addEventListener('resize', () => {
      window.clearTimeout(ozonHomeChartResizeTimer);
      ozonHomeChartResizeTimer = window.setTimeout(() => {
        if (!document.getElementById('ozonHomeDailySalesBarChart')) return;
        const rows = Array.isArray(ozonState.overview?.orderDaily) ? ozonState.overview.orderDaily : [];
        if (rows.length) renderOzonHomeSalesChart(rows);
      }, 120);
    });
  }
  document.querySelectorAll('[data-ozon-order-tab]').forEach(button => {
    if (button.dataset.ozonBound === '1') return;
    button.dataset.ozonBound = '1';
    button.addEventListener('click', () => {
      const status = normalizeOzonOrderBucket(button.dataset.ozonOrderTab || 'all');
      ozonState.orderStatus = status;
      ozonState.orderPage = 1;
      const hidden = document.getElementById('ozonOrderStatusFilter');
      if (hidden) hidden.value = status;
      updateOzonOrderStatusTabs();
      loadOzonOrders({ forceRefresh: true }).catch(err => setOzonResult(err.message || '订单状态查询失败', 'error'));
    });
  });
  ['ozonOrderDateFromInput', 'ozonOrderDateToInput'].forEach(bindOzonOrderDateInput);
  const orderDatePresetSelect = document.getElementById('ozonOrderDatePresetSelect');
  if (orderDatePresetSelect && orderDatePresetSelect.dataset.ozonBound !== '1') {
    orderDatePresetSelect.dataset.ozonBound = '1';
    orderDatePresetSelect.addEventListener('change', () => {
      applyOzonOrderDatePreset(orderDatePresetSelect.value || '');
    });
  }
  const keywordInput = document.getElementById('ozonCollectionKeywordInput');
  if (keywordInput && keywordInput.dataset.ozonBound !== '1') {
    keywordInput.dataset.ozonBound = '1';
    keywordInput.addEventListener('keydown', (event) => {
      if (event.key === 'Enter') document.getElementById('ozonSearchCollectionBtn')?.click();
    });
  }
  const orderKeywordInput = document.getElementById('ozonOrderKeywordInput');
  if (orderKeywordInput && orderKeywordInput.dataset.ozonBound !== '1') {
    orderKeywordInput.dataset.ozonBound = '1';
    orderKeywordInput.addEventListener('keydown', (event) => {
      if (event.key === 'Enter') {
        document.getElementById('ozonSearchOrdersBtn')?.click();
      }
    });
  }
  ['ozonOrderAnalysisSkuInput', 'ozonProductDiagnosisSkuInput', 'ozonProductDiagnosisKeywordInput'].forEach((id) => {
    const input = document.getElementById(id);
    if (input && input.dataset.ozonBound !== '1') {
      input.dataset.ozonBound = '1';
      input.addEventListener('keydown', (event) => {
        if (event.key !== 'Enter') return;
        if (id === 'ozonOrderAnalysisSkuInput') {
          document.getElementById('ozonSearchOrderAnalysisBtn')?.click();
        } else {
          document.getElementById('ozonSearchDiagnosisBtn')?.click();
        }
      });
    }
  });
}

function setAuthMode(mode = 'login') {
  const normalizedMode = mode === 'register' || mode === 'reset' ? mode : 'login';
  const loginPanel = document.getElementById('authLoginPanel');
  const registerPanel = document.getElementById('authRegisterPanel');
  const resetPanel = document.getElementById('authResetPanel');
  const loginBtn = document.getElementById('authModeLoginBtn');
  const registerBtn = document.getElementById('authModeRegisterBtn');
  removeOzonAuthHeaderArtifacts();
  if (loginPanel) loginPanel.style.display = normalizedMode === 'login' ? 'grid' : 'none';
  if (registerPanel) registerPanel.style.display = normalizedMode === 'register' ? 'grid' : 'none';
  if (resetPanel) resetPanel.style.display = normalizedMode === 'reset' ? 'grid' : 'none';
  syncAuthPlatformVisibility(normalizedMode);
  if (loginBtn) loginBtn.classList.toggle('active', normalizedMode === 'login' || normalizedMode === 'reset');
  if (registerBtn) registerBtn.classList.toggle('active', normalizedMode === 'register');
  if (normalizedMode === 'register') refreshAuthCaptcha();
}

function removeOzonAuthHeaderArtifacts() {
  if (!isOzonWorkspace()) return;
  document.querySelectorAll('#authPanelTitle, #loginAuthStatus, #authGate .auth-card-head, #authGate .auth-card > h2').forEach(el => el.remove());
}


function refreshAuthCaptcha() {
  const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
  authCaptchaValue = Array.from({ length: 4 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
  const btn = document.getElementById('authCaptchaRefreshBtn');
  if (btn) btn.textContent = authCaptchaValue;
}

function renderDashAuthStatus(token, user) {
  renderTopbarAccount(token ? user : null);
  const statusEls = [
    document.getElementById('loginAuthStatus'),
    document.getElementById('dashAuthStatus')
  ].filter(Boolean);
  if (!statusEls.length) return;
  if (!token) {
    statusEls.forEach(el => { el.textContent = '未登录'; });
    syncDingHuiTemuHome();
    return;
  }
  const phone = user?.phone ? `：${user.phone}` : '';
  statusEls.forEach(el => { el.textContent = `已登录${phone}`; });
  syncDingHuiTemuHome();
}

function startDashAuthCooldown(buttonId = 'authSendResetCodeBtn', seconds = 60) {
  const btn = document.getElementById(buttonId);
  if (!btn) return;
  let remaining = seconds;
  btn.disabled = true;
  btn.textContent = `${remaining}s后重试`;
  if (dashAuthCooldownTimer) clearInterval(dashAuthCooldownTimer);
  dashAuthCooldownTimer = setInterval(() => {
    remaining -= 1;
    if (remaining <= 0) {
      clearInterval(dashAuthCooldownTimer);
      dashAuthCooldownTimer = null;
      btn.disabled = false;
      btn.textContent = '发送验证码';
      return;
    }
    btn.textContent = `${remaining}s后重试`;
  }, 1000);
}

async function submitPasswordLogin() {
  const phone = document.getElementById('authLoginPhone')?.value?.trim();
  const password = document.getElementById('authLoginPassword')?.value || '';
  if (!(/^(?:admin|1\d{10})$/.test(phone || ''))) {
    setDashAuthResult('请输入正确的手机号或管理员账号', 'error');
    return;
  }
  if (!password) {
    setDashAuthResult('请输入密码', 'error');
    return;
  }
  const resultEl = document.getElementById('loginAuthResult') || document.getElementById('dashAuthResult');
  if (resultEl) {
    resultEl.style.display = 'block';
    resultEl.textContent = '登录中...';
    resultEl.style.color = '#666';
  }
  const loginBtn = document.getElementById('dashPasswordLoginBtn');
  if (loginBtn) {
    loginBtn.disabled = true;
    loginBtn.textContent = '登录中...';
  }
  const pendingUser = { phone, nickname: '同步中...' };
  renderDashAuthStatus('pending', pendingUser);
  setDashboardAuthPanelVisible(false);
  setDashboardLockedState(false);
  renderPendingDashboardLogin(pendingUser);
  setFeatureAccessState(true, '正在登录并同步店铺授权');
  setDashAuthResult('正在登录，页面已先为你打开...', 'info');
  const paintPromise = waitForDashboardPaint();
  try {
    clearServerAuthState({ localOnly: true }).catch(err => {
      console.warn('[小青蛙电商助手] 登录前本地清理失败:', err);
    });
    const loginPromise = postJsonWithFallback('/user/login', { phone, password }, true);
    await paintPromise;
    const loginResult = await loginPromise;
    if (!loginResult.ok || !loginResult.result?.token) {
      setDashboardLockedState(true);
      setDashboardAuthPanelVisible(true);
      setDashAuthResult(loginResult.error || loginResult.result?.error || '登录失败', 'error');
      return;
    }
    SERVER_URL = normalizeRuntimeApiBase(loginResult.base);
    localStorage.setItem('temuApiBase', SERVER_URL);
    localStorage.setItem(AUTH_PLATFORM_KEY, getSelectedAuthPlatform());
    await setServerAuthState({
      token: loginResult.result.token,
      user: loginResult.result.user || { phone }
    });
    enterDashboardOptimistically(loginResult.result.token, loginResult.result.user || { phone });
  } catch (err) {
    console.error('[小青蛙电商助手] 密码登录失败:', err);
    setDashboardLockedState(true);
    setDashboardAuthPanelVisible(true);
    setDashAuthResult('登录失败，请检查后端服务', 'error');
  } finally {
    const latestBtn = document.getElementById('dashPasswordLoginBtn');
    if (latestBtn) {
      latestBtn.disabled = false;
      latestBtn.textContent = '登录';
    }
  }
}

async function sendResetPasswordCode() {
  await sendSmsCode('reset');
}

async function submitResetPassword() {
  const phone = document.getElementById('authResetPhone')?.value?.trim();
  const code = document.getElementById('authResetCode')?.value?.trim();
  const password = document.getElementById('authResetPassword')?.value || '';
  if (!/^1\d{10}$/.test(phone || '')) return setDashAuthResult('请输入正确的 11 位手机号', 'error');
  if (!code) return setDashAuthResult('请输入短信验证码', 'error');
  if (password.length < 6) return setDashAuthResult('新密码至少 6 位', 'error');
  const result = await postJsonWithFallback('/auth/reset-password', { phone, code, password }, true);
  if (!result.ok) return setDashAuthResult(result.error || result.result?.error || '重置密码失败', 'error');
  setDashAuthResult(result.result?.message || '密码已重置，请使用新密码登录', 'success');
  setAuthMode('login');
}

async function sendSmsCode(purpose = 'login') {
  const phone = purpose === 'reset'
    ? document.getElementById('authResetPhone')?.value?.trim()
    : purpose === 'register'
      ? document.getElementById('authRegisterPhone')?.value?.trim()
      : document.getElementById('authCodePhone')?.value?.trim();
  if (!/^1\d{10}$/.test(phone || '')) return setDashAuthResult('请输入正确的 11 位手机号', 'error');
  if (purpose === 'register') {
    const captcha = document.getElementById('authCaptchaInput')?.value?.trim().toUpperCase();
    if (!captcha || captcha !== authCaptchaValue) {
      refreshAuthCaptcha();
      return setDashAuthResult('图形验证码不正确，请重新输入', 'error');
    }
  }
  const buttonId = purpose === 'reset'
    ? 'authSendResetCodeBtn'
    : purpose === 'register'
      ? 'authSendRegisterCodeBtn'
      : 'authSendLoginCodeBtn';
  const result = await postJsonWithFallback('/auth/send-code', { phone, purpose }, true);
  if (!result.ok) return setDashAuthResult(result.error || result.result?.error || '验证码发送失败', 'error');
  setDashAuthResult(result.result?.message || '验证码已发送', 'success');
  startDashAuthCooldown(buttonId, 60);
}

async function submitCodeLogin() {
  const phone = document.getElementById('authCodePhone')?.value?.trim();
  const code = document.getElementById('authLoginCode')?.value?.trim();
  if (!/^1\d{10}$/.test(phone || '')) return setDashAuthResult('请输入正确的 11 位手机号', 'error');
  if (!code) return setDashAuthResult('请输入短信验证码', 'error');
  const loginResult = await postJsonWithFallback('/auth/login', { phone, code }, true);
  if (!loginResult.ok || !loginResult.result?.token) {
    setDashboardLockedState(true);
    setDashboardAuthPanelVisible(true);
    return setDashAuthResult(loginResult.error || loginResult.result?.error || '验证码登录失败', 'error');
  }
  SERVER_URL = normalizeRuntimeApiBase(loginResult.base);
  localStorage.setItem('temuApiBase', SERVER_URL);
  await setServerAuthState({
    token: loginResult.result.token,
    user: loginResult.result.user || { phone }
  });
  enterDashboardOptimistically(loginResult.result.token, loginResult.result.user || { phone });
}

async function submitRegister() {
  const phone = document.getElementById('authRegisterPhone')?.value?.trim();
  const operatorName = document.getElementById('authRegisterOperator')?.value?.trim();
  const captcha = document.getElementById('authCaptchaInput')?.value?.trim().toUpperCase();
  const code = document.getElementById('authRegisterCode')?.value?.trim();
  const password = document.getElementById('authRegisterPassword')?.value || '';
  const passwordConfirm = document.getElementById('authRegisterPasswordConfirm')?.value || '';
  if (!/^1\d{10}$/.test(phone || '')) return setDashAuthResult('请输入正确的 11 位手机号', 'error');
  if (!/^(?=.*[\u4e00-\u9fa5])[\u4e00-\u9fa5\d]{2,12}$/.test(operatorName || '')) {
    return setDashAuthResult('运营人员必填，只能填写中文或中文加数字', 'error');
  }
  if (!captcha || captcha !== authCaptchaValue) {
    refreshAuthCaptcha();
    return setDashAuthResult('图形验证码不正确，请重新输入', 'error');
  }
  if (!code) return setDashAuthResult('请输入短信验证码', 'error');
  if (password.length < 6) return setDashAuthResult('密码至少 6 位', 'error');
  if (password !== passwordConfirm) return setDashAuthResult('两次输入的密码不一致', 'error');

  const registerBtn = document.getElementById('dashRegisterBtn');
  if (registerBtn) {
    registerBtn.disabled = true;
    registerBtn.textContent = '注册中...';
  }
  try {
    const registerResult = await postJsonWithFallback('/auth/register', { phone, code, password, operatorName }, true);
    if (!registerResult.ok || !registerResult.result?.token) {
      return setDashAuthResult(registerResult.error || registerResult.result?.error || '注册失败', 'error');
    }
    SERVER_URL = normalizeRuntimeApiBase(registerResult.base);
    localStorage.setItem('temuApiBase', SERVER_URL);
    await setServerAuthState({
      token: registerResult.result.token,
      user: registerResult.result.user || { phone }
    });
    enterDashboardOptimistically(registerResult.result.token, registerResult.result.user || { phone });
  } catch (err) {
    console.error('[小青蛙电商助手] 注册失败:', err);
    setDashAuthResult('注册失败，请稍后重试', 'error');
  } finally {
    const latestBtn = document.getElementById('dashRegisterBtn');
    if (latestBtn) {
      latestBtn.disabled = false;
      latestBtn.textContent = '注册并登录';
    }
  }
}

async function loadDashboardMembership() {
  try {
    const data = await getServerAuthState();
    const token = data.temuServerToken;
    const user = data.temuServerUser;
    renderDashAuthStatus(token, user);
    if (!token) {
      setDashboardAuthPanelVisible(true);
      state.boundShops = [];
      renderDashboardAccessPanels(null, null, [], null);
      clearDashboardSalesVisuals();
      setDashboardLockedState(true);
      setFeatureAccessState(true, '请先登录并完成店铺授权');
      setAuthMode('login');
      setDashAuthResult('请输入手机号和密码登录，或切换到注册创建新账号', 'info');
      return;
    }
    if (isOzonWorkspace()) {
      const resp = await fetch(`${SERVER_URL}/user/me?platform=ozon`, { headers: { 'Authorization': `Bearer ${token}` } });
      const result = await resp.json().catch(() => ({}));
      if (!resp.ok || !result.ok) {
        setDashboardAuthPanelVisible(true);
        setDashboardLockedState(true);
        setAuthMode('login');
        setDashAuthResult(result.error || '登录已失效，请重新登录', 'error');
        return;
      }
      const currentUser = result.user || user;
      persistCurrentServerUser(currentUser);
      applyRoleFeatureVisibility(currentUser);
      renderDashAuthStatus(token, currentUser);
      setDashboardAuthPanelVisible(false);
      setDashboardLockedState(false);
      setFeatureAccessState(false, '');
      setDashAuthResult('已进入 OZON 工作台', 'success');
      switchWorkspaceForPlatform('ozon');
      scheduleOzonPostAuthLoad({ forceInitialCache: true, delayMs: 80, authTimeoutMs: 0 });
      return;
    }
    const resp = await fetch(`${SERVER_URL}/user/me?platform=ozon`, { headers: { 'Authorization': `Bearer ${token}` } });
    const result = await resp.json().catch(() => ({}));
    if (result.ok) {
      setDashboardAuthPanelVisible(false);
      let membership = result.membership;
      let shops = (Array.isArray(result.shops) ? result.shops : []).filter(shop => String(shop?.mallId || '').toLowerCase().startsWith('ozon:'));
      const currentUser = result.user || user;
      persistCurrentServerUser(currentUser);
      applyRoleFeatureVisibility(currentUser);
      if (state.mallId && !isAdminUser(currentUser) && !findBoundShopForMall(shops, state.mallId)) {
        try {
          const autoBound = await bindCurrentShopSilently(token, currentUser, shops);
          if (autoBound) {
            const refreshResp = await fetch(`${SERVER_URL}/user/me?platform=ozon`, { headers: { 'Authorization': `Bearer ${token}` } });
            const refreshResult = await refreshResp.json().catch(() => ({}));
            if (refreshResp.ok && refreshResult.ok) {
              membership = refreshResult.membership;
              shops = (Array.isArray(refreshResult.shops) ? refreshResult.shops : shops).filter(shop => String(shop?.mallId || '').toLowerCase().startsWith('ozon:'));
            }
          }
        } catch (autoBindErr) {
          console.warn('[小青蛙电商助手] 当前店铺自动绑定失败:', autoBindErr?.message || autoBindErr);
        }
      }
      state.boundShops = shops;
      let currentShopStatus = null;
      if (state.mallId) {
        try {
          const shopResp = await fetch(`${SERVER_URL}/membership/shop-status/${encodeURIComponent(state.mallId)}`, {
            headers: { 'Authorization': `Bearer ${token}` }
          });
          const shopResult = await shopResp.json();
          if (shopResp.ok && shopResult.ok) currentShopStatus = shopResult;
        } catch (shopErr) {
          console.warn('[小青蛙电商助手] 查询当前店铺授权状态失败:', shopErr);
        }
      }
      setDashboardLockedState(false);
      renderDashboardAccessPanels(currentUser, membership, shops, currentShopStatus);
      restoreConnectedShopFromStorage({ updateUi: true, detectIfMissing: true }).catch(err => {
        console.warn('[小青蛙电商助手] 后台恢复当前 TEMU 店铺失败:', err);
      });
      scheduleDashboardDataRefresh();
      const renewCard = document.getElementById('dashRenewSection');
      if (renewCard) renewCard.style.display = 'none';
      const admin = isAdminUser(currentUser);
      const currentShopAllowed = isCurrentShopAllowed(currentShopStatus, shops, state.mallId);
      setFeatureAccessState(!currentShopAllowed, !state.mallId
        ? (admin ? '' : '当前账号暂无可用授权店铺，请先绑定并完成卡密授权')
        : currentShopAllowed
          ? ''
          : currentShopStatus?.shopBound
            ? '当前店铺已绑定但未授权，请输入卡密购买年卡/月卡'
            : '当前店铺未绑定，请先绑定店铺并完成授权');
      if (!state.mallId) {
        setDashAuthResult(admin
          ? '管理员账号已登录；执行 TEMU 操作前请连接当前店铺'
          : (currentShopAllowed
            ? '账号已登录，服务器绑定店铺已加载；执行 TEMU 操作前请连接当前店铺'
            : '账号已登录，但当前账号暂无可用授权店铺，请先绑定并授权'),
          currentShopAllowed ? 'success' : 'info');
      } else if (currentShopAllowed) {
        setDashAuthResult(admin ? '管理员账号已登录，当前店铺可直接使用' : '当前店铺已授权，可正常使用插件', 'success');
      } else if (currentShopStatus && currentShopStatus.shopBound && !currentShopStatus.isMember) {
        setDashAuthResult('当前店铺已绑定但未授权，请输入卡密购买年卡/月卡', 'error');
      } else {
        setDashAuthResult('当前店铺未绑定，请先绑定店铺并完成授权', 'error');
      }
    } else {
      setDashboardAuthPanelVisible(true);
      const statusCode = resp.status;
      const authExpired = statusCode === 401 || statusCode === 403 || /token|登录已失效|用户不存在|expired|unauthor|USER_NOT_FOUND|INVALID_TOKEN|TOKEN_EXPIRED|USER_DISABLED/i.test(String(result.error || result.message || result.code || ''));
      if (authExpired) {
        state.boundShops = [];
        renderDashboardAccessPanels(null, null, [], null);
        clearDashboardSalesVisuals();
        await clearServerAuthState();
        renderDashAuthStatus(null, null);
        setDashboardLockedState(true);
        setAuthMode('login');
        setDashAuthResult(result.error || '登录已失效，请重新登录', 'error');
      } else {
        setDashAuthResult(result.error || `会员状态加载失败（HTTP ${statusCode}），请稍后重试`, 'error');
      }
    }
  } catch (err) {
    console.error('[小青蛙电商助手] 加载会员状态失败:', err);
    setDashboardAuthPanelVisible(true);
    setDashAuthResult('加载会员状态失败，请稍后重试', 'error');
  }
}

async function logoutDashboardAuth() {
  state.boundShops = [];
  renderDashboardAccessPanels(null, null, [], null);
  clearDashboardSalesVisuals();
  await clearServerAuthState();
  renderDashAuthStatus(null, null);
  setDashboardLockedState(true);
  setFeatureAccessState(true, '请先登录并完成店铺授权');
  setAuthMode('login');
  setDashAuthResult('已退出登录', 'info');
}

function cleanupLaunchAuthUi() {
  removeOzonAuthHeaderArtifacts();

  const loginPhone = document.getElementById('authLoginPhone');
  if (loginPhone) {
    loginPhone.setAttribute('placeholder', '手机号 / 管理员账号');
    loginPhone.removeAttribute('inputmode');
    loginPhone.removeAttribute('maxlength');
  }

  const loginPassword = document.getElementById('authLoginPassword');
  if (loginPassword) loginPassword.setAttribute('placeholder', '密码');

  const passwordLoginBtn = document.getElementById('dashPasswordLoginBtn');
  if (passwordLoginBtn) passwordLoginBtn.textContent = '登录';
}

// 初始化首页事件
function initDashboard() {
  cleanupLaunchAuthUi();
  normalizeWorkspaceTabLabels();
  mergeDashboardTrendCardIntoSalesCard();
  initAuthPlatformChoice();
  bindOzonDashboardEvents();
  switchWorkspaceForPlatform(getSelectedAuthPlatform());
  setAuthMode('login');
  document.getElementById('authModeLoginBtn')?.addEventListener('click', () => setAuthMode('login'));
  document.getElementById('authModeRegisterBtn')?.addEventListener('click', () => setAuthMode('register'));
  document.getElementById('authModeCodeBtn')?.addEventListener('click', () => setAuthMode('code'));
  document.getElementById('authModeResetBtn')?.addEventListener('click', () => setAuthMode('reset'));
  document.getElementById('authBackLoginBtn')?.addEventListener('click', () => setAuthMode('login'));
  document.getElementById('authCaptchaRefreshBtn')?.addEventListener('click', refreshAuthCaptcha);
  document.getElementById('dashPasswordLoginBtn')?.addEventListener('click', submitPasswordLogin);
  document.getElementById('authSendLoginCodeBtn')?.addEventListener('click', () => sendSmsCode('login'));
  document.getElementById('dashCodeLoginBtn')?.addEventListener('click', submitCodeLogin);
  document.getElementById('authSendRegisterCodeBtn')?.addEventListener('click', () => sendSmsCode('register'));
  document.getElementById('dashRegisterBtn')?.addEventListener('click', submitRegister);
  document.getElementById('authSendResetCodeBtn')?.addEventListener('click', sendResetPasswordCode);
  document.getElementById('dashResetPasswordBtn')?.addEventListener('click', submitResetPassword);
  document.getElementById('dhTemuReconnectBtn')?.addEventListener('click', async () => {
    await reconnectCurrentShop();
  });
  document.getElementById('dhTemuReconnectQuickBtn')?.addEventListener('click', async () => {
    await reconnectCurrentShop();
  });
  document.getElementById('dhTemuVisitStoreBtn')?.addEventListener('click', () => {
    visitCurrentStore();
  });
  document.getElementById('dashThirtyCard')?.addEventListener('click', refreshDashboardMonthSalesFromCards);
  document.getElementById('dashThirtyRow')?.addEventListener('click', refreshDashboardMonthSalesFromCards);
  document.getElementById('dashRedeemBtn')?.addEventListener('click', async () => {
    const codeInput = document.getElementById('dashCardInput');
    const code = codeInput?.value?.trim();
    if (!code) { showToast('请输入卡密', 'error'); return; }
    const token = await getServerAuthToken();
    if (!token) { showToast('请先登录会员账号', 'error'); return; }
    const resultEl = document.getElementById('dashRedeemResult');
    resultEl.style.display = 'block'; resultEl.textContent = '兑换中...'; resultEl.style.color = '#666';
    try {
      const resp = await fetch(`${SERVER_URL}/membership/redeem`, {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
        body: JSON.stringify({ code })
      });
      const result = await resp.json();
      if (result.ok) {
        resultEl.textContent = '🎉 ' + result.message; resultEl.style.color = '#52c41a'; codeInput.value = '';
        loadDashboardMembership();
      } else { resultEl.textContent = '❌ ' + (result.error || '兑换失败'); resultEl.style.color = '#ff4d4f'; }
    } catch (err) { resultEl.textContent = '❌ 网络错误'; resultEl.style.color = '#ff4d4f'; }
  });
  syncDingHuiTemuHome();
}

function buildSalesRequestParams() {
  if (!state.mallId || !state.antiContent) return null;
  return { mallId: state.mallId, antiContent: state.antiContent, pageNum: 1, pageSize: salesDataState.pageSize };
}

async function refreshDashboardMonthSalesFromCards() {
  if (!state.mallId || !state.antiContent) {
    showToast('请先连接店铺', 'error');
    return;
  }
  if (salesDataState.loading) {
    showToast('销量数据正在同步中，请稍等', 'info');
    return;
  }
  const amount = document.getElementById('dashThirtyDayAmount');
  const count = document.getElementById('dashThirtyDayCount');
  const stand = document.getElementById('dashThirtyDayAmountStandalone');
  if (amount) amount.textContent = '同步中...';
  if (count) count.textContent = '...';
  if (stand) stand.textContent = '同步中...';
  showToast('正在同步销量查询数据...', 'info');
  await refreshSalesData({ fromDashboardMonthCard: true });
}
// 渲染每日销量柱状图（使用真实每日销量数据）
function renderSalesBarChart(products) {
  const containers = [
    document.getElementById('salesBarChart'),
    document.getElementById('dashDailySalesBarChart'),
    document.getElementById('dashBarChart')
  ].filter(Boolean);
  if (containers.length === 0) return;

  if (!products || products.length === 0) {
    containers.forEach(container => { container.innerHTML = '<div class="bar-chart-empty">暂无销量数据，请先查询</div>'; });
    return;
  }

  const bars = buildSevenDayTrendBars(products);
  if (bars.length === 0) {
    containers.forEach(container => { container.innerHTML = '<div class="bar-chart-empty">暂无真实日趋势数据</div>'; });
    return;
  }

  const html = `<div class="bar-chart-row">${renderTrendBarChartMarkup(bars)}</div>`;
  containers.forEach(container => { container.innerHTML = html; });
}

function initSalesDataTab() {
  const refreshBtn = document.getElementById('salesRefreshBtn');
  const tableWrap = document.getElementById('salesTable')?.parentElement?.parentElement;
  const salesTable = document.getElementById('salesTable');
  const getSyncTextEl = () => document.getElementById('salesLastSyncTime');
  const ensureSyncBar = () => {
    const existingSyncText = getSyncTextEl();
    if (!existingSyncText || !tableWrap || !salesTable) return;
    let syncBar = document.getElementById('salesSyncBar');
    if (!syncBar) {
      syncBar = document.createElement('div');
      syncBar.id = 'salesSyncBar';
      syncBar.style.cssText = 'display:grid;grid-template-columns:320px minmax(0,1fr) auto;align-items:end;padding:12px 14px 0;margin-top:16px;';
      tableWrap.insertBefore(syncBar, salesTable.parentElement);
    }
    syncBar.innerHTML = '';
    syncBar.appendChild(existingSyncText);
    existingSyncText.style.cssText = 'grid-column:3;justify-self:end;font-size:15px;color:#334155;line-height:1.25;font-weight:800;letter-spacing:0.01em;';
  };
  if (getSyncTextEl()) {
    ensureSyncBar();
  } else if (refreshBtn) {
    const syncWrap = document.createElement('div');
    syncWrap.style.cssText = 'display:flex;flex-direction:column;align-items:flex-start;gap:8px;margin-top:14px;';
    refreshBtn.parentNode.insertBefore(syncWrap, refreshBtn);
    syncWrap.appendChild(refreshBtn);
    const syncText = document.createElement('div');
    syncText.id = 'salesLastSyncTime';
    syncText.style.cssText = 'font-size:15px;color:#334155;line-height:1.25;font-weight:800;letter-spacing:0.01em;';
    syncText.textContent = '上次同步时间：—';
    syncWrap.appendChild(syncText);
    ensureSyncBar();
  }
  const salesHeadRow = document.querySelector('#salesTable thead tr');
  if (salesHeadRow && salesHeadRow.children.length >= 3) {
    salesHeadRow.children[0].textContent = '商品信息';
    salesHeadRow.children[1].textContent = 'SKU明细';
    salesHeadRow.removeChild(salesHeadRow.children[2]);
  }
  const sortEl = document.getElementById('salesSortSelect');
  if (sortEl) sortEl.value = 'todayDesc';
  document.getElementById('salesRefreshBtn')?.addEventListener('click', () => refreshSalesData());
  document.getElementById('salesRefreshTodayBtn')?.addEventListener('click', () => {
    refreshSalesData();
  });
  document.getElementById('salesSearchBtn')?.addEventListener('click', () => runSalesSearch());
  document.getElementById('salesExportBtn')?.addEventListener('click', () => exportSalesData());
  document.getElementById('salesPageSizeSelect')?.addEventListener('change', (e) => {
    salesDataState.pageSize = parseInt(e.target.value);
    salesDataState.pageNum = 1;
    applySalesSearchFilter();
  });
  document.getElementById('salesTableSearchInput')?.addEventListener('keydown', (e) => {
    if (e.key === 'Enter') runSalesSearch();
  });
  // 排序选择
  document.getElementById('salesSortSelect')?.addEventListener('change', () => {
    salesDataState.sortBy = document.getElementById('salesSortSelect')?.value || 'todayDesc';
    sortSalesData();
  });

  // 初次载入后补刷一次会员状态，避免认证信息尚未恢复完成就被锁死
  setTimeout(() => {
    if (document.body.contains(document.getElementById('dashAuthStatus'))) {
      loadDashboardMembership().catch(err => console.warn('[小青蛙电商助手] 延迟刷新会员状态失败:', err));
    }
  }, 1800);
}

function sortSalesData() {
  salesDataState.pageNum = 1;
  sortSalesProducts();
  applySalesSearchFilter();
}

function runSalesSearch() {
  salesDataState.pageNum = 1;
  const query = getSalesSearchQuery();
  if (!query) salesDataState.viewProducts = null;
  applySalesSearchFilter();
}

async function refreshSalesData(params = {}) {
  if (salesDataState.loading) return;
  salesDataState.loading = true;
  updateSalesSyncDisplay();

  const container = document.getElementById('salesTableBody');
  if (!container && !params.fromDashboardMonthCard) {
    salesDataState.loading = false;
    console.warn('[小青蛙电商助手] 销量表格未初始化，跳过刷新');
    showToast('销量查询界面未初始化完成，请刷新插件页后重试', 'error');
    return;
  }
  if (container) container.innerHTML = '<tr><td colspan="2" class="loading">加载中...</td></tr>';

  const pageNum = params.pageNum || salesDataState.pageNum;
  const pageSize = salesDataState.pageSize;
  const requestParams = { pageSize: 100 };

  try {
    const result = await fetchSalesDataAllDirect(requestParams);

    if (result?.success) {
      const products = result.products || [];
      salesDataState.products = products;
      salesDataState.total = result.total || products.length || 0;
      salesDataState.pageNum = pageNum;
      salesDataState.totalPages = Math.max(1, Math.ceil(products.length / pageSize));
      salesDataState.allProducts = products;
      salesDataState.viewProducts = null;
      salesDataState.lastSyncAt = Date.now();
      sortSalesProducts();
      await saveSalesDataCache();
      updateSalesSyncDisplay();
      if (container) applySalesSearchFilter();
      updateSalesDashboard();
      if (params.fromDashboardMonthCard) showToast('当月销量已同步', 'success');
    } else {
      if (container) container.innerHTML = `<tr><td colspan="2" class="empty"><div class="empty-icon">❌</div><p>${escapeHtml(result?.error || '获取销售数据失败')}</p></td></tr>`;
      if (params.fromDashboardMonthCard) showToast(result?.error || '获取销售数据失败', 'error');
    }
  } catch (err) {
    const message = err?.message || '获取销售数据失败';
    if (container) container.innerHTML = `<tr><td colspan="2" class="empty"><div class="empty-icon">❌</div><p>${escapeHtml(message)}</p></td></tr>`;
    showToast(message, 'error');
  } finally {
    salesDataState.loading = false;
  }
}

function renderSalesDataTable() {
  const container = document.getElementById('salesTableBody');
  if (!container) {
    console.warn('[小青蛙电商助手] 销量表格不存在，跳过渲染');
    return;
  }
  const products = getSalesDisplayProducts();

  if (products.length === 0) {
    const emptyText = salesDataState.lastSyncAt ? '暂无可展示的数据' : '点击同步数据获取销售数据';
    container.innerHTML = `<tr><td colspan="2" class="empty"><div class="empty-icon">📦</div><p>${escapeHtml(emptyText)}</p></td></tr>`;
    return;
  }

  container.innerHTML = products.map(p => {
    const skuRows = getSalesVisibleSkuRows(p);
    const imgHtml = p.mainImage
      ? `<img src="${escapeHtml(p.mainImage)}" class="img-fallback" data-fallback-size="56" style="width:56px;height:56px;object-fit:cover;border-radius:8px;border:1px solid #e5e7eb;flex-shrink:0;">`
      : '<div style="width:56px;height:56px;background:#f5f5f5;border-radius:8px;display:flex;align-items:center;justify-content:center;color:#cbd5e1;font-size:18px;flex-shrink:0;">📷</div>';
    const scoreText = p.score !== null && p.score !== undefined && p.score !== '' ? p.score : '—';
    const durationText = formatDuration(getSalesDurationSeconds(p));
    const nameText = escapeHtml(p.productCNName || p.productName || '—');

    return `<tr>
      <td style="padding:14px 12px;border-bottom:12px solid #f5f7fb;background:#fff;vertical-align:middle;width:320px;">
        <div style="border:1px solid #dbeafe;border-radius:14px;padding:14px;background:linear-gradient(135deg,#eff6ff 0%,#f8fbff 40%,#ffffff 100%);box-shadow:0 4px 14px rgba(59,130,246,0.08);min-height:176px;display:flex;align-items:center;">
          <div style="display:flex;gap:12px;align-items:center;width:100%;">
            ${imgHtml}
            <div style="min-width:0;flex:1;font-size:12px;color:#475569;line-height:1.55;">
              <div style="margin-bottom:8px;font-size:13px;font-weight:800;color:#0f172a;line-height:1.45;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;" title="${nameText}">${nameText}</div>
              <div><span style="display:inline-block;min-width:82px;color:#3b82f6;font-weight:700;">SPU</span><span style="font-weight:700;color:#0f172a;">${escapeHtml(p.productId || '—')}</span></div>
              <div><span style="display:inline-block;min-width:82px;color:#8b5cf6;font-weight:700;">SKC</span><span style="font-weight:700;color:#0f172a;">${escapeHtml(p.skcId || '—')}</span></div>
              <div><span style="display:inline-block;min-width:82px;color:#14b8a6;font-weight:700;">加入站点时长</span><span style="font-weight:700;color:#0f172a;">${escapeHtml(durationText)}</span></div>
              <div><span style="display:inline-block;min-width:82px;color:#f59e0b;font-weight:700;">评分</span><span style="font-weight:700;color:#0f172a;">${escapeHtml(scoreText)}</span></div>
            </div>
          </div>
        </div>
      </td>
      <td style="padding:14px 12px;border-bottom:12px solid #f5f7fb;background:#fff;vertical-align:top;">
        ${renderSalesSkuList(p, skuRows)}
      </td>
    </tr>`;
  }).join('');
}

function renderSalesSkuList(product, visibleRows = null) {
  const rows = Array.isArray(visibleRows) ? visibleRows : getSalesVisibleSkuRows(product);
  const totalRows = rows.length;
  const totalToday = rows.reduce((sum, row) => sum + (row.todaySaleVolume || 0), 0);
  const totalSeven = rows.reduce((sum, row) => sum + (row.sevenDaysSaleVolume || 0), 0);
  const totalThirty = rows.reduce((sum, row) => sum + (row.thirtyDaysSaleVolume || 0), 0);
  const totalAvailable = rows.reduce((sum, row) => sum + (row.availableInventoryNum || 0), 0);
  const totalReserved = rows.reduce((sum, row) => sum + (row.reservedInventoryNum || 0), 0);
  const totalUnavailable = rows.reduce((sum, row) => sum + (row.unavailableInventoryNum || 0), 0);
  const totalShipped = rows.reduce((sum, row) => sum + (row.shippedInventoryNum || 0), 0);
  const columns = 'minmax(0,1.42fr) 92px 52px 52px 52px 70px minmax(112px,0.8fr) minmax(150px,1.1fr)';
  const headerBaseStyle = 'padding:9px 10px;border-bottom:1px solid #eef2f7;background:linear-gradient(180deg,#f8fbff 0%,#f4f8ff 100%);font-size:12px;color:#5b6b7f;font-weight:800;letter-spacing:0.01em;line-height:1.2;';
  const rowBaseStyle = `display:grid;grid-template-columns:${columns};gap:8px;padding:9px 10px;border-bottom:1px solid #eef2f7;align-items:center;`;

  if (totalRows === 0) {
    return `
      <div style="border:1px solid #dbeafe;border-radius:16px;overflow:hidden;background:linear-gradient(180deg,#ffffff 0%,#fbfdff 100%);box-shadow:0 10px 28px rgba(59,130,246,0.08);">
        <div style="${headerBaseStyle};display:grid;grid-template-columns:${columns};gap:10px;">
          <div>SKU信息</div>
          <div style="text-align:right;color:#c2410c;">申报价格(CNY)</div>
          <div style="text-align:right;color:#2563eb;">今日</div>
          <div style="text-align:right;color:#16a34a;">7日</div>
          <div style="text-align:right;color:#7c3aed;">30日</div>
          <div style="text-align:right;color:#d97706;">可售天数</div>
          <div style="color:#0f766e;">备货逻辑</div>
          <div style="color:#334155;">库存数据</div>
        </div>
        <div style="padding:16px 14px;color:#94a3b8;font-size:12px;text-align:center;">暂无可展示的 SKU</div>
      </div>
    `;
  }

  const rowsHtml = rows.map(row => {
    const amountText = row.amountValue !== null && row.amountValue !== undefined ? `¥${formatAmount(row.amountValue)}` : '-';
    const skuId = escapeHtml(row.skuId || '—');
    const skuName = escapeHtml(row.skuName || '—');
    const priceStyle = row.amountValue ? 'color:#c62828;font-weight:700;' : 'color:#94a3b8;';
    const todayText = row.todaySaleVolume || row.todaySaleVolume === 0 ? row.todaySaleVolume.toLocaleString() : '-';
    const sevenText = row.sevenDaysSaleVolume || row.sevenDaysSaleVolume === 0 ? row.sevenDaysSaleVolume.toLocaleString() : '-';
    const thirtyText = row.thirtyDaysSaleVolume || row.thirtyDaysSaleVolume === 0 ? row.thirtyDaysSaleVolume.toLocaleString() : '-';
    const saleDaysText = row.availableSaleDays || row.availableSaleDays === 0 ? `${row.availableSaleDays}` : (row.stockDays || row.stockDays === 0 ? `${row.stockDays}` : '-');
    const purchaseLogicText = row.purchaseConfig || (row.adviceQuantity || row.adviceQuantity === 0 ? `建议备货 ${row.adviceQuantity}` : '-');

    return `
      <div style="${rowBaseStyle}">
        <div style="display:flex;align-items:center;gap:12px;min-width:0;padding:9px 11px;border-radius:12px;background:linear-gradient(135deg,#f8fafc 0%,#eff6ff 100%);border:1px solid #dbeafe;">
          <div style="flex:0 0 auto;font-size:12px;font-weight:800;color:#0f172a;white-space:nowrap;" title="${skuId}">${skuId}</div>
          <div title="${skuName}" style="flex:1 1 auto;min-width:0;font-size:13px;line-height:1.3;color:#1f2937;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${skuName}</div>
        </div>
        <div style="text-align:right;${priceStyle};font-size:12px;padding:2px 0;">${amountText}</div>
        <div style="text-align:right;color:#2563eb;font-size:12px;font-weight:700;padding:2px 0;">${todayText}</div>
        <div style="text-align:right;color:#16a34a;font-size:12px;font-weight:700;padding:2px 0;">${sevenText}</div>
        <div style="text-align:right;color:#7c3aed;font-size:12px;font-weight:700;padding:2px 0;">${thirtyText}</div>
        <div style="text-align:right;color:#d97706;font-size:12px;font-weight:700;padding:2px 0;padding-right:10px;border-right:1px solid #e2e8f0;">${saleDaysText}</div>
        <div style="color:#0f766e;font-size:12px;line-height:1.35;font-weight:700;padding-left:10px;min-width:0;">${escapeHtml(purchaseLogicText)}</div>
        <div>${renderSalesInventoryCell(row)}</div>
      </div>
    `;
  }).join('');

  return `
    <div style="border:1px solid #dbeafe;border-radius:16px;overflow:hidden;background:linear-gradient(180deg,#ffffff 0%,#fbfdff 100%);box-shadow:0 10px 28px rgba(59,130,246,0.08);">
      <div style="${headerBaseStyle};display:grid;grid-template-columns:${columns};gap:10px;">
        <div>SKU信息（${totalRows}）</div>
        <div style="text-align:right;color:#c2410c;">申报价格(CNY)</div>
        <div style="text-align:right;color:#2563eb;">今日</div>
        <div style="text-align:right;color:#16a34a;">7日</div>
        <div style="text-align:right;color:#7c3aed;">30日</div>
        <div style="text-align:right;color:#d97706;">可售天数</div>
        <div style="color:#0f766e;">备货逻辑</div>
        <div style="color:#334155;">库存数据</div>
      </div>
      <div>${rowsHtml}</div>
      <div style="display:grid;grid-template-columns:${columns};gap:10px;padding:10px 12px;background:#f8fafc;border-top:1px solid #e5e7eb;font-weight:800;align-items:center;">
        <div style="font-size:12px;">销量合计</div>
        <div style="text-align:right;color:#94a3b8;font-size:12px;">-</div>
        <div style="text-align:right;color:#0f172a;font-size:12px;">${totalToday.toLocaleString()}</div>
        <div style="text-align:right;color:#0f172a;font-size:12px;">${totalSeven.toLocaleString()}</div>
        <div style="text-align:right;color:#0f172a;font-size:12px;">${totalThirty.toLocaleString()}</div>
        <div style="text-align:right;color:#94a3b8;font-size:12px;">-</div>
        <div style="color:#94a3b8;font-size:12px;padding-left:12px;">-</div>
        <div>${renderSalesInventoryCell({
          availableInventoryNum: totalAvailable,
          reservedInventoryNum: totalReserved,
          unavailableInventoryNum: totalUnavailable,
          shippedInventoryNum: totalShipped,
          warehouseInfoList: []
        })}</div>
      </div>
    </div>
  `;
}

function formatDuration(seconds) {
  if (!seconds) return '-';
  const days = Math.floor(seconds / 86400);
  if (days > 365) return Math.floor(days / 365) + '年' + (days % 365) + '天';
  if (days > 30) return Math.floor(days / 30) + '月' + (days % 30) + '天';
  return days + '天';
}

function renderSalesDataPagination() {
  const info = document.getElementById('salesPaginationInfo');
  const btns = document.getElementById('salesPaginationButtons');
  const container = document.getElementById('salesPaginationContainer');
  if (!info || !btns) return;

  const query = getSalesSearchQuery();

  if (query) {
    if (container) container.style.display = 'none';
    info.innerHTML = `已筛选 <strong>${getSalesFilteredProducts().length}</strong> 条`;
    btns.innerHTML = '';
    return;
  }

  const visibleProducts = getSalesFilteredProducts();
  const totalVisible = visibleProducts.length;
  const totalPages = Math.max(1, Math.ceil(totalVisible / Math.max(1, salesDataState.pageSize || 20)));
  if (salesDataState.total > 0 && container) container.style.display = 'flex';

  if (totalVisible > 0) {
    info.innerHTML = `共 <strong>${totalVisible}</strong> 条，第 <strong>${salesDataState.pageNum}</strong>/<strong>${totalPages}</strong> 页`;
  } else {
    info.innerHTML = '共 0 条';
  }

  btns.innerHTML = '';
  if (totalPages <= 1) return;

  if (salesDataState.pageNum > 1) {
    const btn = document.createElement('button');
    btn.className = 'btn btn-default btn-sm'; btn.textContent = '上一页';
    btn.addEventListener('click', () => goToSalesPage(salesDataState.pageNum - 1));
    btns.appendChild(btn);
  }

  const maxBtns = 5;
  let s = Math.max(1, salesDataState.pageNum - Math.floor(maxBtns / 2));
  let e = Math.min(totalPages, s + maxBtns - 1);
  if (e - s < maxBtns - 1) s = Math.max(1, e - maxBtns + 1);

  for (let i = s; i <= e; i++) {
    const btn = document.createElement('button');
    btn.className = i === salesDataState.pageNum ? 'btn btn-primary btn-sm' : 'btn btn-default btn-sm';
    btn.textContent = i;
    if (i !== salesDataState.pageNum) btn.addEventListener('click', () => goToSalesPage(i));
    btns.appendChild(btn);
  }

  if (salesDataState.pageNum < totalPages) {
    const btn = document.createElement('button');
    btn.className = 'btn btn-default btn-sm'; btn.textContent = '下一页';
    btn.addEventListener('click', () => goToSalesPage(salesDataState.pageNum + 1));
    btns.appendChild(btn);
  }

  if (totalPages > 5) {
    const jumpWrap = document.createElement('span');
    jumpWrap.style.cssText = 'display:flex;align-items:center;gap:4px;margin-left:8px;font-size:12px;';
    jumpWrap.innerHTML = '跳至<input type="number" min="1" max="' + totalPages + '" style="width:50px;padding:2px 4px;border:1px solid #d9d9d9;border-radius:4px;font-size:12px;text-align:center;">页';
    const jumpInput = jumpWrap.querySelector('input');
    jumpInput.value = salesDataState.pageNum;
    jumpInput.addEventListener('keydown', (ev) => {
      if (ev.key === 'Enter') {
        const p = parseInt(jumpInput.value);
        if (p >= 1 && p <= totalPages) goToSalesPage(p);
      }
    });
    btns.appendChild(jumpWrap);
  }
}

async function exportSalesData() {
  const products = getSalesDisplayProducts();
  if (products.length === 0) {
    showToast('没有数据可导出', 'warning');
    return;
  }

  try {
    const XLSX = await ensureXlsxLoaded();
    const rows = [['SPU ID', '商品名称', '今日销量', '7天销量', '30天销量', '库存', '售价', '上架时长(天)']];
    products.forEach(p => {
      rows.push([
        p.productId, p.productCNName, p.todaySaleVolume, p.sevenDaysSaleVolume, p.thirtyDaysSaleVolume,
        p.stockQuantity, p.price || '', Math.floor(getSalesDurationSeconds(p) / 86400)
      ]);
    });

    const ws = XLSX.utils.aoa_to_sheet(rows);
    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, '销售数据');
    XLSX.writeFile(wb, `销售数据_${new Date().toISOString().slice(0, 10)}.xlsx`);
    showToast('导出成功！', 'success');
  } catch (err) {
    showToast('导出失败: ' + err.message, 'error');
  }
}

// ========== 优惠活动报名 Tab（大白鹅风格：表格+一键报名）==========
const activityState = {
  activities: [],
  isRunning: false,
  logHidden: false
};

function updateActivityLogVisibility() {
  const progressSection = document.getElementById('activityProgressSection');
  const fill = document.getElementById('activityProgressFill');
  const text = document.getElementById('activityProgressText');
  const list = document.getElementById('activityResultList');
  const toggleBtn = document.getElementById('activityLogToggleBtn');
  if (!progressSection) return;
  const hasLog = Boolean(text?.textContent?.trim() || list?.innerHTML?.trim());
  progressSection.style.display = hasLog || activityState.isRunning ? 'block' : 'none';
  if (fill) fill.style.display = activityState.logHidden ? 'none' : '';
  if (text) text.style.display = activityState.logHidden ? 'none' : '';
  if (list) list.style.display = activityState.logHidden ? 'none' : '';
  if (toggleBtn) toggleBtn.textContent = activityState.logHidden ? '显示日志' : '隐藏日志';
}

function bindActivityLogToggle() {
  const toggleBtn = document.getElementById('activityLogToggleBtn');
  if (!toggleBtn || toggleBtn.dataset.bound === '1') return;
  toggleBtn.dataset.bound = '1';
  toggleBtn.addEventListener('click', () => {
    activityState.logHidden = !activityState.logHidden;
    updateActivityLogVisibility();
  });
}

function initActivityTab() {
  document.getElementById('activityLoadBtn')?.addEventListener('click', loadActivityList);
  document.getElementById('activityRefreshBtn')?.addEventListener('click', loadActivityList);
  bindActivityLogToggle();
  updateActivityLogVisibility();
}

function getActivityDisplayName(act) {
  return act?.activityName || act?.activityThematicName || act?.activityThemeName || act?.name || act?.title || '未知活动';
}

function formatActivityTime(act) {
  const start = act?.sessionStartTime || act?.startTime || act?.start_time || act?.enrollStartAt || act?.enrollStartTime || act?.enroll_start_time || '';
  const end = act?.sessionEndTime || act?.endTime || act?.end_time || act?.enrollDeadLine || act?.enrollEndTime || act?.enroll_end_time || '';
  if (start && end) {
    return `${formatTimestamp(start)} ~ ${formatTimestamp(end)}`;
  }
  if (start) return formatTimestamp(start);
  if (end) return formatTimestamp(end);
  return '<span style="display:inline-block;padding:2px 8px;border-radius:10px;background:#e8f5e9;color:#2e7d32;font-size:11px;font-weight:500;">长期活动</span>';
}

function formatActivityRate(act) {
  const raw = act?.discountRate;
  if (raw === undefined || raw === null || raw === '') return '-';
  const text = String(raw).trim();
  if (!text) return '-';
  if (/^[0-9]+(\.[0-9]+)?$/.test(text)) return `${text}%`;
  return text.endsWith('%') || text.includes('折') ? text : `${text}%`;
}

function formatActivityEnrollCount(act) {
  const raw = act?.enrolledCount ?? act?.enrollCount ?? act?.participantCount ?? act?.registeredCount ?? act?.canEnrollCnt;
  if (raw === undefined || raw === null || raw === '') return '-';
  return String(raw);
}

function extractActivityMatchProducts(matchResult) {
  const result = matchResult?.result ?? matchResult?.data ?? matchResult?.detail ?? matchResult;
  const candidates = [
    result?.matchList,
    result?.products,
    result?.list,
    result?.items,
    result?.productList,
    Array.isArray(result) ? result : []
  ];

  for (const candidate of candidates) {
    if (Array.isArray(candidate) && candidate.length > 0) return candidate;
  }
  return [];
}

function isActivityEnrollSuccess(result) {
  return result?.success === true
    || result?.code === 0
    || result?.code === '0'
    || result?.result?.success === true
    || result?.result?.code === 0
    || result?.result?.code === '0';
}

function getActivityEnrollError(result) {
  return result?.error_msg
    || result?.msg
    || result?.message
    || result?.error
    || result?.result?.error_msg
    || result?.result?.msg
    || result?.result?.message
    || result?.result?.error
    || '未知错误';
}

function extractActivityIdFromUrl(url) {
  if (!url || typeof url !== 'string') return '';
  try {
    const parsed = new URL(url, 'https://agentseller.temu.com');
    return (
      parsed.searchParams.get('activityId')
      || parsed.searchParams.get('activity_id')
      || parsed.searchParams.get('activityid')
      || parsed.searchParams.get('id')
      || ''
    ).trim();
  } catch (err) {
    return '';
  }
}

function getActivityThematicId(act) {
  return String(
    act?.activityThematicId
    || act?.activityThematic
    || act?.activityThemeId
    || act?.thematicId
    || ''
  ).trim();
}

function buildActivityMatchRequest(act, searchScrollContext = null) {
  const activityThematicId = getActivityThematicId(act);
  const activityType = String(act?.activityType || '').trim();
  return {
    activityType: activityType || undefined,
    rowCount: 50,
    productSkcExtCodes: [],
    productSkuExtCodes: [],
    addSite: true,
    searchScrollContext,
    activityThematicId: activityThematicId || undefined,
    filterUnsalableWarning: false
  };
}

function buildActivitySubmitProduct(product) {
  const skcSource = Array.isArray(product?.skcList) && product.skcList.length > 0
    ? product.skcList
    : (Array.isArray(product?.skcArr) ? product.skcArr : []);

  return {
    productId: String(product?.productId || product?.spuId || product?.goodsId || product?.productSpuId || '').trim(),
    activityStock: product?.suggestActivityStock ?? product?.activityStock ?? product?.stock ?? product?.currentStock ?? 0,
    skcList: skcSource.map((skc) => {
      const skuSource = Array.isArray(skc?.skuList) && skc.skuList.length > 0
        ? skc.skuList
        : (Array.isArray(skc?.skuArr) ? skc.skuArr : []);
      return {
        skcId: String(skc?.skcId || skc?.skc_id || skc?.productSkcId || '').trim(),
        activityPrice: skc?.activityPrice ?? skc?.suggestActivityPrice ?? skc?.privilegeSuggestActivityPrice ?? undefined,
        activityDiscount: skc?.activityDiscount ?? skc?.suggestActivityDiscount ?? undefined,
        skuList: skuSource.map((sku) => ({
          skuId: String(sku?.skuId || sku?.productSkuId || sku?.id || '').trim(),
          activityPrice: sku?.activityPrice ?? sku?.suggestActivityPrice ?? sku?.privilegeSuggestActivityPrice ?? undefined,
          activityDiscount: sku?.activityDiscount ?? sku?.suggestActivityDiscount ?? undefined
        })).filter((sku) => sku.skuId)
      };
    }).filter((skc) => skc.skcId || skc.skuList.length > 0),
    sessionIds: product?.suggestEnrollSessionIdList || product?.sessionIds || product?.sessionIdList || [],
    activityEnrollPrivilegeType: product?.activityEnrollPrivilegeType ?? 0
  };
}

async function matchActivityProductsWithFallback(act) {
  const searchContexts = [null];
  const tried = [];
  const allMatched = [];
  let lastResult = null;
  const baseRequest = buildActivityMatchRequest(act);

  for (let round = 0; round < 20; round++) {
    const searchScrollContext = searchContexts.length > 0 ? searchContexts.shift() : null;
    const requestParams = { ...baseRequest, searchScrollContext };
    const result = await safeRuntimeSendMessage({
      action: 'scrollMatchActivity',
      params: requestParams
    }, { success: false, error: '活动匹配请求失败' }, 30000);

    console.log('[小青蛙电商助手] scrollMatchActivity 返回:', JSON.stringify({ requestParams, result })?.substring(0, 500));
    lastResult = result;
    tried.push(searchScrollContext ? 'scroll' : 'initial');

    const matchedProducts = extractActivityMatchProducts(result);
    if (matchedProducts.length > 0) {
      allMatched.push(...matchedProducts);
    }

    const resultData = result?.result ?? result?.data ?? result?.detail ?? result;
    const stillCount = Number(resultData?.stillCount ?? result?.stillCount ?? 0);
    const nextScrollContext = resultData?.searchScrollContext ?? result?.searchScrollContext ?? null;

    if (stillCount > 0 && nextScrollContext) {
      searchContexts.push(nextScrollContext);
      continue;
    }

    if (matchedProducts.length === 0 && round === 0 && !getActivityThematicId(act)) {
      break;
    }

    if (stillCount > 0 && !nextScrollContext) {
      break;
    }

    break;
  }

  if (allMatched.length === 0) {
    const legacyCandidates = [];
    const legacyId = [
      act?.activityId,
      act?.activityThematicId,
      act?.activityThemeId,
      act?.parentActivityId,
      act?.id,
      extractActivityIdFromUrl(act?.enrollUrl)
    ].filter(Boolean);
    for (const id of legacyId) {
      legacyCandidates.push({ activityId: String(id).trim(), activityType: act?.activityType || undefined, pageNo: 1, pageSize: 200 });
    }

    for (const requestParams of legacyCandidates) {
      const result = await safeRuntimeSendMessage({
        action: 'scrollMatchActivity',
        params: requestParams
      }, { success: false, error: '活动匹配请求失败' }, 30000);
      console.log('[小青蛙电商助手] scrollMatchActivity 旧参数返回:', JSON.stringify({ requestParams, result })?.substring(0, 500));
      lastResult = result;
      tried.push(`legacy:${requestParams.activityId}`);
      const matchedProducts = extractActivityMatchProducts(result);
      if (matchedProducts.length > 0) {
        allMatched.push(...matchedProducts);
        break;
      }
    }
  }

  if (allMatched.length > 0) {
    return { result: lastResult, matchedProducts: allMatched, tried };
  }

  return { result: lastResult, matchedProducts: [], tried };
}

async function loadActivityList() {
  const tbody = document.getElementById('activityTableBody');
  const loadBtn = document.getElementById('activityLoadBtn');
  const refreshBtn = document.getElementById('activityRefreshBtn');
  const countInfo = document.getElementById('activityCountInfo');

  if (!tbody || !loadBtn || !refreshBtn || !countInfo) {
    showToast('活动报名界面未初始化完成', 'error');
    return;
  }

  loadBtn.disabled = true;
  refreshBtn.disabled = true;
  loadBtn.textContent = '⏳ 加载中...';
  tbody.innerHTML = '<tr><td colspan="7" class="loading">加载活动列表中...</td></tr>';

  try {
    const result = await safeRuntimeSendMessage({ action: 'fetchActivityList', params: {} }, { success: false, error: '获取活动列表失败' }, 30000);

    if (result?.success) {
      activityState.activities = result.activities || [];

      if (activityState.activities.length === 0) {
        tbody.innerHTML = '<tr><td colspan="7" class="empty">📦 暂无可报名活动</td></tr>';
        countInfo.textContent = '暂无可报名活动';
      } else {
        countInfo.textContent = `共 ${activityState.activities.length} 个活动`;
        renderActivityTable();
      }
    } else {
      activityState.activities = [];
      tbody.innerHTML = `<tr><td colspan="7" class="empty"><div class="empty-icon">❌</div><p>${escapeHtml(result?.error || '获取活动列表失败')}</p></td></tr>`;
      countInfo.textContent = '加载失败，请重试';
      showToast(result?.error || '获取活动列表失败', 'error');
    }
  } catch (error) {
    activityState.activities = [];
    tbody.innerHTML = `<tr><td colspan="7" class="empty"><div class="empty-icon">❌</div><p>${escapeHtml(error?.message || '获取活动列表失败')}</p></td></tr>`;
    countInfo.textContent = '加载失败，请重试';
    showToast(error?.message || '获取活动列表失败', 'error');
  } finally {
    loadBtn.disabled = false;
    loadBtn.textContent = '🔄 加载活动列表';
    refreshBtn.disabled = activityState.activities.length === 0;
  }
}

function renderActivityTable() {
  const tbody = document.getElementById('activityTableBody');
  if (activityState.activities.length === 0) {
    tbody.innerHTML = '<tr><td colspan="7" class="empty">📦 暂无可报名活动</td></tr>';
    return;
  }

  tbody.innerHTML = activityState.activities.map((act, idx) => {
    const activityName = getActivityDisplayName(act);
    const activityTime = formatActivityTime(act);
    const enrollTime = (act.enrollStartTime && act.enrollEndTime)
      ? `${formatTimestamp(act.enrollStartTime)} ~ ${formatTimestamp(act.enrollEndTime)}`
      : (act.enrollStartTime ? formatTimestamp(act.enrollStartTime) : '-');
    const discount = formatActivityRate(act);
    const enrolledCount = formatActivityEnrollCount(act);

    return `<tr>
      <td style="text-align:center;color:#999;">${idx + 1}</td>
      <td>
        <div style="font-weight:600;font-size:13px;color:var(--text-primary);">${escapeHtml(activityName)}</div>
        ${act.activityContent ? `<div style="margin-top:3px;font-size:11px;color:var(--text-secondary);line-height:1.4;">${escapeHtml(String(act.activityContent))}</div>` : ''}
      </td>
      <td style="font-size:11px;color:var(--text-secondary);white-space:nowrap;">${escapeHtml(activityTime)}</td>
      <td style="text-align:center;font-weight:600;color:#c62828;font-size:13px;">${escapeHtml(String(discount))}</td>
      <td style="font-size:11px;color:var(--text-secondary);white-space:nowrap;">${escapeHtml(enrollTime)}</td>
      <td style="text-align:center;font-weight:500;color:var(--text-primary);">${enrolledCount}</td>
      <td style="text-align:center;">
        <button class="btn btn-primary btn-sm enroll-btn" data-idx="${idx}">一键报名</button>
      </td>
    </tr>`;
  }).join('');

  // 绑定一键报名按钮事件
  tbody.querySelectorAll('.enroll-btn').forEach(btn => {
    btn.addEventListener('click', function() {
      const idx = parseInt(this.dataset.idx);
      if (!isNaN(idx) && typeof oneClickEnroll === 'function') oneClickEnroll(idx);
    });
  });
}

function formatTimestamp(ts) {
  if (!ts) return '-';
  const d = new Date(typeof ts === 'number' ? ts : parseInt(ts));
  if (isNaN(d.getTime())) return '-';
  return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`;
}

// 一键报名：自动匹配商品 + 批量提交
window.oneClickEnroll = async function(idx) {
  const act = activityState.activities[idx];
  if (!act) { showToast('活动不存在', 'error'); return; }
  if (activityState.isRunning) { showToast('有任务正在执行，请稍候', 'info'); return; }

  activityState.isRunning = true;

  // 显示进度区域
  const progressSection = document.getElementById('activityProgressSection');
  if (!progressSection) {
    activityState.isRunning = false;
    showToast('活动报名进度区域未找到', 'error');
    return;
  }
  activityState.logHidden = false;
  progressSection.style.display = 'block';
  const fill = document.getElementById('activityProgressFill');
  const text = document.getElementById('activityProgressText');
  const list = document.getElementById('activityResultList');
  if (!fill || !text || !list) {
    activityState.isRunning = false;
    showToast('活动报名界面未初始化完成', 'error');
    return;
  }
  fill.style.width = '0%';
  fill.className = 'progress-fill blue';
  text.textContent = `开始处理活动: ${getActivityDisplayName(act)}...`;
  list.innerHTML = '';
  updateActivityLogVisibility();

  try {
    // 第一步：匹配可报名商品
    text.textContent = '🔍 正在匹配可报名商品...';
    fill.style.width = '5%';

    const matchPack = await matchActivityProductsWithFallback(act);
    const matchResult = matchPack.result;
    const matchedProducts = matchPack.matchedProducts;

    console.log('[小青蛙电商助手] scrollMatchActivity 返回:', JSON.stringify({ tried: matchPack.tried, matchResult })?.substring(0, 500));

    if (matchedProducts.length === 0 && !matchResult?.success) {
      console.warn('[小青蛙电商助手] 活动匹配失败:', matchResult?.error || '未知错误');
    }

    if (matchedProducts.length === 0) {
      fill.style.width = '100%';
      fill.className = 'progress-fill blue';
      text.textContent = '⚠️ 该活动没有匹配到可报名商品';
      const item = document.createElement('div');
      item.className = 'result-item';
      const triedText = (matchPack.tried || []).length > 0 ? `已尝试：${escapeHtml(matchPack.tried.join('，'))}` : '';
      item.innerHTML = `<span class="icon">ℹ️</span><span class="reason">活动「${escapeHtml(getActivityDisplayName(act))}」没有匹配到可报名商品${triedText ? `，${triedText}` : ''}</span>`;
      list.appendChild(item);
      updateActivityLogVisibility();
      activityState.isRunning = false;
      return;
    }

    text.textContent = `匹配到 ${matchedProducts.length} 个可报名商品`;
    fill.style.width = '15%';

    // 显示匹配到的商品列表（带报名链接）
    const enrollBaseUrl = `https://agentseller.temu.com/marketing-center/activity-manage/detail?activityId=${act.activityId}`;
    let html = `<div style="margin-bottom:10px;display:flex;justify-content:space-between;align-items:center;">
      <span style="font-size:13px;color:#666;">共 ${matchedProducts.length} 个商品可报名</span>
      <div>
        <a href="${enrollBaseUrl}" target="_blank" style="font-size:12px;color:#1890ff;text-decoration:none;margin-right:12px;">🔗 在TEMU查看活动</a>
        <button class="btn btn-primary btn-sm" id="enrollAllBtn">🚀 一键全部报名</button>
      </div>
    </div>`;
    html += `<table class="product-table" style="font-size:12px;">
      <thead><tr><th style="width:40px;">#</th><th>商品ID</th><th>商品名称</th><th>操作</th></tr></thead><tbody>`;
    matchedProducts.forEach((p, i) => {
      const pId = p.productId || p.spuId || p.goodsId || '';
      const pName = p.productCNName || p.productName || '';
      const pUrl = `https://agentseller.temu.com/goods/detail?spuId=${pId}`;
      html += `<tr>
        <td style="text-align:center;color:#999;">${i+1}</td>
        <td><a href="${pUrl}" target="_blank" style="color:#1890ff;text-decoration:none;">${escapeHtml(pId)}</a></td>
        <td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escapeHtml(pName || '-')}</td>
        <td style="text-align:center;"><a href="${pUrl}" target="_blank" class="btn btn-default btn-sm" style="font-size:11px;">查看</a></td>
      </tr>`;
    });
    html += '</tbody></table>';
    list.innerHTML = html;
    fill.style.width = '20%';
    updateActivityLogVisibility();

    // 绑定"一键全部报名"按钮
    document.getElementById('enrollAllBtn')?.addEventListener('click', async () => {
      const activityName = act.activityName || act.name || act.title || getActivityThematicId(act) || '当前活动';
      const confirmed = await showCenterConfirm({
        title: '确认活动报名',
        message: `即将把 ${matchedProducts.length} 个匹配商品报名到「${activityName}」。这是会提交到 TEMU 店铺的真实操作，请确认后继续。`,
        confirmText: '确认报名',
        cancelText: '取消'
      });
      if (!confirmed) return;

      const enrollAllBtn = document.getElementById('enrollAllBtn');
      if (enrollAllBtn) {
        enrollAllBtn.disabled = true;
        enrollAllBtn.textContent = '报名中...';
      }
      let successCount = 0, failCount = 0;
      for (let i = 0; i < matchedProducts.length; i++) {
        const product = matchedProducts[i] || {};
        const productId = String(product.productId || product.spuId || product.goodsId || product.id || product.productSkuId || '').trim();
        const pct = 20 + Math.round(((i + 1) / matchedProducts.length) * 75);
        fill.style.width = pct + '%';
        fill.className = 'progress-fill blue';
        text.textContent = `报名中 ${i + 1}/${matchedProducts.length}（${escapeHtml(productId || '未知商品')}）...`;

        // 更新表格行状态
        const rows = list.querySelectorAll('table tbody tr');
        const statusCell = rows[i]?.querySelector('td:last-child');
        if (statusCell) statusCell.innerHTML = '<span style="color:#faad14;">⏳ 报名中</span>';

        if (!productId) {
          if (statusCell) statusCell.innerHTML = '<span style="color:#ff4d4f;">❌ 无商品ID</span>';
          failCount++;
          continue;
        }

        try {
          const enrollParams = {
            activityType: act.activityType || undefined,
            activityThematicId: getActivityThematicId(act) || undefined,
            productList: [buildActivitySubmitProduct(product)]
          };

          const result = await safeRuntimeSendMessage({
            action: 'enrollActivity',
            params: enrollParams
          }, { success: false, error: '活动报名请求失败' }, 30000);

          if (statusCell) {
            if (isActivityEnrollSuccess(result)) {
              statusCell.innerHTML = '<span style="color:#52c41a;font-weight:500;">✅ 成功</span>';
              successCount++;
            } else {
              statusCell.innerHTML = `<span style="color:#ff4d4f;" title="${escapeHtml(getActivityEnrollError(result))}">❌ 失败</span>`;
              failCount++;
            }
          }
        } catch (err) {
          if (statusCell) statusCell.innerHTML = '<span style="color:#ff4d4f;">❌ 异常</span>';
          failCount++;
        }

        await new Promise(r => setTimeout(r, 500));
      }

    // 最终状态
    fill.style.width = '100%';
    if (failCount === 0) fill.className = 'progress-fill green';
    else if (successCount === 0) fill.className = 'progress-fill red';
    else fill.className = 'progress-fill blue';
    const btn = document.getElementById('enrollAllBtn');
    if (btn) { btn.disabled = false; btn.textContent = `✅ 报名完成：${successCount}成功 ${failCount}失败`; }
    text.textContent = `报名完成！✅ ${successCount} 成功, ❌ ${failCount} 失败（共 ${matchedProducts.length} 个商品）`;
    showToast(`活动报名完成！成功: ${successCount}, 失败: ${failCount}`, successCount > 0 ? 'success' : 'error');
    activityState.isRunning = false;
    updateActivityLogVisibility();
    }); // end enrollAllBtn click handler

  } catch (error) {
    fill.style.width = '100%';
    fill.className = 'progress-fill red';
    text.textContent = `处理失败: ${error.message}`;
    showToast(`一键报名失败: ${error.message}`, 'error');
  } finally {
    activityState.isRunning = false;
    updateActivityLogVisibility();
  }
};

// ========== 资质批量上传 Tab（大白鹅风格：创建任务面板）==========
// ===== 资质上传 Tab =====
const qualState = {
  products: [],
  selectedSpus: [],
  files: [],
  pageNum: 1,
  pageSize: 10,
  totalPages: 0,
  total: 0
};

function initQualificationTab() {
  // 刷新按钮
  document.getElementById('qualRefreshBtn')?.addEventListener('click', () => {
    qualState.pageNum = 1;
    loadQualProductList();
  });

  // 搜索
  document.getElementById('qualSearchInput')?.addEventListener('input', () => renderQualProductList());

  // 状态筛选
  document.getElementById('qualStatusFilter')?.addEventListener('change', () => {
    qualState.pageNum = 1;
    loadQualProductList();
  });

  // 每页条数
  document.getElementById('qualPageSizeSelect')?.addEventListener('change', (e) => {
    qualState.pageSize = parseInt(e.target.value);
    qualState.pageNum = 1;
    loadQualProductList();
  });

  // 选择文件
  document.getElementById('qualSelectFilesBtn')?.addEventListener('click', () => {
    const input = document.getElementById('qualFileInput');
    if (!input) return;
    input.value = '';
    input.click();
  });

  document.getElementById('qualFileInput')?.addEventListener('change', (e) => {
    qualState.files = Array.from(e.target.files);
    updateQualFileList();
  });

  // 清空文件
  document.getElementById('qualClearFilesBtn')?.addEventListener('click', () => {
    qualState.files = [];
    const input = document.getElementById('qualFileInput');
    if (input) input.value = '';
    updateQualFileList();
  });

  // 上传按钮
  document.getElementById('qualUploadBtn')?.addEventListener('click', startQualUpload);

  // 自动加载商品列表
  loadQualProductList();
}

function updateQualFileList() {
  const container = document.getElementById('qualFileList');
  if (!container) return;
  if (qualState.files.length === 0) {
    container.innerHTML = '<div>未选择文件（可选）</div>';
    return;
  }
  container.innerHTML = qualState.files.map((f, i) =>
    `<div style="padding:2px 0;display:flex;justify-content:space-between;align-items:center;">
      <span>${escapeHtml(f.name)}</span>
      <span style="color:#999;font-size:11px;">${(f.size / 1024).toFixed(1)}KB</span>
    </div>`
  ).join('');
}

async function loadQualProductList() {
  const container = document.getElementById('qualProductListContainer');
  const refreshBtn = document.getElementById('qualRefreshBtn');
  if (!container) return;
  container.innerHTML = '<div class="loading">加载中...</div>';
  if (refreshBtn) refreshBtn.disabled = true;

  try {
    const result = await safeRuntimeSendMessage({
      action: 'fetchQualificationPool',
      params: {
        pageNum: qualState.pageNum,
        pageSize: qualState.pageSize,
        fetchAll: false,
        uploadStatusList: [0, 1, 2, 3, 4, 5, 6]
      }
    }, { success: false, error: '加载资质商品失败' }, 30000);

    if (result?.success) {
      qualState.products = Array.isArray(result.products) ? result.products : [];
      qualState.total = Number(result.total || qualState.products.length || 0);
      qualState.totalPages = Number(result.totalPages || Math.ceil(qualState.total / Math.max(1, qualState.pageSize)) || 1);
      renderQualProductList();
      renderQualPagination();
    } else {
      qualState.products = [];
      qualState.total = 0;
      qualState.totalPages = 0;
      container.innerHTML = `<div class="empty"><div class="empty-icon">❌</div><p>${escapeHtml(result?.error || '加载失败')}</p></div>`;
      renderQualPagination();
    }
  } catch (err) {
    console.error('[小青蛙电商助手] loadQualProductList 异常:', err);
    qualState.products = [];
    qualState.total = 0;
    qualState.totalPages = 0;
    container.innerHTML = `<div class="empty"><div class="empty-icon">❌</div><p>${escapeHtml(err?.message || String(err))}</p></div>`;
    renderQualPagination();
  } finally {
    if (refreshBtn) refreshBtn.disabled = false;
  }
}

function renderQualProductList() {
  const container = document.getElementById('qualProductListContainer');
  if (!container) return;

  if (qualState.products.length === 0) {
    container.innerHTML = '<div class="empty"><div class="empty-icon">📦</div><p>暂无商品数据</p></div>';
    updateQualSelectedCount();
    return;
  }

  const statusFilter = document.getElementById('qualStatusFilter')?.value || '';
  const searchQuery = document.getElementById('qualSearchInput')?.value?.trim()?.toLowerCase() || '';

  let filtered = qualState.products;
  if (statusFilter === 'pending') filtered = filtered.filter(p => p.status === 'pending');
  if (statusFilter === 'completed') filtered = filtered.filter(p => p.status === 'completed');
  if (searchQuery) filtered = filtered.filter(p =>
    p.spuId?.toLowerCase().includes(searchQuery) || p.spuName?.toLowerCase().includes(searchQuery)
  );

  // 收集所有待上传资质类型
  const allTaskTypes = new Set();
  qualState.products.forEach(p => {
    (p.waitTaskList || []).forEach(t => {
      if (!t.statusText || !/上传完成|已上传|已完成|完成|成功|通过|审核通过/.test(String(t.statusText))) {
        allTaskTypes.add(t.task_name || t.task_type);
      }
    });
  });
  // 更新资质类型下拉
  const typeSelect = document.getElementById('qualTypeSelect');
  if (typeSelect && allTaskTypes.size > 0) {
    const currentVal = typeSelect.value;
    typeSelect.innerHTML = '<option value="">-- 请选择资质类型 --</option>' +
      Array.from(allTaskTypes).map(t => `<option value="${escapeHtml(String(t))}">${escapeHtml(String(t))}</option>`).join('');
    if (currentVal) typeSelect.value = currentVal;
  }

  const html = `
    <table class="product-table">
      <thead>
        <tr>
          <th style="width:34px;"><input type="checkbox" class="checkbox" id="qualSelectAll"></th>
          <th style="width:290px;">SPU信息</th>
          <th style="min-width:180px;">资质名称</th>
          <th style="width:130px;">资质上传状态</th>
          <th style="min-width:220px;">中文资质名称</th>
        </tr>
      </thead>
      <tbody>
        ${filtered.map(p => {
          const certTasks = Array.isArray(p.waitTaskList) ? p.waitTaskList : [];
          const renderedTasks = renderQualificationTaskRows(certTasks);
          const rows = renderedTasks.length ? renderedTasks : [{ rawName: '—', chineseLabel: '—', statusText: '—' }];
          const rowCount = rows.length;
          const categoryText = p.categoryName || p.category || p.catName || '—';
          const spuInfoCell = `
            <div style="display:flex;gap:10px;align-items:center;height:100%;">
              <div style="width:52px;height:52px;flex:0 0 auto;border-radius:6px;overflow:hidden;border:1px solid #e5e7eb;background:#f8fafc;display:flex;align-items:center;justify-content:center;">
                ${p.mainImage
                  ? `<img src="${escapeHtml(p.mainImage)}" class="img-fallback" data-fallback-size="52" style="width:52px;height:52px;object-fit:cover;">`
                  : '<span style="font-size:18px;color:#cbd5e1;">📷</span>'}
              </div>
              <div style="min-width:0;display:flex;flex-direction:column;gap:4px;justify-content:center;">
                <div style="font-size:12px;color:#475467;line-height:1.35;">类目：${escapeHtml(categoryText)}</div>
                <div style="font-size:12px;color:#475467;line-height:1.35;">SPU：${escapeHtml(String(p.spuId || '—'))}</div>
              </div>
            </div>`;

          return rows.map((row, index) => {
            const isFirst = index === 0;
            return `<tr>
              ${isFirst ? `<td rowspan="${rowCount}" style="vertical-align:top;padding-top:18px;"><input type="checkbox" class="checkbox qual-product-cb" data-spu="${escapeHtml(String(p.spuId))}" ${p.status === 'pending' ? '' : ''}></td>` : ''}
              ${isFirst ? `<td rowspan="${rowCount}" style="vertical-align:middle;padding:14px 10px;">${spuInfoCell}</td>` : ''}
              <td style="font-size:12px;color:#1f2937;">${escapeHtml(String(row.rawName || '—'))}</td>
              <td style="font-size:12px;color:#475467;line-height:1.35;">
                <div>${escapeHtml(String(row.statusText || '—'))}</div>
                ${row.statusDetail ? `<div style="font-size:11px;color:#94a3b8;">${escapeHtml(String(row.statusDetail))}</div>` : ''}
              </td>
              <td style="font-size:12px;color:#1f2937;">${escapeHtml(String(row.chineseLabel || '—'))}</td>
            </tr>`;
          }).join('');
        }).join('')}
      </tbody>
    </table>
  `;

  container.innerHTML = html;

  // 全选
  document.getElementById('qualSelectAll')?.addEventListener('change', (e) => {
    document.querySelectorAll('.qual-product-cb').forEach(cb => cb.checked = e.target.checked);
    updateQualSelectedCount();
  });

  // 单选
  document.querySelectorAll('.qual-product-cb').forEach(cb => {
    cb.addEventListener('change', updateQualSelectedCount);
  });

  updateQualSelectedCount();
}

function updateQualSelectedCount() {
  const countEl = document.getElementById('qualSelectedCountInfo');
  if (!countEl) return;
  const checked = document.querySelectorAll('.qual-product-cb:checked').length;
  countEl.style.display = checked > 0 ? 'block' : 'none';
  countEl.innerHTML = `已选择 <strong>${checked}</strong> 个商品`;
}

function isQualificationProductPending(product = {}) {
  if (product.status === 'pending') return true;
  if (Array.isArray(product.pendingCertTypes) && product.pendingCertTypes.length > 0) return true;
  return (Array.isArray(product.waitTaskList) ? product.waitTaskList : []).some(task => {
    const meta = getQualificationStatusMeta(task);
    return !/上传完成|已上传|已完成|完成|成功|通过|审核通过/.test(String(meta?.label || ''));
  });
}

async function fetchQualificationProductsForUploadScope(scope = 'selected') {
  if (scope === 'selected') return qualState.products || [];

  const pageSize = Math.max(50, Math.min(500, Number(qualState.total || qualState.pageSize || 50)));
  const result = await safeRuntimeSendMessage({
    action: 'fetchQualificationPool',
    params: {
      pageNum: 1,
      pageSize,
      fetchAll: true,
      uploadStatusList: [0, 1, 2, 3, 4, 5, 6]
    }
  }, { success: false, error: '加载资质商品失败' }, 60000);

  if (!result?.success || !Array.isArray(result.products)) {
    throw new Error(result?.error || '加载资质商品失败');
  }
  return result.products;
}

async function resolveQualificationUploadProducts(scope = 'selected') {
  if (scope === 'selected') {
    const selectedSpus = uniqueStringList(Array.from(document.querySelectorAll('.qual-product-cb:checked')).map(cb => cb.dataset.spu));
    return selectedSpus
      .map(spuId => (qualState.products || []).find(p => String(p.spuId) === String(spuId)))
      .filter(Boolean);
  }

  const products = await fetchQualificationProductsForUploadScope(scope);
  const scopedProducts = scope === 'pending' ? products.filter(isQualificationProductPending) : products;
  const seen = new Set();
  return scopedProducts.filter(product => {
    const spuId = String(product?.spuId || '').trim();
    if (!spuId || seen.has(spuId)) return false;
    seen.add(spuId);
    return true;
  });
}

function renderQualPagination() {
  const info = document.getElementById('qualPaginationInfo');
  const btns = document.getElementById('qualPaginationButtons');
  const container = document.getElementById('qualPaginationContainer');
  if (!info || !btns) return;

  if (container) container.style.display = qualState.total > 0 ? 'flex' : 'none';
  if (qualState.total > 0) {
    info.innerHTML = `共 <strong>${qualState.total}</strong> 条，第 <strong>${qualState.pageNum}</strong>/<strong>${qualState.totalPages}</strong> 页`;
  } else {
    info.innerHTML = '';
  }

  btns.innerHTML = '';
  if (qualState.pageNum > 1) {
    const btn = document.createElement('button');
    btn.className = 'btn btn-default btn-sm'; btn.textContent = '上一页';
    btn.addEventListener('click', () => { qualState.pageNum--; loadQualProductList(); });
    btns.appendChild(btn);
  }
  for (let i = 1; i <= qualState.totalPages; i++) {
    if (i > 5 && i < qualState.totalPages - 1 && Math.abs(i - qualState.pageNum) > 1) {
      if (i === 6 || i === qualState.totalPages - 2) {
        const span = document.createElement('span'); span.textContent = '...'; span.style.padding = '0 4px'; btns.appendChild(span);
      }
      continue;
    }
    const btn = document.createElement('button');
    btn.className = i === qualState.pageNum ? 'btn btn-primary btn-sm' : 'btn btn-default btn-sm';
    btn.textContent = i;
    if (i !== qualState.pageNum) btn.addEventListener('click', () => { qualState.pageNum = i; loadQualProductList(); });
    btns.appendChild(btn);
  }
  if (qualState.pageNum < qualState.totalPages) {
    const btn = document.createElement('button');
    btn.className = 'btn btn-default btn-sm'; btn.textContent = '下一页';
    btn.addEventListener('click', () => { qualState.pageNum++; loadQualProductList(); });
    btns.appendChild(btn);
  }
}

async function startQualUpload() {
  const uploadScope = document.getElementById('qualUploadScope')?.value || 'selected';
  const qualType = document.getElementById('qualTypeSelect')?.value;
  let selectedProducts = [];
  try {
    selectedProducts = await resolveQualificationUploadProducts(uploadScope);
  } catch (err) {
    showToast(`加载上传范围失败：${err.message}`, 'error');
    return;
  }
  if (selectedProducts.length === 0) {
    if (uploadScope === 'selected') {
      showToast('请先在左侧勾选要上传资质的商品，或切换上传范围', 'warning');
    } else if (uploadScope === 'pending') {
      showToast('当前没有待上传资质的商品', 'warning');
    } else {
      showToast('未找到可上传资质的商品数据，请先刷新列表', 'error');
    }
    return;
  }
  const inferCertType = (product = {}) => {
    const pendingTask = (product?.waitTaskList || []).find(task => {
      const meta = getQualificationStatusMeta(task);
      return !/上传完成|已上传|已完成|完成|成功|通过|审核通过/.test(String(meta?.label || ''));
    });
    return qualType
      || product?.pendingCertTypes?.[0]
      || pendingTask?.certType
      || pendingTask?.task_type
      || pendingTask?.taskName
      || pendingTask?.task_name
      || '';
  };
  const groups = new Map();
  selectedProducts.forEach(product => {
    const certType = inferCertType(product);
    const key = certType || '__unknown__';
    if (!groups.has(key)) groups.set(key, []);
    groups.get(key).push(product);
  });

  const progressSection = document.getElementById('qualProgressSection');
  const fill = document.getElementById('qualProgressFill');
  const text = document.getElementById('qualProgressText');
  const list = document.getElementById('qualResultList');
  if (!progressSection || !fill || !text || !list) {
    showToast('资质上传界面未初始化完成，请刷新插件页后重试', 'error');
    return;
  }
  progressSection.style.display = 'block';
  fill.style.width = '0%';
  fill.className = 'progress-fill blue';
  text.textContent = '准备同步资质任务...';
  list.innerHTML = '';

  let success = 0, fail = 0;
  const groupEntries = Array.from(groups.entries());
  for (let i = 0; i < groupEntries.length; i++) {
    const [certTypeKey, groupProducts] = groupEntries[i];
    const spuIds = groupProducts.map(p => String(p.spuId));
    const pct = Math.round(((i + 1) / groupEntries.length) * 100);
    fill.style.width = pct + '%';
    text.textContent = `同步批量资质任务（${i + 1}/${groupEntries.length}）...`;

    const displayLabel = spuIds.length === 1 ? spuIds[0] : `${spuIds[0]} 等 ${spuIds.length} 个 SPU`;

    try {
      if (!certTypeKey || certTypeKey === '__unknown__') {
        throw new Error('未能识别资质类型');
      }
      const result = await safeRuntimeSendMessage({
        action: 'uploadQualificationBatch',
        params: {
          productIds: spuIds,
          certType: certTypeKey,
          qualType: certTypeKey,
          files: qualState.files.map(f => f.name)
        }
      }, { success: false, error: '上传资质批量任务失败' }, 45000);

      const item = document.createElement('div');
      if (result?.success) {
        item.className = 'result-item success';
        item.innerHTML = `<span class="icon">✅</span><span class="reason"><strong>${escapeHtml(displayLabel)}</strong> — 批量任务已创建${result.taskId ? `，Task ${escapeHtml(String(result.taskId))}` : ''}</span>`;
        success += groupProducts.length;
      } else {
        item.className = 'result-item fail';
        item.innerHTML = `<span class="icon">❌</span><span class="reason"><strong>${escapeHtml(displayLabel)}</strong> — ${escapeHtml(result?.error || '失败')}</span>`;
        fail += groupProducts.length;
      }
      list.appendChild(item);
    } catch (err) {
      const item = document.createElement('div');
      item.className = 'result-item fail';
      item.innerHTML = `<span class="icon">❌</span><span class="reason"><strong>${escapeHtml(displayLabel)}</strong> — ${escapeHtml(err.message)}</span>`;
      list.appendChild(item);
      fail += groupProducts.length;
    }

    await new Promise(r => setTimeout(r, 400));
  }

  fill.className = fail === 0 ? 'progress-fill green' : 'progress-fill orange';
  text.textContent = `资质任务完成！成功 ${success}，失败 ${fail}`;
  showToast(`资质任务完成：成功${success}，失败${fail}`, success > 0 ? 'success' : 'error');
  loadQualProductList();
}

// ========== 店铺搬家 Tab ==========
const collectionState = {
  items: [],     // 店铺搬家复制任务列表
  selected: new Set(),  // 选中的商品ID
  sourceProducts: [],
  sourceSelected: new Set(),
  templates: [],
  templateSelected: new Set(),
  successCount: 0,
  failCount: 0,
  draftCount: 0
};

function getCollectionItemKey(item) {
  return item?.id || item?.templateId || item?.goodsId || item?.url || item?.title || '';
}

function upsertCollectionItem(item) {
  const key = getCollectionItemKey(item);
  if (!key) return;
  const index = collectionState.items.findIndex(existing => getCollectionItemKey(existing) === key);
  if (index >= 0) {
    collectionState.items[index] = { ...collectionState.items[index], ...item };
  } else {
    collectionState.items.push(item);
  }
}

function formatCollectionPrice(price) {
  if (price === undefined || price === null || price === '') return '-';
  const num = Number(price);
  if (Number.isFinite(num)) return `¥${num.toFixed(2)}`;
  return String(price);
}

function pickCollectionValue(...values) {
  for (const value of values) {
    if (value === undefined || value === null || value === '') continue;
    return value;
  }
  return '';
}

function normalizeCollectionImages(product = {}) {
  const sources = [
    product.images,
    product.imageList,
    product.productImageList,
    product.skcImages,
    product.goodsImages,
    product.mainImage,
    product.image,
    product.thumbUrl
  ];
  const list = [];
  for (const source of sources) {
    if (!source) continue;
    if (Array.isArray(source)) {
      source.forEach(item => {
        if (typeof item === 'string') list.push(item);
        else if (item && typeof item === 'object') list.push(item.url || item.imageUrl || item.imgUrl || item.image || '');
      });
    } else if (typeof source === 'string') {
      list.push(source);
    } else if (typeof source === 'object') {
      list.push(source.url || source.imageUrl || source.imgUrl || source.image || '');
    }
  }
  return [...new Set(list.map(url => String(url || '').trim()).filter(Boolean))].slice(0, 30);
}

function normalizeCollectionSkus(product = {}) {
  const skuSource = [
    product.skus,
    product.skuList,
    product.productSkuSummaries,
    product.productSkuList,
    product.skuListInfo,
    product.variantList
  ].find(Array.isArray) || [];
  return skuSource.map((sku, index) => ({
    id: pickCollectionValue(sku.id, sku.skuId, sku.productSkuId, sku.goodsSkuId, sku.sku_id, `${index + 1}`),
    name: pickCollectionValue(sku.name, sku.skuName, sku.specText, sku.specName, sku.skuSpecText, sku.goodsSkuName, `SKU ${index + 1}`),
    price: pickCollectionValue(sku.price, sku.salePrice, sku.currentPrice, sku.supplierPrice, product.price),
    stock: pickCollectionValue(sku.stock, sku.stockCnt, sku.availableStock, sku.available_stock, ''),
    image: pickCollectionValue(sku.image, sku.skuImage, sku.thumbUrl, sku.sku_image, '')
  }));
}

function normalizeCollectionProduct(product = {}, source = 'seller-product') {
  const images = normalizeCollectionImages(product);
  const skus = normalizeCollectionSkus(product);
  const goodsId = String(pickCollectionValue(product.goodsId, product.goods_id, product.productId, product.spuId, product.productSpuId, product.goodspu, product.skcId)).trim();
  const spuId = String(pickCollectionValue(product.spuId, product.spu_id, product.productId, product.productSpuId, product.goodspu, goodsId)).trim();
  const title = pickCollectionValue(product.title, product.productName, product.spuName, product.productCNName, product.name, product.goodsName, `商品 ${goodsId || spuId}`.trim());
  return {
    id: product.id || `${source}_${goodsId || spuId || Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
    goodsId,
    spuId,
    title,
    productName: title,
    price: pickCollectionValue(product.price, product.salePrice, product.supplierPrice, product.minPrice, ''),
    description: pickCollectionValue(product.description, product.goodsDesc, product.desc, ''),
    category: pickCollectionValue(product.category, product.categoryName, product.catName, product.categoryNamePath, ''),
    categoryId: pickCollectionValue(product.categoryId, product.catId, product.cat_id, ''),
    categoryPath: pickCollectionValue(product.categoryPath, product.catPath, product.category_path, ''),
    mainImage: images[0] || '',
    images,
    skus,
    sourceType: source,
    raw: product
  };
}

function normalizeProductMoveTemplatePayload(product, mallId = '') {
  const normalized = normalizeCollectionProduct(product, product.sourceType || 'seller-product');
  const sourceProductDetail = product.sourceProductDetail ||
    product.productDetail ||
    product.rawProductDetail ||
    product.lingfengProductDetail ||
    null;
  return {
    ...normalized,
    productId: product.productId || normalized.productId || normalized.spuId || normalized.goodsId || '',
    sourceProductId: product.sourceProductId || product.productId || normalized.spuId || normalized.goodsId || '',
    sourceProductDetail,
    sourceMallId: mallId || normalized.sourceMallId || state.mallId || '',
    sourceMallName: state.mallName || normalized.sourceMallName || '',
    savedAt: new Date().toISOString(),
    platform: 'TEMU'
  };
}

function getCollectionSourceProductKey(product) {
  return String(product?.goodsId || product?.spuId || product?.productId || product?.id || product?.title || '').trim();
}

function getProductTemplateKey(template) {
  return String(template?.id || template?.goodsId || template?.data?.goodsId || template?.name || '').trim();
}

function getMoveProductSourceId(product = {}) {
  return String(
    product.sourceProductId ||
    product.productId ||
    product.spuId ||
    product.goodsId ||
    product.raw?.goodspu ||
    product.raw?.productSpuId ||
    product.raw?.productId ||
    ''
  ).trim();
}

function getMoveDetailLeafCategory(productDetail = {}) {
  const categories = productDetail.categories || {};
  if (categories && typeof categories === 'object') {
    const catKeys = Object.keys(categories)
      .filter(key => /^cat\d+$/.test(key))
      .map(key => Number(key.replace('cat', '')))
      .filter(Number.isFinite)
      .sort((a, b) => b - a);
    for (const key of catKeys) {
      const cat = categories[`cat${key}`];
      const catId = cat?.catId || cat?.categoryId || cat?.id || '';
      if (catId) {
        return {
          categoryId: String(catId),
          categoryName: cat?.catName || cat?.categoryName || cat?.name || '',
          categoryPath: catKeys
            .slice()
            .reverse()
            .map(index => categories[`cat${index}`]?.catName || categories[`cat${index}`]?.categoryName || '')
            .filter(Boolean)
            .join(' / ')
        };
      }
    }
  }
  return {
    categoryId: String(productDetail.categoryId || productDetail.catId || productDetail.leafCatId || ''),
    categoryName: productDetail.categoryName || productDetail.catName || '',
    categoryPath: ''
  };
}

function summarizeMoveProductDetail(productDetail = {}, fallback = {}) {
  const images = [];
  const pushImage = (url) => {
    const text = String(url || '').trim();
    if (text && !images.includes(text)) images.push(text);
  };
  (productDetail.carouselImageUrls || []).forEach(pushImage);
  pushImage(productDetail.materialImgUrl);
  (productDetail.productSkcList || []).forEach(skc => {
    pushImage(skc.colorImageUrl);
    (skc.previewImgUrls || []).forEach(pushImage);
    (skc.productSkcImageExtList || []).forEach(item => pushImage(item?.imageUrl || item?.url));
    (skc.productSkuList || []).forEach(sku => {
      pushImage(sku.thumbUrl);
      (sku.productSkuThumbUrlI18nVOList || []).forEach(item => pushImage(item?.imageUrl || item?.url));
    });
  });

  const skus = [];
  (productDetail.productSkcList || []).forEach(skc => {
    (skc.productSkuList || []).forEach((sku, index) => {
      const specName = (sku.productSkuSpecList || [])
        .map(spec => spec?.specValue || spec?.value || spec?.name || spec?.parentSpecName || '')
        .filter(Boolean)
        .join(' / ');
      skus.push({
        id: sku.productSkuId || sku.skuId || `${skc.productSkcId || 'skc'}_${index + 1}`,
        name: specName || sku.extCode || `SKU ${index + 1}`,
        price: sku.supplierPrice || sku.siteSupplierPrices?.[0]?.supplierPrice || fallback.price || '',
        stock: sku.productSkuQuantity || '',
        image: sku.thumbUrl || skc.colorImageUrl || ''
      });
    });
  });

  const category = getMoveDetailLeafCategory(productDetail);
  return {
    productId: productDetail.productId || fallback.productId || fallback.spuId || fallback.goodsId || '',
    title: productDetail.productName || fallback.title || fallback.productName || '',
    productName: productDetail.productName || fallback.productName || fallback.title || '',
    categoryId: category.categoryId || fallback.categoryId || '',
    category: category.categoryName || fallback.category || '',
    categoryPath: category.categoryPath || fallback.categoryPath || '',
    mainImage: images[0] || fallback.mainImage || '',
    images: images.length ? images.slice(0, 60) : (fallback.images || []),
    skus: skus.length ? skus : (fallback.skus || [])
  };
}

async function hydrateMoveSourceProduct(product, logList = null) {
  const sourceProductId = getMoveProductSourceId(product);
  if (!sourceProductId) {
    throw new Error('缺少 SPU ID，无法按凌风逻辑读取商品详情');
  }
  const detailResp = await safeRuntimeSendMessage({
    type: 'FETCH_MOVE_PRODUCT_DETAIL',
    productId: sourceProductId
  }, { success: false, error: '读取源商品详情失败' }, 40000);
  if (!detailResp?.success) {
    throw new Error(detailResp?.error || '读取源商品详情失败');
  }
  const detail = detailResp.productDetail || detailResp.data || detailResp.result || null;
  if (!detail || typeof detail !== 'object') {
    throw new Error('源商品详情为空');
  }
  const summary = summarizeMoveProductDetail(detail, product);
  addCollectionLog(logList, `🔎 ${product.title || product.goodsId || sourceProductId} - 已读取凌风同款商品详情`, 'success');
  return {
    ...product,
    ...summary,
    sourceProductId,
    sourceProductDetail: detail,
    lingfengReady: true
  };
}

function renderCollectionSourceProducts() {
  const container = document.getElementById('collectionSourceProductList');
  const countEl = document.getElementById('collectionSourceCount');
  if (!container) return;
  const query = (document.getElementById('collectionSourceSearchInput')?.value || '').trim().toLowerCase();
  const products = collectionState.sourceProducts.filter(product => {
    if (!query) return true;
    return [
      product.title,
      product.productName,
      product.goodsId,
      product.spuId,
      product.category
    ].some(value => String(value || '').toLowerCase().includes(query));
  });
  if (countEl) countEl.textContent = `源店商品 ${collectionState.sourceProducts.length} 个，已选 ${collectionState.sourceSelected.size} 个`;
  if (!products.length) {
    container.innerHTML = '<div class="empty" style="padding:18px;color:var(--text-muted);font-size:13px;">暂无源店商品，或筛选条件无匹配</div>';
    return;
  }
  container.innerHTML = products.map((product, idx) => {
    const key = getCollectionSourceProductKey(product);
    const image = product.mainImage || product.images?.[0] || '';
    return `
      <label style="display:grid;grid-template-columns:34px 54px minmax(0,1fr) auto;gap:10px;align-items:center;padding:10px 12px;border-bottom:1px solid var(--border);cursor:pointer;">
        <input type="checkbox" class="checkbox collection-source-check" data-key="${escapeHtml(key)}" ${collectionState.sourceSelected.has(key) ? 'checked' : ''}>
        <img src="${escapeHtml(image)}" class="img-fallback" data-fallback-size="54" style="width:54px;height:54px;object-fit:cover;border-radius:8px;border:1px solid var(--border);background:#f8fafc;">
        <span style="min-width:0;">
          <span style="display:block;font-size:13px;font-weight:700;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${escapeHtml(product.title || '未知商品')}</span>
          <span style="display:block;margin-top:4px;font-size:12px;color:var(--text-muted);">SPU：${escapeHtml(product.spuId || product.productId || '-')} ｜ SKC：${escapeHtml(product.goodsId || product.skcId || '-')} ｜ SKU ${escapeHtml(String(product.skus?.length || 0))}</span>
        </span>
        <button type="button" class="btn btn-default btn-sm collection-source-add-one" data-key="${escapeHtml(key)}">保存模板</button>
      </label>
    `;
  }).join('');
  container.querySelectorAll('.collection-source-check').forEach(cb => {
    cb.addEventListener('change', event => {
      const key = event.currentTarget.dataset.key;
      if (event.currentTarget.checked) collectionState.sourceSelected.add(key);
      else collectionState.sourceSelected.delete(key);
      renderCollectionSourceProducts();
    });
  });
  container.querySelectorAll('.collection-source-add-one').forEach(btn => {
    btn.addEventListener('click', async event => {
      event.preventDefault();
      const product = collectionState.sourceProducts.find(item => getCollectionSourceProductKey(item) === event.currentTarget.dataset.key);
      if (product) await saveSelectedSourceProducts([product]);
    });
  });
}

async function loadCollectionSourceProducts() {
  if (!requireAdminFeature('店铺搬家')) return;
  if (!state.mallId) return showToast('请先连接源店铺', 'error');
  const btn = document.getElementById('collectionLoadSourceBtn');
  const oldText = btn?.textContent || '刷新源店商品';
  try {
    if (btn) { btn.disabled = true; btn.textContent = '加载中...'; }
    const result = await safeRuntimeSendMessage({
      type: 'FETCH_MOVE_SOURCE_PRODUCTS',
      params: { pageNo: 1, pageSize: 100 }
    }, { success: false, error: '加载源店商品失败' }, 45000);
    if (!result?.success) throw new Error(result?.error || '加载源店商品失败');
    collectionState.sourceProducts = (Array.isArray(result.products) ? result.products : []).map(item => normalizeCollectionProduct(item, 'seller-product'));
    collectionState.sourceSelected.clear();
    renderCollectionSourceProducts();
    showToast(`已加载源店商品 ${collectionState.sourceProducts.length} 个`, 'success');
  } catch (error) {
    showToast(`加载源店商品失败：${error.message || error}`, 'error');
  } finally {
    if (btn) { btn.disabled = false; btn.textContent = oldText; }
  }
}

function resetCollectionRunCounters() {
  collectionState.successCount = 0;
  collectionState.failCount = 0;
  collectionState.draftCount = 0;
}

async function saveSelectedSourceProducts(productsOverride = null) {
  if (!requireAdminFeature('店铺搬家')) return;
  const products = Array.isArray(productsOverride)
    ? productsOverride
    : collectionState.sourceProducts.filter(item => collectionState.sourceSelected.has(getCollectionSourceProductKey(item)));
  if (!products.length) return showToast('请先选择要保存的源店商品', 'error');
  const logList = document.getElementById('collectionLogList');
  const progressSection = document.getElementById('collectionProgressSection');
  const progressText = document.getElementById('collectionProgressText');
  const progressFill = document.getElementById('collectionProgressFill');
  if (progressSection) progressSection.style.display = '';
  if (progressText) progressText.textContent = `正在保存 ${products.length} 个商品模板...`;
  if (progressFill) progressFill.style.width = '0%';
  if (logList) logList.innerHTML = '';
  let success = 0;
  let fail = 0;
  for (let i = 0; i < products.length; i += 1) {
    const product = products[i];
    if (progressFill) progressFill.style.width = `${Math.round(((i + 1) / products.length) * 100)}%`;
    setCollectionProgressStage(`读取商品详情 ${i + 1}/${products.length}`);
    try {
      const detailedProduct = await hydrateMoveSourceProduct(product, logList);
      setCollectionProgressStage(`保存模板 ${i + 1}/${products.length}`);
      const result = await saveProductMoveTemplate(detailedProduct);
      if (!result.success) throw new Error(result.error || '保存失败');
      success += 1;
      addCollectionLog(logList, `✅ ${detailedProduct.title || detailedProduct.goodsId || '商品'} - 已保存完整商品模板`, 'success');
    } catch (error) {
      fail += 1;
      addCollectionLog(logList, `❌ ${product.title || product.goodsId || '商品'} - ${error.message || error}`, 'error');
    }
  }
  if (progressText) progressText.textContent = `商品模板保存完成：成功 ${success} 个，失败 ${fail} 个`;
  setCollectionProgressStage('模板保存完成');
  showToast(`商品模板保存完成：成功 ${success}，失败 ${fail}`, success > 0 ? 'success' : 'error');
  await loadCollectionProductTemplates();
}

function renderCollectionTemplateStats() {
  const stats = document.getElementById('collectionTemplateStats');
  if (!stats) return;
  stats.innerHTML = `
    <span style="display:inline-flex;align-items:center;padding:4px 10px;border-radius:999px;background:#eef2ff;color:#3730a3;">模板 ${collectionState.templates.length} 个</span>
    <span style="display:inline-flex;align-items:center;padding:4px 10px;border-radius:999px;background:#ecfdf3;color:#027a48;">已选 ${collectionState.templateSelected.size} 个</span>
    <span style="display:inline-flex;align-items:center;padding:4px 10px;border-radius:999px;background:#fff7ed;color:#c2410c;">当前目标店 ${escapeHtml(state.mallId || '未连接')}</span>
  `;
}

function renderCollectionTemplates() {
  const container = document.getElementById('collectionTemplateList');
  if (!container) return;
  renderCollectionTemplateStats();
  if (!collectionState.templates.length) {
    container.innerHTML = '<div class="empty" style="padding:18px;color:var(--text-muted);font-size:13px;">暂无商品模板，请先从源店保存模板</div>';
    return;
  }
  container.innerHTML = collectionState.templates.map(template => {
    const key = getProductTemplateKey(template);
    const image = template.mainImage || template.data?.mainImage || template.data?.images?.[0] || '';
    const disabledSameShop = state.mallId && template.sourceMallId && String(template.sourceMallId) === String(state.mallId);
    const hasDetail = Boolean(template.data?.sourceProductDetail || template.data?.productDetail || template.data?.rawProductDetail);
    const sourceProductId = template.data?.sourceProductId || template.spuId || template.goodsId || '-';
    return `
      <div style="display:grid;grid-template-columns:34px 54px minmax(0,1fr) auto;gap:10px;align-items:center;padding:10px 12px;border-bottom:1px solid var(--border);${disabledSameShop ? 'background:#fff7ed;' : ''}">
        <input type="checkbox" class="checkbox collection-template-check" data-key="${escapeHtml(key)}" ${collectionState.templateSelected.has(key) ? 'checked' : ''}>
        <img src="${escapeHtml(image)}" class="img-fallback" data-fallback-size="54" style="width:54px;height:54px;object-fit:cover;border-radius:8px;border:1px solid var(--border);background:#f8fafc;">
        <div style="min-width:0;">
          <div style="font-size:13px;font-weight:700;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${escapeHtml(template.productName || template.name || '未命名模板')}</div>
          <div style="margin-top:4px;font-size:12px;color:var(--text-muted);">源店：${escapeHtml(template.sourceMallName || template.sourceMallId || '-')} ｜ SPU：${escapeHtml(String(sourceProductId))} ｜ SKU ${escapeHtml(String(template.skuCount || template.data?.skus?.length || 0))} ｜ ${hasDetail ? '凌风详情已保存' : '缺少完整详情'}</div>
          ${disabledSameShop ? '<div style="margin-top:4px;font-size:11px;color:#c2410c;">当前连接的是源店，建议切换到目标店铺后再搬家</div>' : ''}
          ${!hasDetail ? '<div style="margin-top:4px;font-size:11px;color:#b42318;">旧模板缺少完整商品详情，请回源店重新保存后再复制</div>' : ''}
        </div>
        <div style="display:flex;gap:8px;align-items:center;">
          <button type="button" class="btn btn-primary btn-sm collection-template-add-one" data-key="${escapeHtml(key)}" ${hasDetail ? '' : 'disabled'}>加入复制</button>
          <button type="button" class="btn btn-danger btn-sm collection-template-delete" data-id="${escapeHtml(String(template.id || ''))}">删除</button>
        </div>
      </div>
    `;
  }).join('');
  container.querySelectorAll('.collection-template-check').forEach(cb => {
    cb.addEventListener('change', event => {
      const key = event.currentTarget.dataset.key;
      if (event.currentTarget.checked) collectionState.templateSelected.add(key);
      else collectionState.templateSelected.delete(key);
      renderCollectionTemplateStats();
    });
  });
  container.querySelectorAll('.collection-template-add-one').forEach(btn => {
    btn.addEventListener('click', event => {
      const template = collectionState.templates.find(item => getProductTemplateKey(item) === event.currentTarget.dataset.key);
      if (template) addTemplatesToMoveTasks([template]);
    });
  });
  container.querySelectorAll('.collection-template-delete').forEach(btn => {
    btn.addEventListener('click', async event => {
      const id = event.currentTarget.dataset.id;
      if (!id) return;
      const result = await deleteProductMoveTemplate(id);
      if (!result.success) return showToast(result.error || '删除模板失败', 'error');
      collectionState.templates = collectionState.templates.filter(item => String(item.id) !== String(id));
      collectionState.templateSelected.delete(id);
      renderCollectionTemplates();
      showToast('商品模板已删除', 'success');
    });
  });
}

async function loadCollectionProductTemplates() {
  const btn = document.getElementById('collectionTemplateRefreshBtn');
  const oldText = btn?.textContent || '刷新模板';
  try {
    if (btn) { btn.disabled = true; btn.textContent = '刷新中...'; }
    const result = await fetchProductMoveTemplates();
    if (!result.success) throw new Error(result.error || '加载商品模板失败');
    collectionState.templates = result.templates || [];
    collectionState.templateSelected.clear();
    renderCollectionTemplates();
  } catch (error) {
    showToast(`加载商品模板失败：${error.message || error}`, 'error');
  } finally {
    if (btn) { btn.disabled = false; btn.textContent = oldText; }
  }
}

function buildMoveTaskFromTemplate(template) {
  const data = template?.data || {};
  const item = normalizeCollectionProduct(data.product || data, 'product-template');
  item.id = `tpl_${template.id}_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
  item.templateId = template.id;
  item.templateName = template.name;
  item.productId = data.productId || data.sourceProductId || item.productId || item.spuId || item.goodsId || '';
  item.sourceProductId = data.sourceProductId || data.productId || item.spuId || item.goodsId || '';
  item.sourceProductDetail = data.sourceProductDetail || data.productDetail || data.rawProductDetail || null;
  item.lingfengReady = Boolean(item.sourceProductDetail);
  item.sourceMallId = template.sourceMallId || data.sourceMallId || '';
  item.sourceMallName = template.sourceMallName || data.sourceMallName || '';
  item.url = data.sourceUrl || data.url || '';
  item.sourceUrl = data.sourceUrl || data.url || '';
  item.collectionProgress = {
    capture: 'done',
    category: item.category || item.categoryId || item.lingfengReady ? 'done' : 'pending',
    spec: item.skus?.length || item.lingfengReady ? 'done' : 'pending',
    draft: 'pending',
    submit: 'pending'
  };
  return item;
}

function addTemplatesToMoveTasks(templates) {
  const repeat = Math.max(1, Math.min(20, Number(document.getElementById('collectionRepeatInput')?.value || 1) || 1));
  let added = 0;
  for (const template of templates) {
    for (let i = 0; i < repeat; i += 1) {
      const item = buildMoveTaskFromTemplate(template);
      item.repeatIndex = i + 1;
      if (repeat > 1) item.title = `${item.title || template.name || '商品'} - 副本${i + 1}`;
      collectionState.items.push(item);
      collectionState.selected.add(getCollectionItemKey(item));
      added += 1;
    }
  }
  renderCollectionResults();
  showToast(`已加入 ${added} 个复制任务`, 'success');
}

const qualificationNameMap = {
  'cpc': '美国CPSC儿童产品安全认证',
  'kc safety check(children)': '韩国KC安全确认(儿童产品)',
  'kc supplier conformity check(children)': '韩国KC供应商符合性确认(儿童产品)',
  'rsl-phthalate': '欧盟RSL邻苯项目检测报告',
  'ccpsa': '加拿大消费品安全检测报告',
  'gcc': '美国一般商品合格证书',
  'uk furniture flammability': '英国家具阻燃性',
  'eu doc': '欧盟符合性声明',
  'eu doc declaration': '欧盟符合性声明',
  'doc': '符合性声明',
  'cpc': '儿童产品证书',
  'ce': 'CE 认证',
  'fsc': 'FSC 认证',
  'rohs': 'RoHS 认证',
  'reach': 'REACH 认证',
  'test report': '检测报告',
  'inspection report': '检测报告',
  'product certificate': '产品证书',
  'quality certificate': '质量证书',
  'packaging material': '包装材料信息',
  '资质': '资质'
};

function getQualificationRawName(task = {}) {
  return String(task.certName || task.showName || task.task_name || task.task_type || '').trim();
}

function pickQualificationValue(...values) {
  for (const value of values) {
    if (value === undefined || value === null) continue;
    if (typeof value === 'string') {
      const text = value.trim();
      if (text) return text;
      continue;
    }
    if (typeof value === 'number' && Number.isFinite(value)) return value;
    if (typeof value === 'boolean') return value;
  }
  return undefined;
}

function translateQualificationName(task = {}) {
  const raw = getQualificationRawName(task);
  if (!raw) return '资质';
  if (/[\u4e00-\u9fa5]/.test(raw)) return raw;
  const lower = raw.toLowerCase();
  for (const [key, value] of Object.entries(qualificationNameMap)) {
    if (lower === key || lower.includes(key)) {
      return value;
    }
  }
  return raw;
}

function getQualificationStatusMeta(task = {}) {
  const failureReason = pickQualificationValue(
    task.raw?.failure_reason,
    task.raw?.error_message,
    task.raw?.error_msg,
    task.raw?.reject_reason,
    task.raw?.rejectReason,
    task.failure_reason,
    task.error_message,
    task.errorMsg,
    task.reject_reason,
    task.rejectReason
  );
  if (failureReason) {
    return { label: '上传失败', tone: 'danger', detail: String(failureReason) };
  }

  const rawText = pickQualificationValue(
    task.raw?.reject_reason,
    task.raw?.rejectReason,
    task.reject_reason,
    task.rejectReason,
    task.statusText,
    task.statusLabel,
    task.status_name,
    task.statusName,
    task.statusDesc,
    task.status_desc,
    task.uploadStatusText,
    task.upload_status_text,
    task.uploadStatusLabel,
    task.upload_status_label,
    task.uploadStatusDesc,
    task.upload_status_desc,
    task.auditStatusText,
    task.audit_status_text,
    task.auditStatusDesc,
    task.audit_status_desc,
    task.raw?.status_text,
    task.raw?.statusText,
    task.raw?.status_name,
    task.raw?.statusName,
    task.raw?.status_desc,
    task.raw?.statusDesc,
    task.raw?.upload_status_text,
    task.raw?.uploadStatusText,
    task.raw?.upload_status_name,
    task.raw?.uploadStatusName,
    task.raw?.upload_status_desc,
    task.raw?.uploadStatusDesc,
    task.raw?.audit_status_text,
    task.raw?.auditStatusText,
    task.raw?.audit_status_name,
    task.raw?.auditStatusName,
    task.raw?.audit_status_desc,
    task.raw?.auditStatusDesc,
    task.raw?.display_status,
    task.raw?.displayStatus,
    task.raw?.process_status,
    task.raw?.processStatus
  );
  if (typeof rawText === 'string' && rawText) {
    const normalizedText = rawText.trim().toLowerCase();
    if (/^状态\s*\d+$/i.test(rawText.trim())) {
      // Ignore generated labels like "状态1" and use numeric mapping below.
    } else {
      if (/失败|驳回|拒绝|不通过|failed|rejected|refused|error|not\s*pass|declined/.test(rawText) || /failed|rejected|refused|error|not\s*pass|declined/.test(normalizedText)) {
        return { label: '上传失败', tone: 'danger', detail: rawText };
      }
      if (/完成|已上传|已完成|成功|通过|审核通过|uploaded|passed|success|done|approved|complete/.test(rawText) || /uploaded|passed|success|done|approved|complete/.test(normalizedText)) {
        return { label: '上传完成', tone: 'success', detail: rawText };
      }
      if (/审核中|待审|待审核|review|processing|pending|in\s*review|reviewing/.test(rawText) || /review|processing|pending|in\s*review|reviewing/.test(normalizedText)) {
        return { label: '审核中', tone: 'warning', detail: rawText };
      }
      if (/待上传|未开始|not started|to be uploaded|waiting|to\s*upload/.test(rawText) || /not started|to be uploaded|waiting|to\s*upload/.test(normalizedText)) {
        return { label: '待上传', tone: 'warning', detail: rawText };
      }
      if (/废弃|失效|作废|expired|abandoned|expired/.test(rawText) || /expired|abandoned/.test(normalizedText)) {
        return { label: '已废弃', tone: 'muted', detail: rawText };
      }
      return {
        label: rawText,
        tone: 'muted',
        detail: ''
      };
    }
  }

  const rawStatus = pickQualificationValue(
    task.statusCode,
    task.uploadStatus,
    task.status,
    task.upload_status,
    task.task_status,
    task.raw?.status_code,
    task.raw?.statusCode,
    task.raw?.upload_status_code,
    task.raw?.uploadStatusCode,
    task.raw?.task_status_code,
    task.raw?.taskStatusCode,
    task.raw?.upload_status,
    task.raw?.uploadStatus,
    task.raw?.status,
    task.raw?.task_status
  );
  const statusNum = Number(rawStatus);
  if (Number.isFinite(statusNum)) {
    if ([3, 4, 7, 8].includes(statusNum)) return { label: '上传完成', tone: 'success', detail: '' };
    if ([2, 9, 10].includes(statusNum)) return { label: '审核中', tone: 'warning', detail: '' };
    if ([0, 1, 6].includes(statusNum)) return { label: '待上传', tone: 'warning', detail: '' };
    if ([5, 11, 12].includes(statusNum)) return { label: '上传失败', tone: 'danger', detail: '' };
    return { label: '待识别', tone: 'muted', detail: '' };
  }

  if (task.raw?.cert_file_list?.length) {
    return { label: '上传完成', tone: 'success', detail: 'cert_file_list' };
  }

  return { label: '待识别', tone: 'muted', detail: '' };
}

function renderQualificationTaskRows(tasks = []) {
  return tasks.map(t => {
    const statusMeta = getQualificationStatusMeta(t);
    return {
      rawName: getQualificationRawName(t) || '—',
      chineseLabel: translateQualificationName(t) || '—',
      statusText: statusMeta.label,
      statusDetail: statusMeta.detail || ''
    };
  });
}

function getCollectionItemProgress(item) {
  return item?.collectionProgress || {
    capture: item ? 'done' : 'pending',
    category: item?.recommendedCategory?.categoryName ? 'done' : 'pending',
    spec: item?.specTemplate ? 'done' : 'pending',
    draft: item?.draftStatus || 'pending',
    submit: item?.submitStatus || 'pending'
  };
}

function validateCollectionReady(item, mode = 'draft') {
  if (item?.sourceProductDetail) {
    return { ok: true, missing: [], message: '' };
  }
  const missing = [];
  const targetCategory = (document.getElementById('collectionTargetCategoryInput')?.value || '').trim();
  if (!String(item?.title || '').trim()) missing.push('标题');
  if (!String(item?.categoryId || item?.category || item?.recommendedCategory?.categoryId || targetCategory).trim()) missing.push('类目');
  if (!Array.isArray(item?.images) || item.images.length === 0) missing.push('图片');
  if (!Array.isArray(item?.skus) || item.skus.length === 0) missing.push('SKU');
  return {
    ok: missing.length === 0,
    missing,
    message: missing.length ? `缺少${missing.join('、')}，请先补全后再${mode === 'submit' ? '提交上架' : '创建草稿'}` : ''
  };
}

function buildCollectionPayload(item, lotId = '') {
  const targetCategory = (document.getElementById('collectionTargetCategoryInput')?.value || '').trim();
  const targetSite = (document.getElementById('collectionTargetSiteInput')?.value || '').trim();
  const moveMode = 'draft';
  return {
    lotId,
    goodsId: item.goodsId,
    productId: item.productId || item.sourceProductId || item.spuId || item.goodsId || '',
    sourceProductId: item.sourceProductId || item.productId || item.spuId || item.goodsId || '',
    spuId: item.spuId || '',
    title: item.title,
    price: item.price,
    description: item.description,
    category: targetCategory || item.category,
    categoryId: item.categoryId || item.recommendedCategory?.categoryId || '',
    categoryName: targetCategory || item.recommendedCategory?.categoryName || item.category || '',
    categoryPath: item.categoryPath || item.recommendedCategory?.path || '',
    specTemplate: item.specTemplate || item.specTemplateDetail || '',
    goodsMetadata: item.goodsMetadata || null,
    images: item.images || [],
    skus: item.skus || [],
    sourceUrl: item.url || item.sourceUrl,
    sourceType: item.sourceType || 'buyer-url',
    sourceMallId: item.sourceMallId || '',
    sourceMallName: item.sourceMallName || '',
    sourceProductDetail: item.sourceProductDetail || null,
    lingfengReady: Boolean(item.sourceProductDetail || item.lingfengReady),
    templateId: item.templateId || '',
    targetMallId: state.mallId || '',
    targetMallName: state.mallName || '',
    targetSite,
    moveMode,
    platform: 'TEMU'
  };
}

function getCollectionStepLabel(status) {
  switch (status) {
    case 'done': return '已完成';
    case 'running': return '进行中';
    case 'error': return '失败';
    default: return '待处理';
  }
}

function getCollectionCardBadges(item) {
  const badges = [];
  if (item.sourceProductDetail || item.lingfengReady) badges.push('凌风复制');
  if (item.sourceProductId || item.productId) badges.push(`SPU ${item.sourceProductId || item.productId}`);
  if (item.goodsId) badges.push(`商品ID ${item.goodsId}`);
  if (item.price !== undefined && item.price !== null && item.price !== '') badges.push(`售价 ${formatCollectionPrice(item.price)}`);
  badges.push(`SKU ${item.skus?.length || 0}`);
  badges.push(`图片 ${item.images?.length || 0}`);
  if (item.category) badges.push(String(item.category));
  if (item.recommendedCategory?.categoryName) badges.push(`推荐类目 ${item.recommendedCategory.categoryName}`);
  if (item.categorySource) badges.push(`类目来源 ${item.categorySource === 'recommended' ? '推荐' : item.categorySource === 'metadata' ? '元数据' : '当前'}`);
  if (item.specTemplate) badges.push(`规格模板 ${item.specTemplate}`);
  return badges;
}

function updateCollectionStats() {
  const stats = document.getElementById('collectionStats');
  if (!stats) return;
  const total = collectionState.items.length;
  const selected = collectionState.selected.size;
  const repeat = document.getElementById('collectionRepeatInput')?.value || '1';
  stats.innerHTML = `
    <span style="display:inline-flex;align-items:center;padding:4px 10px;border-radius:999px;background:#eef2ff;color:#3730a3;">任务 ${total} 个</span>
    <span style="display:inline-flex;align-items:center;padding:4px 10px;border-radius:999px;background:#ecfdf3;color:#027a48;">已选中 ${selected} 个</span>
    <span style="display:inline-flex;align-items:center;padding:4px 10px;border-radius:999px;background:#fff7ed;color:#c2410c;">成功 ${collectionState.successCount} 个</span>
    <span style="display:inline-flex;align-items:center;padding:4px 10px;border-radius:999px;background:#fef2f2;color:#b42318;">失败 ${collectionState.failCount} 个</span>
    <span style="display:inline-flex;align-items:center;padding:4px 10px;border-radius:999px;background:#eff6ff;color:#1d4ed8;">草稿 ${collectionState.draftCount} 个</span>
    <span style="display:inline-flex;align-items:center;padding:4px 10px;border-radius:999px;background:#f8fafc;color:#475467;">凌风草稿模式 / 重复 ${escapeHtml(repeat)}</span>
  `;
}

function setCollectionProgressStage(text) {
  const stage = document.getElementById('collectionProgressStage');
  if (stage) stage.textContent = `当前步骤：${text || '-'}`;
}

setTimeout(() => updateCollectionStats(), 0);

function addCollectionLog(container, text, type) {
  if (!container) return;
  const div = document.createElement('div');
  div.style.cssText = `font-size:12px;padding:4px 8px;margin:2px 0;border-radius:4px;${type === 'error' ? 'color:#EF4444;background:#FEF2F2;' : 'color:#10B981;background:#F0FDF4;'}`;
  div.textContent = text;
  container.appendChild(div);
  container.scrollTop = container.scrollHeight;
}

function renderCollectionResults() {
  const container = document.getElementById('collectionResultList');
  if (!container) return;
  updateCollectionStats();

  if (collectionState.items.length === 0) {
    container.innerHTML = '<div class="empty" style="padding:20px;color:var(--text-muted);font-size:13px;">暂无复制任务，请从模板库加入商品</div>';
    return;
  }

  container.innerHTML = collectionState.items.map((item, idx) => {
    const badges = getCollectionCardBadges(item);
    const image = item.images?.[0] || item.mainImage || item.image || '';
    const isSelected = collectionState.selected.has(getCollectionItemKey(item));
    const progress = getCollectionItemProgress(item);
    const progressChip = (label, status) => {
      const colors = status === 'done'
        ? 'background:#ecfdf3;color:#027a48;'
        : status === 'running'
          ? 'background:#eff6ff;color:#1d4ed8;'
          : status === 'error'
            ? 'background:#fef2f2;color:#b42318;'
            : 'background:#f4f6f8;color:#667085;';
      return `<span style="display:inline-flex;align-items:center;padding:3px 8px;border-radius:999px;${colors}">${escapeHtml(label)}：${getCollectionStepLabel(status)}</span>`;
    };
    return `
    <div style="display:flex;gap:12px;padding:12px;border:1px solid var(--border);border-radius:12px;margin-bottom:10px;align-items:flex-start;background:var(--surface);box-shadow:0 1px 2px rgba(0,0,0,.04);">
      <input type="checkbox" class="checkbox collection-item-check" data-idx="${idx}" ${isSelected ? 'checked' : ''} style="margin-top:6px;">
      <img src="${image}" class="img-fallback" style="width:72px;height:72px;object-fit:cover;border-radius:10px;border:1px solid var(--border);background:#fafafa;">
      <div style="flex:1;min-width:0;">
        <div style="display:flex;justify-content:space-between;gap:10px;align-items:flex-start;">
          <div style="min-width:0;flex:1;">
            <div style="font-size:14px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${escapeHtml(item.title || '未知商品')}</div>
            <div style="margin-top:4px;font-size:12px;color:var(--text-muted);display:flex;flex-wrap:wrap;gap:8px;">
              <span>商品ID：${escapeHtml(String(item.goodsId || '-'))}</span>
              <span>价格：${escapeHtml(formatCollectionPrice(item.price))}</span>
              <span>SKU：${escapeHtml(String(item.skus?.length || 0))}</span>
              <span>图片：${escapeHtml(String(item.images?.length || 0))}</span>
            </div>
          </div>
           <div style="display:flex;gap:8px;flex-shrink:0;">
             <button class="btn btn-default btn-sm collection-edit-btn" data-idx="${idx}">编辑</button>
             ${item.editUrl ? `<button class="btn btn-primary btn-sm collection-open-draft-btn" data-idx="${idx}">打开草稿</button>` : ''}
             <button class="btn btn-danger btn-sm collection-remove-btn" data-idx="${idx}">移除</button>
           </div>
        </div>
        <div style="margin-top:8px;display:flex;flex-wrap:wrap;gap:6px;">
          ${badges.map(tag => `<span style="display:inline-flex;align-items:center;padding:3px 8px;border-radius:999px;background:#f4f6f8;color:#475467;font-size:11px;line-height:1;">${escapeHtml(tag)}</span>`).join('')}
        </div>
        <div style="margin-top:8px;display:flex;flex-wrap:wrap;gap:10px;font-size:11px;color:var(--text-secondary);">
          <span>推荐类目：${escapeHtml(item.recommendedCategory?.categoryName || item.category || '—')}</span>
          <span>规格模板：${escapeHtml(item.specTemplate || item.specTemplateDetail || '—')}</span>
        </div>
        <div style="margin-top:8px;display:flex;flex-wrap:wrap;gap:6px;font-size:11px;">
          ${progressChip('采集', progress.capture)}
          ${progressChip('类目', progress.category)}
          ${progressChip('规格', progress.spec)}
          ${progressChip('复制草稿', progress.draft)}
        </div>
        <div style="margin-top:8px;font-size:11px;color:var(--text-muted);word-break:break-all;">${escapeHtml(item.editUrl || item.url || item.sourceUrl || '')}</div>
      </div>
    </div>
  `;
  }).join('');

  // 绑定复选框
  container.querySelectorAll('.collection-item-check').forEach(cb => {
    cb.addEventListener('change', (e) => {
      const item = collectionState.items[+e.target.dataset.idx];
      const id = getCollectionItemKey(item);
      if (e.target.checked && id) collectionState.selected.add(id);
      else collectionState.selected.delete(id);
      updateCollectionStats();
    });
  });

  // 绑定编辑按钮
  container.querySelectorAll('.collection-edit-btn').forEach(btn => {
    btn.addEventListener('click', (e) => {
      const idx = +e.target.dataset.idx;
      const item = collectionState.items[idx];
      if (item) {
        const nextTitle = window.prompt('修改商品名称', item.title || '');
        if (nextTitle !== null) item.title = nextTitle.trim() || item.title;
        const nextPrice = window.prompt('修改价格', item.price ?? '');
        if (nextPrice !== null && nextPrice.trim() !== '') item.price = nextPrice.trim();
        const nextCategory = window.prompt('修改分类', item.category || '');
        if (nextCategory !== null) item.category = nextCategory.trim() || item.category;
        renderCollectionResults();
        showToast('商品信息已更新', 'success');
      }
    });
  });

  container.querySelectorAll('.collection-open-draft-btn').forEach(btn => {
    btn.addEventListener('click', async (e) => {
      const item = collectionState.items[+e.target.dataset.idx];
      const url = item?.editUrl || '';
      if (!url) return showToast('暂无草稿链接', 'error');
      window.open(url, '_blank');
    });
  });

  container.querySelectorAll('.collection-remove-btn').forEach(btn => {
    btn.addEventListener('click', (e) => {
      const idx = +e.target.dataset.idx;
      const item = collectionState.items[idx];
      if (!item) return;
      const key = getCollectionItemKey(item);
      collectionState.items.splice(idx, 1);
      collectionState.selected.delete(key);
      renderCollectionResults();
      showToast('已移除该商品', 'info');
    });
  });
}

// 创建草稿
async function createCollectionDrafts() {
  if (!requireAdminFeature('店铺搬家')) return;
  const selectedItems = collectionState.items.filter(item => collectionState.selected.has(getCollectionItemKey(item)));
  if (selectedItems.length === 0) { showToast('请先选择要创建草稿的商品', 'error'); return; }
  if (!state.mallId) { showToast('请先连接目标店铺', 'error'); return; }
  resetCollectionRunCounters();

  const progressSection = document.getElementById('collectionProgressSection');
  const progressFill = document.getElementById('collectionProgressFill');
  const progressText = document.getElementById('collectionProgressText');
  const logList = document.getElementById('collectionLogList');
  if (progressSection) progressSection.style.display = '';
  if (progressFill) progressFill.style.width = '0%';
  if (progressText) progressText.textContent = `准备复制 ${selectedItems.length} 个商品...`;
  setCollectionProgressStage('准备凌风复制');
  if (logList) logList.innerHTML = '';

  let success = 0;
  let fail = 0;
  for (let i = 0; i < selectedItems.length; i++) {
    const item = selectedItems[i];
    const pct = Math.round(((i + 1) / selectedItems.length) * 100);
    if (progressFill) progressFill.style.width = pct + '%';
    if (progressText) progressText.textContent = `复制商品 ${i + 1}/${selectedItems.length}：${item.title || item.goodsId || '未知商品'}`;
    setCollectionProgressStage(`凌风复制 ${i + 1}/${selectedItems.length}`);
    item.collectionProgress = item.collectionProgress || {};
    item.collectionProgress.draft = 'running';

    const ready = validateCollectionReady(item, 'draft');
    if (!ready.ok) {
      fail += 1;
      collectionState.failCount += 1;
      item.collectionProgress.draft = 'error';
      addCollectionLog(logList, `❌ ${item.title || item.goodsId || '商品'} - ${ready.message}`, 'error');
      renderCollectionResults();
      continue;
    }

    const draftData = {
      ...buildCollectionPayload(item),
      openAfterCreate: selectedItems.length === 1
    };

    try {
      const draftResp = await safeRuntimeSendMessage({
        type: 'COPY_MOVE_PRODUCT_TO_DRAFT',
        productData: draftData
      }, { success: false, error: '复制商品到草稿失败' }, 60000);
      if (!draftResp?.success && draftResp?.error) throw new Error(draftResp.error);

      success += 1;
      collectionState.successCount += 1;
      collectionState.draftCount += 1;
      item.collectionProgress.draft = 'done';
      item.productDraftId = draftResp.productDraftId || draftResp.data?.productDraftId || '';
      item.editUrl = draftResp.editUrl || '';
      addCollectionLog(logList, `✅ ${item.title || item.goodsId || '商品'} - 已按凌风逻辑复制到草稿${item.productDraftId ? `（${item.productDraftId}）` : ''}`, 'success');
    } catch (err) {
      fail += 1;
      collectionState.failCount += 1;
      item.collectionProgress.draft = 'error';
      addCollectionLog(logList, `❌ ${item.title || item.goodsId || '商品'} - ${err.message}`, 'error');
      renderCollectionResults();
    }
  }

  if (progressText) progressText.textContent = `复制完成：成功 ${success} 个，失败 ${fail} 个`;
  setCollectionProgressStage(`凌风复制完成 成功 ${success} 失败 ${fail}`);
  renderCollectionResults();
  showToast(`复制完成：成功 ${success}，失败 ${fail}`, success > 0 ? 'success' : 'error');
}

// 清空复制数据
function clearCollection() {
  collectionState.items = [];
  collectionState.selected.clear();
  resetCollectionRunCounters();
  renderCollectionResults();
  showToast('复制任务已清空', 'info');
}

// ========== 财务数据 Tab ==========
const financeState = {
  available: 0,
  frozen: 0,
  pending: 0,
  pendingAreas: { global: 0, eu: 0, us: 0 },
  stockValue: 0,
  returnValue: 0,
  source: '',
  partialErrors: [],
  history: []
};

function financeToday() {
  const d = new Date();
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}

async function persistFinanceSnapshot() {
  try {
    const token = await getServerAuthToken();
    if (!token || !state.mallId) return;
    await fetch(`${SERVER_URL}/data/finance`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        mallId: state.mallId,
        date: financeToday(),
        availableBalance: financeState.available,
        frozenBalance: financeState.frozen,
        pendingSettlement: financeState.pending
      })
    }).catch(() => null);
  } catch (err) {
    console.warn('[小青蛙电商助手] 保存财务快照失败:', err);
  }
}

async function refreshFinanceData() {
  if (!requireAdminFeature('财务数据')) return;
  showToast('正在获取财务数据...', 'info');
  
  try {
    // 通过 background.js 调用 TEMU 后台 API
    const result = await safeRuntimeSendMessage({
      type: 'GET_FINANCE_DATA'
    }, { success: false, error: '获取财务数据失败' }, 30000);

    if (result && result.success) {
      financeState.available = result.data.available || 0;
      financeState.frozen = result.data.frozen || 0;
      financeState.pending = result.data.pending || 0;
      financeState.pendingAreas = result.data.pendingAreas || { global: 0, eu: 0, us: 0 };
      financeState.stockValue = result.data.stockValue || 0;
      financeState.returnValue = result.data.returnValue || 0;
      financeState.source = result.data.source || '';
      financeState.partialErrors = Array.isArray(result.data.partialErrors) ? result.data.partialErrors : [];
      financeState.history = result.data.history || [];
      renderFinanceData();
      persistFinanceSnapshot();
      showToast('财务数据已更新', 'success');
    } else {
      showToast(result?.error || '获取财务数据失败', 'error');
    }
  } catch (err) {
    showToast('获取财务数据失败：' + err.message, 'error');
  }
}

async function queryFinanceHistory() {
  if (!requireAdminFeature('财务数据')) return;
  const startDate = document.getElementById('financeStartDate')?.value || '';
  const endDate = document.getElementById('financeEndDate')?.value || '';
  showToast('正在查询资金流水...', 'info');
  try {
    const result = await safeRuntimeSendMessage({
      type: 'GET_FINANCE_HISTORY',
      startDate,
      endDate,
      page: 1,
      pageSize: 100
    }, { success: false, error: '获取流水失败' }, 30000);
    if (!result?.success) throw new Error(result?.error || '获取流水失败');
    financeState.history = Array.isArray(result.data) ? result.data : [];
    renderFinanceData();
    showToast('资金流水已更新', 'success');
  } catch (err) {
    showToast('查询流水失败：' + (err.message || String(err)), 'error');
  }
}

function renderFinanceData() {
  const fmt = (cents) => '¥' + (cents / 100).toFixed(2);
  
  const availableEl = document.getElementById('financeAvailable');
  const frozenEl = document.getElementById('financeFrozen');
  const pendingEl = document.getElementById('financePending');
  const totalEl = document.getElementById('financeTotal');

  if (availableEl) availableEl.textContent = fmt(financeState.available);
  if (frozenEl) frozenEl.textContent = fmt(financeState.frozen);
  if (pendingEl) pendingEl.textContent = fmt(financeState.pending);
  if (totalEl) totalEl.textContent = fmt(financeState.available + financeState.frozen + financeState.pending);

  const chart = document.getElementById('financeBarChart');
  if (chart) {
    const pendingAreas = financeState.pendingAreas || {};
    chart.innerHTML = `
      <div class="finance-overview-grid">
        <div class="stat-card" style="box-shadow:none;"><div class="stat-card-label">备货单货值</div><div class="stat-card-value" style="color:var(--accent-red);font-size:18px;">${fmt(financeState.stockValue)}</div></div>
        <div class="stat-card" style="box-shadow:none;"><div class="stat-card-label">退货货值</div><div class="stat-card-value" style="color:var(--accent-amber);font-size:18px;">${fmt(financeState.returnValue)}</div></div>
        <div class="stat-card" style="box-shadow:none;"><div class="stat-card-label">美国待结算</div><div class="stat-card-value" style="color:var(--accent-blue);font-size:18px;">${fmt(pendingAreas.us || 0)}</div></div>
        <div class="stat-card" style="box-shadow:none;"><div class="stat-card-label">欧区待结算</div><div class="stat-card-value" style="color:var(--accent-blue);font-size:18px;">${fmt(pendingAreas.eu || 0)}</div></div>
        <div class="stat-card" style="box-shadow:none;"><div class="stat-card-label">全球待结算</div><div class="stat-card-value" style="color:var(--accent-blue);font-size:18px;">${fmt(pendingAreas.global || 0)}</div></div>
        <div style="grid-column:1/-1;color:var(--text-muted);font-size:12px;line-height:1.6;">
          数据来源：${escapeHtml(financeState.source || 'TEMU 财务接口')}
          ${financeState.partialErrors.length ? `；部分区域未登录或接口无响应：${escapeHtml(financeState.partialErrors.join('；'))}` : ''}
        </div>
      </div>
    `;
  }

  // 渲染流水表格
  const tbody = document.getElementById('financeTableBody');
  if (!tbody) return;

  if (financeState.history.length === 0) {
    tbody.innerHTML = '<tr><td colspan="5" class="empty">暂无流水数据</td></tr>';
    return;
  }

  tbody.innerHTML = financeState.history.map(item => `
    <tr>
      <td>${escapeHtml(item.date || '-')}</td>
      <td>${escapeHtml(item.type || '-')}</td>
      <td>${escapeHtml(item.description || '-')}</td>
      <td style="color:${item.amount >= 0 ? 'var(--accent-green)' : 'var(--accent-red)'}">${item.amount >= 0 ? '+' : ''}${fmt(item.amount)}</td>
      <td>${fmt(item.balance || 0)}</td>
    </tr>
  `).join('');
}

function exportFinanceHistory() {
  const rows = financeState.history || [];
  if (!rows.length) {
    showToast('暂无财务流水可导出', 'warning');
    return;
  }
  const escapeCsv = (value) => `"${String(value ?? '').replace(/"/g, '""')}"`;
  const fmtMoney = (cents) => ((Number(cents) || 0) / 100).toFixed(2);
  const csv = [
    ['日期', '类型', '描述', '金额', '余额'].map(escapeCsv).join(','),
    ...rows.map(item => [
      item.date || '',
      item.type || '',
      item.description || '',
      fmtMoney(item.amount),
      fmtMoney(item.balance)
    ].map(escapeCsv).join(','))
  ].join('\r\n');
  const blob = new Blob([`\uFEFF${csv}`], { type: 'text/csv;charset=utf-8' });
  triggerBrowserDownload(`xiaoqingwa-finance-${financeToday()}.csv`, blob);
  showToast('财务数据已导出', 'success');
}


